Add working basic component based GUI system. Allows to open and close windows without restarting.
This commit is contained in:
parent
f18ad715fb
commit
eee13026b0
|
@ -3,7 +3,6 @@ This is the repository of a Nintendo Entertainment System (NES) emulator in acti
|
|||
currently complete but can run and partially display a ROM. Note that the project may support Windows and MacOS, but was
|
||||
only tested on Linux. Here is how to run the project:
|
||||
- Change the ```rom_path``` at line 26 of ```main.c```
|
||||
- Optionally, change ```gui.debug_enabled``` to ```true``` at line 33 of ```gui.c``` to enable debugging
|
||||
- Generate the Makefile with CMake: ```cmake .```
|
||||
- Build the project with Make: ```make```
|
||||
- Run the emulator: ```./nes_emulator```
|
||||
|
@ -21,6 +20,7 @@ only tested on Linux. Here is how to run the project:
|
|||
- SDL
|
||||
|
||||
## Development Roadmap
|
||||
- Basic component based GUI: Done
|
||||
- CPU
|
||||
- RAM: Done
|
||||
- ROM: Done (iNes 1.0 format only)
|
||||
|
@ -42,7 +42,7 @@ only tested on Linux. Here is how to run the project:
|
|||
- MMC5: To Do
|
||||
- ...
|
||||
- Debug
|
||||
- Frame Delay: Done
|
||||
- Frame Delay: Broken, removed
|
||||
- Pattern Table Viewer: Done
|
||||
- Nametable Viewer: Done
|
||||
- CPU Debugger: To Do
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
set(HEADERS gui.h main_window.h)
|
||||
set(SOURCE gui.c main_window.c)
|
||||
|
||||
if (NES_DEBUG)
|
||||
list(APPEND HEADERS char_map.h pattern_window.h nametable_window.h dbg_pattern_table.h dbg_nametable.h dbg_palette.h)
|
||||
list(APPEND SOURCE char_map.c pattern_window.c nametable_window.c dbg_pattern_table.c dbg_nametable.c dbg_palette.c)
|
||||
endif (NES_DEBUG)
|
||||
set(HEADERS actions.h gui.h main_window.h char_map.h pattern_window.h nametable_window.h dbg_pattern_table.h dbg_nametable.h dbg_palette.h)
|
||||
set(SOURCE actions.c gui.c main_window.c char_map.c pattern_window.c nametable_window.c dbg_pattern_table.c dbg_nametable.c dbg_palette.c)
|
||||
|
||||
add_library(nes_gui ${SOURCE} ${HEADERS})
|
||||
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
//
|
||||
// Created by william on 9/2/24.
|
||||
//
|
||||
|
||||
#include "actions.h"
|
||||
#include "gui.h"
|
||||
|
||||
void process_action(ActionType type) {
|
||||
switch (type) {
|
||||
case ACTION_TYPE_OPEN_WINDOW_NAMETABLE:
|
||||
gui_window_create(WINDOW_TYPE_NAMETABLE);
|
||||
break;
|
||||
case ACTION_TYPE_OPEN_WINDOW_PATTERN_TABLE:
|
||||
gui_window_create(WINDOW_TYPE_PATTERN_TABLE);
|
||||
break;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
//
|
||||
// Created by william on 9/2/24.
|
||||
//
|
||||
|
||||
#ifndef NES_EMULATOR_ACTIONS_H
|
||||
#define NES_EMULATOR_ACTIONS_H
|
||||
|
||||
typedef enum {
|
||||
ACTION_TYPE_OPEN_WINDOW_NAMETABLE = 1,
|
||||
ACTION_TYPE_OPEN_WINDOW_PATTERN_TABLE = 2
|
||||
} ActionType;
|
||||
|
||||
void process_action(ActionType type);
|
||||
|
||||
#endif //NES_EMULATOR_ACTIONS_H
|
|
@ -7,6 +7,9 @@
|
|||
#include "log.h"
|
||||
#include "component.h"
|
||||
|
||||
// A sequential window ID
|
||||
static int next_window_id = 1;
|
||||
|
||||
/**
|
||||
* Easy declaration of commonly used component loop. The current component is in the 'component' variable.
|
||||
* Put the loop's body between FOR_EACH_COMPONENT and END_FOR_EACH_COMPONENT.
|
||||
|
@ -68,6 +71,7 @@ Window window_create(char *title, int width, int height, int scale) {
|
|||
}
|
||||
|
||||
Window window;
|
||||
window.id = next_window_id++;
|
||||
window.width = width;
|
||||
window.height = height;
|
||||
window.scale = scale;
|
||||
|
|
|
@ -15,6 +15,7 @@ typedef struct window_sdl_context {
|
|||
} WindowSdlContext;
|
||||
|
||||
typedef struct window {
|
||||
int id;
|
||||
int width;
|
||||
int height;
|
||||
int scale;
|
||||
|
|
|
@ -49,12 +49,13 @@ static inline MenuItemComponent *menu_get_next_item(LinkedListCursor *cursor) {
|
|||
return next_node->data;
|
||||
}
|
||||
|
||||
MenuComponent *menu_create(Window *window, TTF_Font *font) {
|
||||
MenuComponent *menu_create(Window *window, TTF_Font *font, menu_action_processor action_processor) {
|
||||
MenuComponent *menu = malloc(sizeof(MenuComponent));
|
||||
|
||||
menu->window_width = window->width * window->scale;
|
||||
menu->visible = false;
|
||||
|
||||
menu->action_processor = action_processor;
|
||||
menu->items = linked_list_create(false);
|
||||
menu->highlight_item = NULL;
|
||||
|
||||
|
@ -66,11 +67,11 @@ MenuComponent *menu_create(Window *window, TTF_Font *font) {
|
|||
return menu;
|
||||
}
|
||||
|
||||
MenuItemComponent *menu_item_create(char *label, on_click_callback on_click) {
|
||||
MenuItemComponent *menu_item_create(char *label, int click_action_type) {
|
||||
MenuItemComponent *menu_item = malloc(sizeof(MenuItemComponent));
|
||||
|
||||
menu_item->label = label;
|
||||
menu_item->on_click = on_click;
|
||||
menu_item->click_action_type = click_action_type;
|
||||
menu_item->sub_items = linked_list_create(false);
|
||||
menu_item->is_highlighted = false;
|
||||
|
||||
|
@ -234,7 +235,9 @@ bool menu_mouse_click(MenuComponent *menu) {
|
|||
return false;
|
||||
}
|
||||
|
||||
log_info("%s", menu->highlight_item->label);
|
||||
int action_type = menu->highlight_item->click_action_type;
|
||||
menu->action_processor(action_type);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -17,11 +17,9 @@
|
|||
#define MENU_ITEM_MARGIN_X 20
|
||||
#define MENU_ITEM_MARGIN_Y 4
|
||||
|
||||
typedef void (*on_click_callback)();
|
||||
|
||||
typedef struct menu_item_component {
|
||||
char *label;
|
||||
on_click_callback on_click;
|
||||
int click_action_type;
|
||||
|
||||
bool is_highlighted;
|
||||
|
||||
|
@ -31,10 +29,13 @@ typedef struct menu_item_component {
|
|||
SDL_Rect collision_rect;
|
||||
} MenuItemComponent;
|
||||
|
||||
typedef void (*menu_action_processor)(int);
|
||||
|
||||
typedef struct menu_component {
|
||||
int window_width;
|
||||
bool visible;
|
||||
|
||||
menu_action_processor action_processor;
|
||||
LinkedList items;
|
||||
MenuItemComponent *highlight_item;
|
||||
|
||||
|
@ -48,9 +49,10 @@ typedef struct menu_component {
|
|||
* Creates a menu fow a window.
|
||||
* @param window A reference to the window
|
||||
* @param font A reference to the TTF font to use to render text
|
||||
* @param action_processor A reference to a function that will processes actions
|
||||
* @return A reference to the menu component
|
||||
*/
|
||||
MenuComponent *menu_create(Window *window, TTF_Font *font);
|
||||
MenuComponent *menu_create(Window *window, TTF_Font *font, menu_action_processor action_processor);
|
||||
|
||||
/**
|
||||
* Creates a menu item. Can be configured with a callback function which will be called when the menu item is clicked.
|
||||
|
@ -59,7 +61,7 @@ MenuComponent *menu_create(Window *window, TTF_Font *font);
|
|||
* @param on_click The callback function to call when clicked
|
||||
* @return A reference to the menu item
|
||||
*/
|
||||
MenuItemComponent *menu_item_create(char *label, on_click_callback on_click);
|
||||
MenuItemComponent *menu_item_create(char *label, int click_action_type);
|
||||
|
||||
/**
|
||||
* Adds an item to a menu.
|
||||
|
|
|
@ -12,7 +12,15 @@
|
|||
DebugNameTable dbg_nametable;
|
||||
|
||||
void dbg_nametable_init() {
|
||||
if (dbg_nametable.initialized) {
|
||||
// Already initialized
|
||||
return;
|
||||
}
|
||||
|
||||
dbg_pattern_table_init();
|
||||
|
||||
dbg_nametable.vertical_mirroring = rom_get()->nametable_mirrored;
|
||||
dbg_nametable.initialized = true;
|
||||
}
|
||||
|
||||
void dbg_nametable_build_bank(byte *nametable, DebugTile *bank) {
|
||||
|
|
|
@ -22,6 +22,7 @@ typedef struct dbg_nametable {
|
|||
DebugTile bank_0[NAMETABLE_BANK_SIZE];
|
||||
DebugTile bank_1[NAMETABLE_BANK_SIZE];
|
||||
bool vertical_mirroring;
|
||||
bool initialized;
|
||||
} DebugNameTable;
|
||||
|
||||
/**
|
||||
|
|
|
@ -18,7 +18,7 @@ pixel dbg_color_list[0x40] = COLOR_LIST;
|
|||
COPY_PALETTE((memory)[(base_addr) + 0xd], (dest)[3]) \
|
||||
|
||||
|
||||
void dbg_palette_init() {
|
||||
void dbg_palette_update() {
|
||||
byte *memory = ppu_get_state()->memory.palette;
|
||||
|
||||
palette_memory.universal_background_color = memory[0];
|
||||
|
|
|
@ -19,7 +19,7 @@ typedef struct dbg_palette_memory {
|
|||
DebugPalette sprite_palettes[PALETTE_COUNT];
|
||||
} DebugPaletteMemory;
|
||||
|
||||
void dbg_palette_init();
|
||||
void dbg_palette_update();
|
||||
|
||||
pixel dbg_get_background_color(byte palette, byte data);
|
||||
|
||||
|
|
|
@ -23,10 +23,16 @@ void dbg_pattern_table_build_bank(DebugPattern *bank, byte *pattern_memory) {
|
|||
}
|
||||
|
||||
void dbg_pattern_table_init() {
|
||||
if (pattern_table.initialized) {
|
||||
// Already initialized
|
||||
return;
|
||||
}
|
||||
|
||||
byte *pattern_memory = system_get_mapper()->ppu_read(0);
|
||||
|
||||
dbg_pattern_table_build_bank(pattern_table.bank_0, pattern_memory);
|
||||
dbg_pattern_table_build_bank(pattern_table.bank_1, &pattern_memory[PATTERN_BANK_SIZE]);
|
||||
pattern_table.initialized = true;
|
||||
}
|
||||
|
||||
DebugPattern dbg_pattern_get(int pattern_id, int bank) {
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#ifndef NES_EMULATOR_DBG_PATTERN_TABLE_H
|
||||
#define NES_EMULATOR_DBG_PATTERN_TABLE_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "../include/types.h"
|
||||
|
||||
#define PATTERN_BANK_SIZE 0x1000
|
||||
|
@ -29,6 +30,7 @@ typedef struct dbg_pattern {
|
|||
typedef struct dbg_pattern_table {
|
||||
DebugPattern bank_0[PATTERN_TABLE_SIZE];
|
||||
DebugPattern bank_1[PATTERN_TABLE_SIZE];
|
||||
bool initialized;
|
||||
} DebugPatternTable;
|
||||
|
||||
/**
|
||||
|
|
199
gui/gui.c
199
gui/gui.c
|
@ -3,6 +3,7 @@
|
|||
//
|
||||
|
||||
#include <SDL_ttf.h>
|
||||
#include <assert.h>
|
||||
#include "log.h"
|
||||
#include "gui.h"
|
||||
#include "main_window.h"
|
||||
|
@ -14,29 +15,95 @@
|
|||
#include "char_map.h"
|
||||
#include "dbg_palette.h"
|
||||
|
||||
#if DEBUG
|
||||
#define WINDOW_ID_MAIN 3
|
||||
#else
|
||||
#define WINDOW_ID_MAIN 1
|
||||
#endif
|
||||
|
||||
typedef struct nes_gui {
|
||||
NesMainWindow main_window;
|
||||
NesPatternWindow pattern_window;
|
||||
NesNametableWindow nametable_window;
|
||||
bool window_types_open[WINDOW_TYPE_MAX + 1];
|
||||
void *window_types_ref[WINDOW_TYPE_MAX + 1];
|
||||
int window_types_ids[WINDOW_TYPE_MAX + 1];
|
||||
|
||||
TTF_Font *font;
|
||||
|
||||
Uint32 last_frame_tick;
|
||||
Uint32 frame_delay;
|
||||
|
||||
#if DEBUG
|
||||
unsigned long tick;
|
||||
#endif
|
||||
} NesGui;
|
||||
NesGui gui;
|
||||
|
||||
#define GUI_WINDOW_OPEN_(window_type) gui.window_types_open[window_type]
|
||||
#define GUI_WINDOW_REF_(window_type) gui.window_types_ref[window_type]
|
||||
#define GUI_WINDOW_ACTION_BASE(window_type, call) \
|
||||
if (GUI_WINDOW_OPEN_(window_type)) { \
|
||||
call(GUI_WINDOW_REF_(window_type)); \
|
||||
}
|
||||
|
||||
void gui_window_create(WindowType type) {
|
||||
void **ref = &gui.window_types_ref[type];
|
||||
if (gui.window_types_open[type]) {
|
||||
return;
|
||||
}
|
||||
gui.window_types_open[type] = true;
|
||||
int window_id = -1;
|
||||
|
||||
switch (type) {
|
||||
case WINDOW_TYPE_MAIN:
|
||||
*ref = main_window_create(&window_id);
|
||||
break;
|
||||
case WINDOW_TYPE_NAMETABLE:
|
||||
*ref = nametable_window_create(&window_id);
|
||||
break;
|
||||
case WINDOW_TYPE_PATTERN_TABLE:
|
||||
*ref = pattern_window_create(&window_id);
|
||||
break;
|
||||
}
|
||||
|
||||
gui.window_types_ids[type] = window_id;
|
||||
}
|
||||
|
||||
void gui_window_destroy(WindowType type) {
|
||||
bool *open = &gui.window_types_open[type];
|
||||
if (*open == false) {
|
||||
// The window doesn't exist
|
||||
return;
|
||||
}
|
||||
|
||||
void *ref = gui.window_types_ref[type];
|
||||
switch (type) {
|
||||
case WINDOW_TYPE_MAIN:
|
||||
main_window_destroy(ref);
|
||||
break;
|
||||
case WINDOW_TYPE_NAMETABLE:
|
||||
nametable_window_destroy(ref);
|
||||
break;
|
||||
case WINDOW_TYPE_PATTERN_TABLE:
|
||||
pattern_window_destroy(ref);
|
||||
break;
|
||||
}
|
||||
|
||||
gui.window_types_ids[type] = -1;
|
||||
*open = false;
|
||||
}
|
||||
|
||||
void gui_window_destroy_by_id(int window_id) {
|
||||
WindowType type = -1;
|
||||
for (int i = 0; i <= WINDOW_TYPE_MAX; i++) {
|
||||
if (gui.window_types_ids[i] == window_id) {
|
||||
type = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (type == -1) {
|
||||
// Close event is sent twice?
|
||||
log_error("Couldn't find window with ID %d", window_id);
|
||||
return;
|
||||
}
|
||||
|
||||
gui_window_destroy(type);
|
||||
}
|
||||
|
||||
bool gui_init() {
|
||||
memset(gui.window_types_open, false, sizeof(bool) * WINDOW_TYPE_MAX);
|
||||
memset(gui.window_types_ref, 0, sizeof(void *) * WINDOW_TYPE_MAX);
|
||||
|
||||
TTF_Init();
|
||||
gui.font = TTF_OpenFont("./nintendo-nes-font.ttf", 16);
|
||||
if (gui.font == NULL) {
|
||||
|
@ -44,56 +111,40 @@ bool gui_init() {
|
|||
return false;
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
gui.tick = 0;
|
||||
pattern_window_init(&gui.pattern_window);
|
||||
nametable_window_init(&gui.nametable_window);
|
||||
|
||||
char_map_init(gui.main_window.window.sdl_context.renderer, gui.font);
|
||||
#endif
|
||||
|
||||
main_window_init(&gui.main_window);
|
||||
memset(gui.window_types_ids, -1, (WINDOW_TYPE_MAX + 1) * sizeof(*gui.window_types_ids));
|
||||
gui_window_create(WINDOW_TYPE_MAIN);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void gui_uninit() {
|
||||
main_window_uninit(&gui.main_window);
|
||||
|
||||
#if DEBUG
|
||||
char_map_uninit();
|
||||
|
||||
pattern_window_uninit(&gui.pattern_window);
|
||||
nametable_window_uninit(&gui.nametable_window);
|
||||
#endif
|
||||
void gui_free() {
|
||||
for (int i = 0; i < WINDOW_TYPE_MAX; i++) {
|
||||
gui_window_destroy(i);
|
||||
}
|
||||
|
||||
TTF_CloseFont(gui.font);
|
||||
}
|
||||
|
||||
void gui_post_sysinit() {
|
||||
#if DEBUG
|
||||
dbg_palette_init();
|
||||
dbg_pattern_table_init();
|
||||
dbg_nametable_init();
|
||||
|
||||
// TODO: The background_texture is rendered before the palette data is in the PPU memory, so the only color is grey
|
||||
pattern_window_build_table(&gui.pattern_window);
|
||||
nametable_window_update(&gui.nametable_window);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool lctrl = false;
|
||||
|
||||
int gui_input() {
|
||||
assert(gui.window_types_open[WINDOW_TYPE_MAIN]);
|
||||
SDL_Event event;
|
||||
|
||||
#if DEBUG
|
||||
PPUDebugFlags *ppu_debug = &ppu_get_state()->debug;
|
||||
#endif
|
||||
|
||||
while (SDL_PollEvent(&event)) {
|
||||
if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE) {
|
||||
return -1;
|
||||
Uint32 window_id = event.window.windowID;
|
||||
for (int type = 0; type <= WINDOW_TYPE_MAX; type++) {
|
||||
bool is_main_window = gui.window_types_ids[WINDOW_TYPE_MAIN] == window_id;
|
||||
|
||||
gui_window_destroy_by_id(window_id);
|
||||
|
||||
if (is_main_window) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_LCTRL) {
|
||||
|
@ -108,59 +159,53 @@ int gui_input() {
|
|||
case SDLK_p:
|
||||
system_toggle_pause(lctrl);
|
||||
break;
|
||||
#if DEBUG
|
||||
case SDLK_t:
|
||||
ppu_debug->flags.tile_debugger = !ppu_debug->flags.tile_debugger;
|
||||
break;
|
||||
case SDLK_n:
|
||||
ppu_debug->flags.tile_debugger_pattern_half = (ppu_debug->flags.tile_debugger_pattern_half + 1) % 3;
|
||||
break;
|
||||
default:
|
||||
pattern_window_key_up(&gui.pattern_window, event.key.keysym.sym);
|
||||
break;
|
||||
#else
|
||||
default:
|
||||
case SDLK_t:
|
||||
ppu_debug->flags.tile_debugger = !ppu_debug->flags.tile_debugger;
|
||||
break;
|
||||
case SDLK_n:
|
||||
ppu_debug->flags.tile_debugger_pattern_half = (ppu_debug->flags.tile_debugger_pattern_half + 1) % 3;
|
||||
break;
|
||||
default:
|
||||
if (gui.window_types_open[WINDOW_TYPE_PATTERN_TABLE]) {
|
||||
pattern_window_key_up(gui.window_types_ref[WINDOW_TYPE_PATTERN_TABLE], event.key.keysym.sym);
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void *main_window_ref = gui.window_types_ref[WINDOW_TYPE_MAIN];
|
||||
if (event.type == SDL_MOUSEMOTION) {
|
||||
int x = event.motion.x;
|
||||
int y = event.motion.y;
|
||||
|
||||
if (event.window.windowID == WINDOW_ID_MAIN) {
|
||||
main_window_mouse_motion(&gui.main_window, x, y);
|
||||
if (gui.window_types_ids[WINDOW_TYPE_MAIN] == event.window.windowID) {
|
||||
main_window_mouse_motion(main_window_ref, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
if (event.type == SDL_MOUSEBUTTONUP && event.window.windowID == WINDOW_ID_MAIN) {
|
||||
main_window_mouse_click(&gui.main_window);
|
||||
if (event.type == SDL_MOUSEBUTTONUP && gui.window_types_ids[WINDOW_TYPE_MAIN] == event.window.windowID) {
|
||||
main_window_mouse_click(main_window_ref);
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
#define GUI_WINDOW_RENDER(window_type, prefix) GUI_WINDOW_ACTION_BASE(window_type, prefix ## _window_render)
|
||||
|
||||
void gui_render() {
|
||||
main_window_render(&gui.main_window, ppu_get_state()->pixels);
|
||||
assert(gui.window_types_open[WINDOW_TYPE_MAIN]);
|
||||
main_window_render(gui.window_types_ref[WINDOW_TYPE_MAIN], ppu_get_state()->pixels);
|
||||
|
||||
#if DEBUG
|
||||
dbg_palette_init();
|
||||
pattern_window_render(&gui.pattern_window);
|
||||
// Update the nametable
|
||||
GUI_WINDOW_ACTION_BASE(WINDOW_TYPE_NAMETABLE, nametable_window_update);
|
||||
GUI_WINDOW_RENDER(WINDOW_TYPE_NAMETABLE, nametable);
|
||||
|
||||
nametable_window_update(&gui.nametable_window);
|
||||
nametable_window_render(&gui.nametable_window);
|
||||
// Update the pattern table
|
||||
GUI_WINDOW_ACTION_BASE(WINDOW_TYPE_PATTERN_TABLE, pattern_window_update);
|
||||
GUI_WINDOW_RENDER(WINDOW_TYPE_PATTERN_TABLE, pattern)
|
||||
|
||||
gui.tick++;
|
||||
#endif
|
||||
}
|
||||
|
||||
void gui_present() {
|
||||
#if DEBUG
|
||||
pattern_window_present(&gui.pattern_window);
|
||||
nametable_window_present(&gui.nametable_window);
|
||||
#endif
|
||||
}
|
||||
|
||||
void gui_delay() {
|
||||
|
@ -178,8 +223,4 @@ void gui_delay() {
|
|||
|
||||
TTF_Font *gui_get_font() {
|
||||
return gui.font;
|
||||
}
|
||||
|
||||
unsigned int gui_get_frame_delay() {
|
||||
return gui.frame_delay;
|
||||
}
|
41
gui/gui.h
41
gui/gui.h
|
@ -8,17 +8,50 @@
|
|||
#include <stdbool.h>
|
||||
#include <SDL_ttf.h>
|
||||
|
||||
typedef enum {
|
||||
WINDOW_TYPE_MAIN = 0,
|
||||
WINDOW_TYPE_NAMETABLE = 1,
|
||||
WINDOW_TYPE_PATTERN_TABLE = 2,
|
||||
} WindowType;
|
||||
#define WINDOW_TYPE_MAX WINDOW_TYPE_PATTERN_TABLE
|
||||
|
||||
/**
|
||||
* Initializes the graphical user interface of the emulator.
|
||||
* @return A boolean indicating if the GUI was successfully initialized.
|
||||
*/
|
||||
bool gui_init();
|
||||
void gui_uninit();
|
||||
|
||||
void gui_post_sysinit();
|
||||
/**
|
||||
* Free the resources used by the graphical user interface.
|
||||
*/
|
||||
void gui_free();
|
||||
|
||||
/**
|
||||
* Creates and open a window. If a window of the given type already exists, focus it.
|
||||
* @param type The type of window to open.
|
||||
*/
|
||||
void gui_window_create(WindowType type);
|
||||
|
||||
/**
|
||||
* Process user input events received since the last call to gui_input.
|
||||
* @return An integer indicating if the user closed the main window (-1).
|
||||
*/
|
||||
int gui_input();
|
||||
|
||||
/**
|
||||
* Renders the graphical user interface to the screen.
|
||||
*/
|
||||
void gui_render();
|
||||
void gui_present();
|
||||
|
||||
/**
|
||||
* Blocks until the next frame should be drawn, making the frame rate 60 hertz.
|
||||
*/
|
||||
void gui_delay();
|
||||
|
||||
/**
|
||||
* Gets the font used for the graphical user interface.
|
||||
* @return A reference to the TTF font.
|
||||
*/
|
||||
TTF_Font* gui_get_font();
|
||||
unsigned int gui_get_frame_delay();
|
||||
|
||||
#endif //NES_EMULATOR_GUI_H
|
||||
|
|
|
@ -8,52 +8,46 @@
|
|||
#include "char_map.h"
|
||||
#include "gui.h"
|
||||
#include "components/window_menu.h"
|
||||
#include "actions.h"
|
||||
|
||||
void main_window_init(NesMainWindow *window) {
|
||||
void main_window_menu_process_action(int action_type) {
|
||||
process_action(action_type);
|
||||
}
|
||||
|
||||
MainWindow *main_window_create(int* window_id) {
|
||||
MainWindow *window = malloc(sizeof(MainWindow));
|
||||
window->window = window_create("NES Emulator", MAIN_WINDOW_WIDTH, MAIN_WINDOW_HEIGHT, MAIN_WINDOW_SCALE);
|
||||
window->texture = window_create_texture(&window->window, SDL_TEXTUREACCESS_STREAMING);
|
||||
|
||||
MenuComponent *menu = menu_create(&window->window, gui_get_font());
|
||||
MenuComponent *menu = menu_create(&window->window, gui_get_font(), &main_window_menu_process_action);
|
||||
|
||||
MenuItemComponent *mi_file = menu_item_create("FILE", NULL);
|
||||
menu_append(menu, mi_file);
|
||||
MenuItemComponent *mi_file_open = menu_item_create("OPEN ROM...", NULL);
|
||||
menu_item_append(mi_file, mi_file_open);
|
||||
// MenuItemComponent *mi_file = menu_item_create("FILE", ACTION_TYPE_OPEN_);
|
||||
// menu_append(menu, mi_file);
|
||||
// MenuItemComponent *mi_file_open = menu_item_create("OPEN ROM...", NULL);
|
||||
// menu_item_append(mi_file, mi_file_open);
|
||||
|
||||
MenuItemComponent *mi_debug = menu_item_create("DEBUG", NULL);
|
||||
MenuItemComponent *mi_debug = menu_item_create("DEBUG", -1);
|
||||
menu_append(menu, mi_debug);
|
||||
MenuItemComponent *mi_debug_nametable = menu_item_create("NAMETABLE", NULL);
|
||||
MenuItemComponent *mi_debug_nametable = menu_item_create("NAMETABLE", ACTION_TYPE_OPEN_WINDOW_NAMETABLE);
|
||||
menu_item_append(mi_debug, mi_debug_nametable);
|
||||
MenuItemComponent *mi_debug_pattern = menu_item_create("PATTERN TABLE", NULL);
|
||||
MenuItemComponent *mi_debug_pattern = menu_item_create("PATTERN TABLE", ACTION_TYPE_OPEN_WINDOW_PATTERN_TABLE);
|
||||
menu_item_append(mi_debug, mi_debug_pattern);
|
||||
|
||||
menu_build(menu);
|
||||
window_add_component(&window->window, menu_as_component(menu));
|
||||
|
||||
*window_id = window->window.id;
|
||||
return window;
|
||||
}
|
||||
|
||||
void main_window_uninit(NesMainWindow *window) {
|
||||
void main_window_destroy(MainWindow *window) {
|
||||
SDL_DestroyTexture(window->texture);
|
||||
window_destroy(&window->window);
|
||||
|
||||
free(window);
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
||||
void main_window_render_delay(SDL_Renderer *renderer) {
|
||||
Uint32 delay = gui_get_frame_delay();
|
||||
|
||||
char buffer[5];
|
||||
buffer[0] = (char) ((delay / 10) + 48);
|
||||
buffer[1] = (char) ((delay % 10) + 48);
|
||||
buffer[2] = ' ';
|
||||
buffer[3] = 'm';
|
||||
buffer[4] = 's';
|
||||
|
||||
char_map_render(renderer, buffer);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void main_window_render(NesMainWindow *window, pixel *pixels) {
|
||||
void main_window_render(MainWindow *window, pixel *pixels) {
|
||||
SDL_UpdateTexture(window->texture, NULL, pixels, 256 * sizeof(pixel));
|
||||
|
||||
window_render_texture(&window->window, window->texture);
|
||||
|
@ -62,10 +56,10 @@ void main_window_render(NesMainWindow *window, pixel *pixels) {
|
|||
window_present(&window->window);
|
||||
}
|
||||
|
||||
void main_window_mouse_motion(NesMainWindow *window, int x, int y) {
|
||||
void main_window_mouse_motion(MainWindow *window, int x, int y) {
|
||||
window_mouse_motion(&window->window, x, y);
|
||||
}
|
||||
|
||||
void main_window_mouse_click(NesMainWindow *window) {
|
||||
void main_window_mouse_click(MainWindow *window) {
|
||||
window_mouse_click(&window->window);
|
||||
}
|
|
@ -17,13 +17,24 @@ typedef struct nes_main_window {
|
|||
Window window;
|
||||
|
||||
SDL_Texture *texture;
|
||||
} NesMainWindow;
|
||||
} MainWindow;
|
||||
|
||||
void main_window_init(NesMainWindow *window);
|
||||
void main_window_uninit(NesMainWindow *window);
|
||||
/**
|
||||
* Creates an instance of the main window.
|
||||
* @return A reference to the created main window instance
|
||||
*/
|
||||
MainWindow *main_window_create(int *window_id);
|
||||
|
||||
void main_window_render(NesMainWindow *window, pixel* pixels);
|
||||
void main_window_mouse_motion(NesMainWindow *window, int x, int y);
|
||||
void main_window_mouse_click(NesMainWindow *window);
|
||||
/**
|
||||
* Destroys an instance of the main window.
|
||||
* @param window A reference to the window to destroy
|
||||
*/
|
||||
void main_window_destroy(MainWindow *window);
|
||||
|
||||
void main_window_render(MainWindow *window, pixel *pixels);
|
||||
|
||||
void main_window_mouse_motion(MainWindow *window, int x, int y);
|
||||
|
||||
void main_window_mouse_click(MainWindow *window);
|
||||
|
||||
#endif //NES_EMULATOR_MAIN_WINDOW_H
|
||||
|
|
|
@ -4,22 +4,32 @@
|
|||
|
||||
#include "nametable_window.h"
|
||||
#include "dbg_nametable.h"
|
||||
#include "dbg_palette.h"
|
||||
|
||||
#define NW_WIDTH (NW_ROW_TILE_COUNT * PATTERN_DRAW_SIZE)
|
||||
#define NW_HEIGHT (NW_ROW_COUNT * PATTERN_DRAW_SIZE)
|
||||
#define NW_BUFFER_SIZE (NAMETABLE_ROW_WIDTH * NAMETABLE_COL_HEIGHT * PATTERN_DRAW_SIZE * PATTERN_DRAW_SIZE)
|
||||
|
||||
void nametable_window_init(NesNametableWindow *window) {
|
||||
NametableWindow *nametable_window_create(int *window_id) {
|
||||
NametableWindow *window = malloc(sizeof(NametableWindow));
|
||||
|
||||
window->window = window_create("Nametable", NW_WIDTH, NW_HEIGHT, NW_SCALE);
|
||||
window->texture = window_create_texture(&window->window, SDL_TEXTUREACCESS_STREAMING);
|
||||
|
||||
dbg_nametable_init();
|
||||
|
||||
*window_id = window->window.id;
|
||||
return window;
|
||||
}
|
||||
|
||||
void nametable_window_uninit(NesNametableWindow *window) {
|
||||
void nametable_window_destroy(NametableWindow *window) {
|
||||
SDL_DestroyTexture(window->texture);
|
||||
window_destroy(&window->window);
|
||||
|
||||
free(window);
|
||||
}
|
||||
|
||||
void nametable_window_update_bank(NesNametableWindow *window, int bank, pixel *buffer) {
|
||||
void nametable_window_update_bank(NametableWindow *window, int bank, pixel *buffer) {
|
||||
dbg_nametable_render_bank(bank, buffer);
|
||||
|
||||
SDL_Rect rect;
|
||||
|
@ -31,7 +41,8 @@ void nametable_window_update_bank(NesNametableWindow *window, int bank, pixel *b
|
|||
SDL_UpdateTexture(window->texture, &rect, buffer, (NW_WIDTH / 2) * sizeof(pixel));
|
||||
}
|
||||
|
||||
void nametable_window_update(NesNametableWindow *window) {
|
||||
void nametable_window_update(NametableWindow *window) {
|
||||
dbg_palette_update();
|
||||
dbg_nametable_update();
|
||||
|
||||
pixel buffer[NW_BUFFER_SIZE * 4] = {0};
|
||||
|
@ -41,10 +52,7 @@ void nametable_window_update(NesNametableWindow *window) {
|
|||
nametable_window_update_bank(window, 3, buffer);
|
||||
}
|
||||
|
||||
void nametable_window_render(NesNametableWindow *window) {
|
||||
void nametable_window_render(NametableWindow *window) {
|
||||
window_render_texture(&window->window, window->texture);
|
||||
}
|
||||
|
||||
void nametable_window_present(NesNametableWindow *window) {
|
||||
window_present(&window->window);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,16 +15,30 @@
|
|||
typedef struct nes_nametable_window {
|
||||
Window window;
|
||||
SDL_Texture *texture;
|
||||
} NesNametableWindow;
|
||||
} NametableWindow;
|
||||
|
||||
void nametable_window_init(NesNametableWindow *window);
|
||||
/**
|
||||
* Creates a nametable window instance.
|
||||
* @return A reference to the created window
|
||||
*/
|
||||
NametableWindow *nametable_window_create(int *window_id);
|
||||
|
||||
void nametable_window_uninit(NesNametableWindow *window);
|
||||
/**
|
||||
* Destroys a nametable window instance.
|
||||
* @param window A reference to the window to destroy
|
||||
*/
|
||||
void nametable_window_destroy(NametableWindow *window);
|
||||
|
||||
void nametable_window_update(NesNametableWindow *window);
|
||||
/**
|
||||
* Updates the content of a nametable window with the current data of the PPU.
|
||||
* @param window A reference to the window to update
|
||||
*/
|
||||
void nametable_window_update(NametableWindow *window);
|
||||
|
||||
void nametable_window_render(NesNametableWindow *window);
|
||||
|
||||
void nametable_window_present(NesNametableWindow *window);
|
||||
/**
|
||||
* Renders a nemtable window to the screen.
|
||||
* @param window A reference to the window to render
|
||||
*/
|
||||
void nametable_window_render(NametableWindow *window);
|
||||
|
||||
#endif //NES_EMULATOR_NAMETABLE_WINDOW_H
|
||||
|
|
|
@ -4,23 +4,35 @@
|
|||
|
||||
#include "pattern_window.h"
|
||||
#include "dbg_pattern_table.h"
|
||||
#include "dbg_palette.h"
|
||||
|
||||
#define PW_WIDTH (PW_ROW_TILE_COUNT * PATTERN_DRAW_SIZE)
|
||||
#define PW_HEIGHT (PW_WIDTH * 2)
|
||||
#define PW_BUFFER_SIZE (PW_WIDTH * PW_HEIGHT)
|
||||
|
||||
void pattern_window_init(NesPatternWindow *window) {
|
||||
PatternWindow *pattern_window_create(int *window_id) {
|
||||
PatternWindow *window = malloc(sizeof(PatternWindow));
|
||||
|
||||
window->window = window_create("Pattern Table", PW_WIDTH, PW_HEIGHT, PW_SCALE);
|
||||
window->texture = window_create_texture(&window->window, SDL_TEXTUREACCESS_STATIC);
|
||||
window->palette = 0;
|
||||
|
||||
dbg_pattern_table_init();
|
||||
|
||||
*window_id = window->window.id;
|
||||
return window;
|
||||
}
|
||||
|
||||
void pattern_window_uninit(NesPatternWindow *window) {
|
||||
void pattern_window_destroy(PatternWindow *window) {
|
||||
SDL_DestroyTexture(window->texture);
|
||||
window_destroy(&window->window);
|
||||
|
||||
free(window);
|
||||
}
|
||||
|
||||
void pattern_window_build_table(NesPatternWindow *window) {
|
||||
void pattern_window_update(PatternWindow *window) {
|
||||
dbg_palette_update();
|
||||
|
||||
pixel buffer[PW_BUFFER_SIZE] = {0};
|
||||
dbg_pattern_draw_bank(PATTERN_BANK_0, buffer, window->palette);
|
||||
dbg_pattern_draw_bank(PATTERN_BANK_1, &buffer[PW_WIDTH * (PW_HEIGHT / 2)], window->palette);
|
||||
|
@ -28,7 +40,7 @@ void pattern_window_build_table(NesPatternWindow *window) {
|
|||
SDL_UpdateTexture(window->texture, NULL, buffer, PW_WIDTH * sizeof(pixel));
|
||||
}
|
||||
|
||||
void pattern_window_key_up(NesPatternWindow *window, SDL_KeyCode keycode) {
|
||||
void pattern_window_key_up(PatternWindow *window, SDL_KeyCode keycode) {
|
||||
if (keycode != SDLK_o) {
|
||||
return;
|
||||
}
|
||||
|
@ -38,13 +50,10 @@ void pattern_window_key_up(NesPatternWindow *window, SDL_KeyCode keycode) {
|
|||
window->palette = 0;
|
||||
}
|
||||
|
||||
pattern_window_build_table(window);
|
||||
pattern_window_update(window);
|
||||
}
|
||||
|
||||
void pattern_window_render(NesPatternWindow *window) {
|
||||
void pattern_window_render(PatternWindow *window) {
|
||||
window_render_texture(&window->window, window->texture);
|
||||
}
|
||||
|
||||
void pattern_window_present(NesPatternWindow *window) {
|
||||
window_present(&window->window);
|
||||
}
|
|
@ -16,15 +16,37 @@ typedef struct nes_pattern_window {
|
|||
Window window;
|
||||
SDL_Texture *texture;
|
||||
byte palette;
|
||||
} NesPatternWindow;
|
||||
} PatternWindow;
|
||||
|
||||
void pattern_window_init(NesPatternWindow *window);
|
||||
void pattern_window_uninit(NesPatternWindow *window);
|
||||
/**
|
||||
* Creates a pattern window instance.
|
||||
* @return A reference to the created window instance
|
||||
*/
|
||||
PatternWindow *pattern_window_create(int *window_id);
|
||||
|
||||
void pattern_window_build_table(NesPatternWindow *window);
|
||||
/**
|
||||
* Destroys a pattern window instance.
|
||||
* @param window A reference to the window to destroy
|
||||
*/
|
||||
void pattern_window_destroy(PatternWindow *window);
|
||||
|
||||
void pattern_window_key_up(NesPatternWindow *window, SDL_KeyCode keycode);
|
||||
void pattern_window_render(NesPatternWindow *window);
|
||||
void pattern_window_present(NesPatternWindow *window);
|
||||
/**
|
||||
* Updates the content of a pattern window with the current data from the ROM mapper.
|
||||
* @param window A reference to the window to update
|
||||
*/
|
||||
void pattern_window_update(PatternWindow *window);
|
||||
|
||||
/**
|
||||
* Renders a pattern window to the screen.
|
||||
* @param window A reference to the window to render
|
||||
*/
|
||||
void pattern_window_render(PatternWindow *window);
|
||||
|
||||
/**
|
||||
* Sends a key up event to a pattern window.
|
||||
* @param window A reference to the window to send the event to
|
||||
* @param keycode The code of the key of the event
|
||||
*/
|
||||
void pattern_window_key_up(PatternWindow *window, SDL_KeyCode keycode);
|
||||
|
||||
#endif //NES_EMULATOR_PATTERN_WINDOW_H
|
||||
|
|
|
@ -69,7 +69,6 @@ typedef struct ppu_tile_fetch {
|
|||
byte pattern_table_tile_high;
|
||||
} PPUTileFetch;
|
||||
|
||||
#if DEBUG
|
||||
typedef union {
|
||||
struct {
|
||||
byte tile_debugger: 1;
|
||||
|
@ -77,7 +76,6 @@ typedef union {
|
|||
} flags;
|
||||
byte flags_byte;
|
||||
} PPUDebugFlags;
|
||||
#endif
|
||||
|
||||
typedef struct ppu {
|
||||
PPUMemory memory;
|
||||
|
@ -103,9 +101,7 @@ typedef struct ppu {
|
|||
unsigned int scanline;
|
||||
unsigned int cycle;
|
||||
|
||||
#if DEBUG
|
||||
PPUDebugFlags debug;
|
||||
#endif
|
||||
} PPU;
|
||||
|
||||
PPU *ppu_get_state();
|
||||
|
|
14
main.c
14
main.c
|
@ -101,30 +101,28 @@ int main() {
|
|||
|
||||
if (!rom_load(rom_path)) {
|
||||
system_uninit();
|
||||
gui_uninit();
|
||||
gui_free();
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
gui_post_sysinit();
|
||||
system_start();
|
||||
|
||||
bool stop = false;
|
||||
while (!stop) {
|
||||
while (true) {
|
||||
if (gui_input() < 0) {
|
||||
stop = true;
|
||||
// The main window has been closed, stop the emulation
|
||||
break;
|
||||
}
|
||||
|
||||
system_next_frame();
|
||||
|
||||
gui_render();
|
||||
|
||||
gui_present();
|
||||
// Delay the next frame to lock the emulation to 60hz
|
||||
gui_delay();
|
||||
}
|
||||
|
||||
system_uninit();
|
||||
rom_unload();
|
||||
gui_uninit();
|
||||
gui_free();
|
||||
|
||||
close_logging();
|
||||
|
||||
|
|
|
@ -20,8 +20,8 @@
|
|||
#include "../cpu/cpu.h"
|
||||
#include "../include/rom.h"
|
||||
#include "colors.h"
|
||||
#include "tile_debugger.h"
|
||||
#include "log.h"
|
||||
#include "tile_debugger.h"
|
||||
|
||||
#define PPU_SCANLINE_VISIBLE_MAX 240
|
||||
#define PPU_SCANLINE_POST_RENDER_MIN PPU_SCANLINE_VISIBLE_MAX
|
||||
|
@ -132,7 +132,6 @@ void ppu_draw_tile() {
|
|||
}
|
||||
|
||||
byte ppu_get_pattern(byte tile_index, byte high) {
|
||||
#if DEBUG
|
||||
if (ppu_state.debug.flags.tile_debugger) {
|
||||
if ((ppu_state.debug.flags.tile_debugger_pattern_half == 1 && high) ||
|
||||
(ppu_state.debug.flags.tile_debugger_pattern_half == 2 && !high)) {
|
||||
|
@ -141,7 +140,6 @@ byte ppu_get_pattern(byte tile_index, byte high) {
|
|||
|
||||
return tile_debugger_encode_number_as_pattern(tile_index, ppu_state.scanline % 8);
|
||||
}
|
||||
#endif
|
||||
|
||||
byte tile_row_index = (ppu_state.scanline + ppu_state.y_scroll) % 8;
|
||||
address pattern_addr = ppu_state.bg_pattern_table_addr | tile_index << 4 | high << 3 | tile_row_index;
|
||||
|
|
Loading…
Reference in New Issue