SDL menu in top of main window

This commit is contained in:
william 2024-09-01 15:54:41 -04:00
parent 32c9cebd19
commit f18ad715fb
27 changed files with 883 additions and 396 deletions

View File

@ -0,0 +1,8 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="ClangTidy" enabled="true" level="WARNING" enabled_by_default="true">
<option name="clangTidyChecks" value="-*,bugprone-argument-comment,bugprone-assert-side-effect,bugprone-bad-signal-to-kill-thread,bugprone-branch-clone,bugprone-copy-constructor-init,bugprone-dangling-handle,bugprone-dynamic-static-initializers,bugprone-fold-init-type,bugprone-forward-declaration-namespace,bugprone-forwarding-reference-overload,bugprone-inaccurate-erase,bugprone-incorrect-roundings,bugprone-integer-division,bugprone-lambda-function-name,bugprone-macro-parentheses,bugprone-macro-repeated-side-effects,bugprone-misplaced-operator-in-strlen-in-alloc,bugprone-misplaced-pointer-arithmetic-in-alloc,bugprone-misplaced-widening-cast,bugprone-move-forwarding-reference,bugprone-multiple-statement-macro,bugprone-no-escape,bugprone-parent-virtual-call,bugprone-posix-return,bugprone-reserved-identifier,bugprone-sizeof-container,bugprone-sizeof-expression,bugprone-spuriously-wake-up-functions,bugprone-string-constructor,bugprone-string-integer-assignment,bugprone-string-literal-with-embedded-nul,bugprone-suspicious-enum-usage,bugprone-suspicious-include,bugprone-suspicious-memset-usage,bugprone-suspicious-missing-comma,bugprone-suspicious-semicolon,bugprone-suspicious-string-compare,bugprone-suspicious-memory-comparison,bugprone-suspicious-realloc-usage,bugprone-swapped-arguments,bugprone-terminating-continue,bugprone-throw-keyword-missing,bugprone-too-small-loop-variable,bugprone-undefined-memory-manipulation,bugprone-undelegated-constructor,bugprone-unhandled-self-assignment,bugprone-unused-raii,bugprone-unused-return-value,bugprone-use-after-move,bugprone-virtual-near-miss,cert-dcl21-cpp,cert-dcl58-cpp,cert-err34-c,cert-err52-cpp,cert-err60-cpp,cert-flp30-c,cert-msc50-cpp,cert-msc51-cpp,cert-str34-c,cppcoreguidelines-interfaces-global-init,cppcoreguidelines-narrowing-conversions,cppcoreguidelines-pro-type-member-init,cppcoreguidelines-pro-type-static-cast-downcast,cppcoreguidelines-slicing,google-default-arguments,google-explicit-constructor,google-runtime-operator,hicpp-exception-baseclass,hicpp-multiway-paths-covered,misc-misplaced-const,misc-new-delete-overloads,misc-non-copyable-objects,misc-throw-by-value-catch-by-reference,misc-unconventional-assign-operator,misc-uniqueptr-reset-release,modernize-avoid-bind,modernize-concat-nested-namespaces,modernize-deprecated-headers,modernize-deprecated-ios-base-aliases,modernize-loop-convert,modernize-make-shared,modernize-make-unique,modernize-pass-by-value,modernize-raw-string-literal,modernize-redundant-void-arg,modernize-replace-auto-ptr,modernize-replace-disallow-copy-and-assign-macro,modernize-replace-random-shuffle,modernize-return-braced-init-list,modernize-shrink-to-fit,modernize-unary-static-assert,modernize-use-auto,modernize-use-bool-literals,modernize-use-emplace,modernize-use-equals-default,modernize-use-equals-delete,modernize-use-nodiscard,modernize-use-noexcept,modernize-use-nullptr,modernize-use-override,modernize-use-transparent-functors,modernize-use-uncaught-exceptions,mpi-buffer-deref,mpi-type-mismatch,openmp-use-default-none,performance-faster-string-find,performance-for-range-copy,performance-implicit-conversion-in-loop,performance-inefficient-algorithm,performance-inefficient-string-concatenation,performance-inefficient-vector-operation,performance-move-const-arg,performance-move-constructor-init,performance-no-automatic-move,performance-noexcept-move-constructor,performance-trivially-destructible,performance-type-promotion-in-math-fn,performance-unnecessary-copy-initialization,performance-unnecessary-value-param,portability-simd-intrinsics,readability-avoid-const-params-in-decls,readability-const-return-type,readability-container-size-empty,readability-convert-member-functions-to-static,readability-delete-null-pointer,readability-deleted-default,readability-inconsistent-declaration-parameter-name,readability-make-member-function-const,readability-misleading-indentation,readability-misplaced-array-index,readability-non-const-parameter,readability-redundant-control-flow,readability-redundant-declaration,readability-redundant-function-ptr-dereference,readability-redundant-smartptr-get,readability-redundant-string-cstr,readability-redundant-string-init,readability-simplify-subscript-expr,readability-static-accessed-through-instance,readability-static-definition-in-anonymous-namespace,readability-string-compare,readability-uniqueptr-delete-release,readability-use-anyofallof" />
</inspection_tool>
</profile>
</component>

View File

@ -15,7 +15,6 @@ add_subdirectory(cpu)
add_subdirectory(ppu)
add_subdirectory(mappers)
add_subdirectory(rom)
add_subdirectory(utils)
add_subdirectory(gui)
list(APPEND EXTRA_INCLUDES
@ -23,8 +22,6 @@ list(APPEND EXTRA_INCLUDES
"${PROJECT_SOURCE_DIR}/ppu"
"${PROJECT_SOURCE_DIR}/mappers"
"${PROJECT_SOURCE_DIR}/rom"
"${PROJECT_SOURCE_DIR}/debugger"
"${PROJECT_SOURCE_DIR}/utils"
"${PROJECT_SOURCE_DIR}/gui")
set(HEADERS include/system.h include/types.h)
@ -32,7 +29,7 @@ set(SOURCE main.c system.c)
add_executable(nes_emulator ${HEADERS} ${SOURCE})
target_link_libraries(nes_emulator nes_cpu nes_ppu nes_mappers nes_rom nes_utils nes_gui log.c)
target_link_libraries(nes_emulator nes_cpu nes_ppu nes_mappers nes_rom nes_gui log.c)
target_include_directories(nes_emulator PUBLIC
"${PROJECT_BINARY_DIR}"
${EXTRA_INCLUDES})

View File

@ -29,7 +29,7 @@ only tested on Linux. Here is how to run the project:
- PPU
- Registers: Done
- VRAM: Done
- Background rendering: In Progress
- Background rendering: Done
- Sprite rendering: To Do
- Input: To Do
- APU: To Do

View File

@ -10,7 +10,7 @@
#include "../cpu/decoding.h"
#include "cursor.h"
#include "window.h"
#include "../utils/linked_list.h"
#include "components/linked_list.h"
#define PROGRAM_VIEW_HEIGHT 19
#define PROGRAM_VIEW_WIDTH 42

View File

@ -1,5 +1,5 @@
set(HEADERS gui.h window.h main_window.h)
set(SOURCE gui.c window.c main_window.c)
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)
@ -8,7 +8,6 @@ endif (NES_DEBUG)
add_library(nes_gui ${SOURCE} ${HEADERS})
find_package(SDL2 REQUIRED)
include_directories(nes_gui ${SDL2_INCLUDE_DIRS})
add_subdirectory(components)
target_link_libraries(nes_gui log.c ${SDL2_LIBRARIES} SDL2_ttf)
target_link_libraries(nes_gui nes_gui_components log.c SDL2_ttf)

View File

@ -0,0 +1,9 @@
set(HEADERS component.h linked_list.h window.h window_menu.h)
set(SOURCE linked_list.c window.c window_menu.c)
add_library(nes_gui_components ${SOURCE} ${HEADERS})
find_package(SDL2 REQUIRED)
include_directories(nes_gui_components ${SDL2_INCLUDE_DIRS})
target_link_libraries(nes_gui_components ${SDL2_LIBRARIES})

View File

@ -0,0 +1,20 @@
//
// Created by william on 8/24/24.
//
#ifndef NES_EMULATOR_COMPONENT_H
#define NES_EMULATOR_COMPONENT_H
typedef struct component {
void *ref;
void (*render)(void *ref);
void (*destroy)(void *ref);
bool (*mouse_motion)(void *ref, int x, int y);
bool (*mouse_click)(void *ref);
} Component;
#endif //NES_EMULATOR_COMPONENT_H

View File

@ -0,0 +1,87 @@
//
// Created by william on 1/16/24.
//
#include <stddef.h>
#include <malloc.h>
#include <stdlib.h>
#include <assert.h>
#include "linked_list.h"
LinkedList linked_list_create(bool circular) {
LinkedList list;
list.circular = circular;
list.size = 0;
list.head = NULL;
list.end = NULL;
return list;
}
void linked_list_add(LinkedList *list, void *data) {
assert(list != NULL);
assert(data != NULL);
LinkedListNode *node = malloc(sizeof(LinkedListNode));
if (node == NULL) {
perror("Failed to allocate memory for linked list node");
exit(EXIT_FAILURE);
}
node->data = data;
node->previous = list->end;
if (list->head == NULL) {
list->head = node;
}
if (list->end != NULL) {
list->end->next = node;
}
if (list->circular) {
node->next = list->head;
} else {
node->next = NULL;
}
list->end = node;
list->size++;
}
void linked_list_destroy(LinkedList *list) {
assert(list != NULL);
LinkedListNode *node = list->head;
while (node != NULL) {
LinkedListNode *current_node = node;
node = node->next;
free(current_node);
if (node == list->head) {
// The list may be circular, we don't want an infinite free loop
break;
}
}
}
LinkedListCursor linked_list_cursor_create(LinkedList *list) {
LinkedListCursor cursor = {list->head};
return cursor;
}
LinkedListNode *linked_list_cursor_next(LinkedListCursor *cursor) {
if (cursor->current == NULL) {
return NULL;
}
LinkedListNode *next_node = cursor->current->next;
cursor->current = next_node;
return next_node;
}
bool linked_list_cursor_has_next(LinkedListCursor *cursor) {
return cursor->current != NULL && cursor->current->next != NULL;
}

View File

@ -0,0 +1,71 @@
//
// Created by william on 1/16/24.
//
#include <stdbool.h>
#ifndef NESEMULATOR_LINKED_LIST_H
#define NESEMULATOR_LINKED_LIST_H
typedef struct linked_list_node {
struct linked_list_node *previous;
struct linked_list_node *next;
void *data;
} LinkedListNode;
typedef struct linked_list {
bool circular;
unsigned int size;
LinkedListNode *head;
LinkedListNode *end;
} LinkedList;
typedef struct linked_list_cursor {
LinkedListNode *current;
} LinkedListCursor;
/**
* Initializes a new linked list.
*
* @param circular If the list is circular, meaning that the last node is linked to the first node.
* @return The linked list instance
*/
LinkedList linked_list_create(bool circular);
/**
* Adds data to a linked list.
*
* @param list The linked list
* @param data The data to add
*/
void linked_list_add(LinkedList *list, void *data);
/**
* Destroys a linked list. The data must be freed before.
*
* @param list The list to destroy
*/
void linked_list_destroy(LinkedList *list);
/**
* Creates a read cursor for a linked list.
* @param list The list
* @return A cursor initialized to the first item in the list.
*/
LinkedListCursor linked_list_cursor_create(LinkedList *list);
/**
* Gets the next node in the list.
*
* @param cursor A read cursor for the list
* @return The next node in the list. Can be NULL if the list is empty or depleted.
*/
LinkedListNode *linked_list_cursor_next(LinkedListCursor *cursor);
/**
* Checks if there is nodes remaining after a cursor.
* @param cursor A cursor
*/
bool linked_list_cursor_has_next(LinkedListCursor *cursor);
#endif //NESEMULATOR_LINKED_LIST_H

134
gui/components/window.c Normal file
View File

@ -0,0 +1,134 @@
//
// Created by william on 17/05/24.
//
#include <SDL.h>
#include "window.h"
#include "log.h"
#include "component.h"
/**
* 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.
*/
#define FOR_EACH_COMPONENT(window) \
Component *component; \
LinkedListCursor cursor = window_create_component_cursor(window, &component); \
while (component != NULL) {
#define END_FOR_EACH_COMPONENT \
component = get_next_component(&cursor); \
}
static inline LinkedListCursor window_create_component_cursor(Window *window, Component **first_component) {
LinkedListCursor cursor = linked_list_cursor_create(&window->components);
if (cursor.current != NULL) {
*first_component = cursor.current->data;
} else {
*first_component = NULL;
}
return cursor;
}
static inline Component *get_next_component(LinkedListCursor *cursor) {
if (!linked_list_cursor_has_next(cursor)) {
return NULL;
}
LinkedListNode *next_node = linked_list_cursor_next(cursor);
return next_node->data;
}
Window window_create(char *title, int width, int height, int scale) {
WindowSdlContext context;
int renderer_flags = SDL_RENDERER_ACCELERATED;
int window_flags = 0;
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
log_error("Couldn't initialize SDL: %s", SDL_GetError());
exit(-1);
}
int actual_width = width * scale;
int actual_height = height * scale;
context.window = SDL_CreateWindow(title, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, actual_width,
actual_height, window_flags);
if (!context.window) {
log_error("Failed to open %d x %d SDL window: %s", actual_width, actual_height, SDL_GetError());
exit(-1);
}
context.renderer = SDL_CreateRenderer(context.window, -1, renderer_flags);
if (!context.renderer) {
log_error("Failed to create renderer: %s", SDL_GetError());
SDL_DestroyWindow(context.window);
exit(-1);
}
Window window;
window.width = width;
window.height = height;
window.scale = scale;
window.sdl_context = context;
window.components = linked_list_create(false);
return window;
}
SDL_Texture *window_create_texture(Window *window, int access) {
SDL_Renderer *renderer = window->sdl_context.renderer;
return SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, access, window->width, window->height);
}
void window_add_component(Window *window, Component *component) {
LinkedList *components = &window->components;
linked_list_add(components, component);
}
void window_render_components(Window *window) {
FOR_EACH_COMPONENT(window)
component->render(component->ref);
END_FOR_EACH_COMPONENT
}
void window_render_texture(Window *window, SDL_Texture *texture) {
SDL_Renderer *renderer = window->sdl_context.renderer;
SDL_RenderClear(renderer);
SDL_RenderCopy(renderer, texture, NULL, NULL);
}
void window_present(Window *window) {
SDL_RenderPresent(window->sdl_context.renderer);
}
void window_destroy(Window *window) {
FOR_EACH_COMPONENT(window)
component->destroy(component->ref);
free(component);
END_FOR_EACH_COMPONENT
SDL_DestroyRenderer(window->sdl_context.renderer);
SDL_DestroyWindow(window->sdl_context.window);
}
void window_mouse_motion(Window *window, int x, int y) {
FOR_EACH_COMPONENT(window)
bool matching = component->mouse_motion(component->ref, x, y);
if (matching) {
// If a component has matched, we don't pass the event to the next ones
// This will prevent, for example, clicking on a UI element behind another one.
break;
}
END_FOR_EACH_COMPONENT
}
void window_mouse_click(Window *window) {
FOR_EACH_COMPONENT(window)
bool matching = component->mouse_click(component->ref);
if (matching) {
break;
}
END_FOR_EACH_COMPONENT
}

91
gui/components/window.h Normal file
View File

@ -0,0 +1,91 @@
//
// Created by william on 17/05/24.
//
#ifndef NES_EMULATOR_WINDOW_H
#define NES_EMULATOR_WINDOW_H
#include <SDL.h>
#include "linked_list.h"
#include "component.h"
typedef struct window_sdl_context {
SDL_Renderer *renderer;
SDL_Window *window;
} WindowSdlContext;
typedef struct window {
int width;
int height;
int scale;
WindowSdlContext sdl_context;
LinkedList components;
} Window;
/**
* Creates a window.
* @param title The title of the window
* @param width The width in pixels
* @param height The height in pixels
* @param scale The scale of pixels of this window (number of real pixels per drawn pixel)
* @return The created window
*/
Window window_create(char *title, int width, int height, int scale);
/**
* Creates a background_texture for a window. (same width/height)
* @param window A reference to the window
* @param access The SDL background_texture access type
* @return An SDL background_texture for the window
*/
SDL_Texture *window_create_texture(Window *window, int access);
/**
* Adds a component to a window.
* @param window A reference to the window
* @param component A reference to the component to add
*/
void window_add_component(Window *window, Component *component);
/**
* Renders the components of a window (but not the window itself)
* @param window The window to render components
*/
void window_render_components(Window *window);
/**
* Renders a background_texture in the window.
* @param window A reference to the window
* @param texture The background_texture to render
*/
void window_render_texture(Window *window, SDL_Texture *texture);
/**
* Presents a window.
* @param window A reference to the window
*/
void window_present(Window *window);
/**
* Destroys a window.
* Free the ressources of the window and its components.
* @param window The window to destroy
*/
void window_destroy(Window *window);
/**
* Handles mouse motion events in the window.
* @param window A reference to the window
* @param x The x position of the mouse
* @param y The y position of the mouse
*/
void window_mouse_motion(Window *window, int x, int y);
/**
* Handles mouse click events in the window.
* @param window A reference to the window
*/
void window_mouse_click(Window *window);
#endif //NES_EMULATOR_WINDOW_H

View File

@ -0,0 +1,251 @@
//
// Created by william on 8/23/24.
//
#include <SDL_ttf.h>
#include <assert.h>
#include "window_menu.h"
#include "../../include/types.h"
#include "log.h"
/**
* Easy declaration of commonly used item loop. The current item is in the 'item' variable.
* Put the loop's body between FOR_EACH_ITEM and END_FOR_EACH_ITEM.
*/
#define FOR_EACH_ITEM_(list)\
MenuItemComponent *item; \
LinkedListCursor cursor = menu_create_item_cursor(&(list), &item); \
while (item != NULL) {
#define FOR_EACH_ITEM(menu) FOR_EACH_ITEM_((menu)->items)
#define FOR_EACH_SUBITEM(menu_item) FOR_EACH_ITEM_((menu_item)->sub_items)
#define END_FOR_EACH_ITEM \
item = menu_get_next_item(&cursor); \
}
#define CREATE_PIXEL_TEXTURE(texture, menu, color) \
pixel texture ## _buffer[1] = {color}; \
(menu)->texture = SDL_CreateTexture((menu)->renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STATIC, 1, 1); \
SDL_UpdateTexture((menu)->texture, NULL, &texture ## _buffer, sizeof(pixel))
static inline LinkedListCursor menu_create_item_cursor(LinkedList *list, MenuItemComponent **first_component) {
LinkedListCursor cursor = linked_list_cursor_create(list);
if (cursor.current != NULL) {
*first_component = cursor.current->data;
} else {
*first_component = NULL;
}
return cursor;
}
static inline MenuItemComponent *menu_get_next_item(LinkedListCursor *cursor) {
if (!linked_list_cursor_has_next(cursor)) {
return NULL;
}
LinkedListNode *next_node = linked_list_cursor_next(cursor);
return next_node->data;
}
MenuComponent *menu_create(Window *window, TTF_Font *font) {
MenuComponent *menu = malloc(sizeof(MenuComponent));
menu->window_width = window->width * window->scale;
menu->visible = false;
menu->items = linked_list_create(false);
menu->highlight_item = NULL;
menu->renderer = window->sdl_context.renderer;
menu->font = font;
CREATE_PIXEL_TEXTURE(background_texture, menu, MENU_BACKGROUND_COLOR);
CREATE_PIXEL_TEXTURE(highlight_texture, menu, MENU_HIGHLIGHT_COLOR);
return menu;
}
MenuItemComponent *menu_item_create(char *label, on_click_callback on_click) {
MenuItemComponent *menu_item = malloc(sizeof(MenuItemComponent));
menu_item->label = label;
menu_item->on_click = on_click;
menu_item->sub_items = linked_list_create(false);
menu_item->is_highlighted = false;
return menu_item;
}
void menu_append(MenuComponent *menu, MenuItemComponent *menu_item) {
LinkedList *menu_items = &menu->items;
linked_list_add(menu_items, menu_item);
}
void menu_item_append(MenuItemComponent *menu_item, MenuItemComponent *sub_item) {
LinkedList *sub_items = &menu_item->sub_items;
linked_list_add(sub_items, sub_item);
}
void menu_item_build(MenuItemComponent *menu_item, SDL_Renderer *renderer, TTF_Font *font, bool top_level) {
SDL_Color label_color = MENU_TEXT_COLOR;
int base_x = menu_item->collision_rect.x;
int base_y = MENU_HEIGHT + MENU_ITEM_MARGIN_Y;
if (!top_level) {
base_x += menu_item->collision_rect.w;
base_y = menu_item->collision_rect.y + MENU_ITEM_MARGIN_Y;
}
FOR_EACH_SUBITEM(menu_item)
SDL_Surface *label_surface = TTF_RenderText_Solid(font, item->label, label_color);
item->label_texture = SDL_CreateTextureFromSurface(renderer, label_surface);
SDL_Rect draw_rect = {base_x + MENU_ITEM_MARGIN_X, base_y, label_surface->w, label_surface->h};
item->draw_rect = draw_rect;
SDL_Rect menu_item_col = {base_x, base_y - MENU_ITEM_MARGIN_Y, label_surface->w + MENU_ITEM_MARGIN_X * 2,
MENU_HEIGHT};
item->collision_rect = menu_item_col;
base_y += item->draw_rect.h + MENU_ITEM_MARGIN_Y * 2;
SDL_FreeSurface(label_surface);
menu_item_build(item, renderer, font, false);
END_FOR_EACH_ITEM
}
void menu_build(MenuComponent *menu) {
assert(menu->font != NULL);
SDL_Color label_color = MENU_TEXT_COLOR;
int next_x = MENU_ITEM_MARGIN_X;
FOR_EACH_ITEM(menu)
SDL_Surface *label_surface = TTF_RenderText_Solid(menu->font, item->label, label_color);
item->label_texture = SDL_CreateTextureFromSurface(menu->renderer, label_surface);
SDL_Rect draw_rect = {next_x, MENU_ITEM_MARGIN_Y, label_surface->w, label_surface->h};
item->draw_rect = draw_rect;
SDL_Rect menu_item_col = {next_x - MENU_ITEM_MARGIN_X, 0, label_surface->w + MENU_ITEM_MARGIN_X * 2,
MENU_HEIGHT};
item->collision_rect = menu_item_col;
next_x += item->draw_rect.w + MENU_ITEM_MARGIN_X * 2;
SDL_FreeSurface(label_surface);
menu_item_build(item, menu->renderer, menu->font, true);
END_FOR_EACH_ITEM
}
void menu_item_render(MenuComponent *menu, MenuItemComponent *menu_item) {
SDL_Texture *back_texture = menu_item->is_highlighted ? menu->highlight_texture : menu->background_texture;
SDL_RenderCopy(menu->renderer, back_texture, NULL, &menu_item->collision_rect);
SDL_RenderCopy(menu->renderer, menu_item->label_texture, NULL, &menu_item->draw_rect);
if (!menu_item->is_highlighted) {
return;
}
FOR_EACH_SUBITEM(menu_item)
menu_item_render(menu, item);
END_FOR_EACH_ITEM
}
void menu_render(MenuComponent *menu) {
if (!menu->visible) {
return;
}
int display_width = menu->window_width;
int display_height = MENU_HEIGHT;
SDL_Rect menu_rect = {0, 0, display_width, display_height};
SDL_RenderCopy(menu->renderer, menu->background_texture, NULL, &menu_rect);
FOR_EACH_ITEM(menu)
menu_item_render(menu, item);
END_FOR_EACH_ITEM
}
void menu_item_list_destroy(LinkedList *list) {
MenuItemComponent *menu_item;
LinkedListCursor cursor = menu_create_item_cursor(list, &menu_item);
while (menu_item != NULL) {
menu_item_destroy(menu_item);
menu_item = menu_get_next_item(&cursor);
}
linked_list_destroy(list);
}
void menu_destroy(MenuComponent *menu) {
menu_item_list_destroy(&menu->items);
SDL_DestroyTexture(menu->background_texture);
free(menu);
}
void menu_item_destroy(MenuItemComponent *menu_item) {
SDL_DestroyTexture(menu_item->label_texture);
menu_item_list_destroy(&menu_item->sub_items);
free(menu_item);
}
/**
* Checks if a point is over a menu item.
* This function will return true if the point is in an item or one of its sub-items.
*/
void menu_item_hover(MenuComponent *menu, MenuItemComponent *menu_item, SDL_Point *pos) {
if (SDL_PointInRect(pos, &menu_item->collision_rect)) {
menu_item->is_highlighted = true;
menu->highlight_item = menu_item;
} else if (menu_item->is_highlighted) { // We can't hover a sub-item if this item is not highlighted because it is not visible
bool any_sub_highlighted = false;
FOR_EACH_SUBITEM(menu_item)
menu_item_hover(menu, item, pos);
if (item->is_highlighted) {
any_sub_highlighted = true;
}
END_FOR_EACH_ITEM
// If any sub-item is highlighted, the current item also is
menu_item->is_highlighted = any_sub_highlighted;
}
}
bool menu_mouse_motion(MenuComponent *menu, int x, int y) {
menu->visible = y <= MENU_VISIBLE_HEIGHT;
SDL_Point pos = {x, y};
menu->highlight_item = NULL;
FOR_EACH_ITEM(menu)
menu_item_hover(menu, item, &pos);
END_FOR_EACH_ITEM
return menu->highlight_item != NULL;
}
bool menu_mouse_click(MenuComponent *menu) {
if (menu->highlight_item == NULL) {
return false;
}
log_info("%s", menu->highlight_item->label);
return true;
}
Component *menu_as_component(MenuComponent *menu) {
Component *component = malloc(sizeof(Component));
component->ref = menu;
component->render = (void (*)(void *)) &menu_render;
component->destroy = (void (*)(void *)) &menu_destroy;
component->mouse_motion = (bool (*)(void *, int, int)) &menu_mouse_motion;
component->mouse_click = (bool (*)(void *)) &menu_mouse_click;
return component;
}

View File

@ -0,0 +1,110 @@
//
// Created by william on 8/23/24.
//
#ifndef NES_EMULATOR_WINDOW_MENU_H
#define NES_EMULATOR_WINDOW_MENU_H
#include "window.h"
#include "linked_list.h"
#include "component.h"
#define MENU_HEIGHT 24
#define MENU_VISIBLE_HEIGHT 64;
#define MENU_BACKGROUND_COLOR 0xff353535
#define MENU_HIGHLIGHT_COLOR 0xff4d4d4d
#define MENU_TEXT_COLOR {0xff, 0xff, 0xff}
#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;
bool is_highlighted;
LinkedList sub_items;
SDL_Texture *label_texture;
SDL_Rect draw_rect;
SDL_Rect collision_rect;
} MenuItemComponent;
typedef struct menu_component {
int window_width;
bool visible;
LinkedList items;
MenuItemComponent *highlight_item;
SDL_Renderer *renderer;
TTF_Font *font;
SDL_Texture *background_texture;
SDL_Texture *highlight_texture;
} MenuComponent;
/**
* 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
* @return A reference to the menu component
*/
MenuComponent *menu_create(Window *window, TTF_Font *font);
/**
* Creates a menu item. Can be configured with a callback function which will be called when the menu item is clicked.
* Note that the callback function will be overridden if the menu item has sub items.
* @param label The label of the menu item
* @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);
/**
* Adds an item to a menu.
* @param menu A reference to the menu
* @param menu_item A reference to the menu item to add to the menu
*/
void menu_append(MenuComponent *menu, MenuItemComponent *menu_item);
/**
* Adds an sub-item to a menu item.
* Note that this will have the effect of preventing the callback to be called.
* @param menu_item A reference to the menu item
* @param sub_item A reference to the sub item to add to the menu item
*/
void menu_item_append(MenuItemComponent *menu_item, MenuItemComponent *sub_item);
/**
* Builds the ressources needed for a menu to be displayed. (ex: textures)
* @param menu The menu to build
*/
void menu_build(MenuComponent *menu);
/**
* Renders a menu to its window.
* @param menu A reference to the menu to render
*/
void menu_render(MenuComponent *menu);
/**
* Destroys a menu, freeing its memory.
* @param menu A reference to the menu to destroy
*/
void menu_destroy(MenuComponent *menu);
/**
* Destroys a menu item to free its memory.
* @param menu_item A reference to the menu item to destroy
*/
void menu_item_destroy(MenuItemComponent *menu_item);
/**
* Returns a menu as a component.
* @param menu The menu
* @return
*/
Component *menu_as_component(MenuComponent *menu);
#endif //NES_EMULATOR_WINDOW_MENU_H

View File

@ -14,6 +14,12 @@
#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;
@ -43,7 +49,7 @@ bool gui_init() {
pattern_window_init(&gui.pattern_window);
nametable_window_init(&gui.nametable_window);
char_map_init(gui.main_window.sdl_context.renderer, gui.font);
char_map_init(gui.main_window.window.sdl_context.renderer, gui.font);
#endif
main_window_init(&gui.main_window);
@ -70,7 +76,7 @@ void gui_post_sysinit() {
dbg_pattern_table_init();
dbg_nametable_init();
// TODO: The texture is rendered before the palette data is in the PPU memory, so the only color is grey
// 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
@ -103,21 +109,34 @@ int gui_input() {
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:
pattern_window_key_up(&gui.pattern_window, event.key.keysym.sym);
break;
#else
default:
break;
#endif
}
}
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 (event.type == SDL_MOUSEBUTTONUP && event.window.windowID == WINDOW_ID_MAIN) {
main_window_mouse_click(&gui.main_window);
}
}
return 1;
@ -142,8 +161,6 @@ void gui_present() {
pattern_window_present(&gui.pattern_window);
nametable_window_present(&gui.nametable_window);
#endif
main_window_present(&gui.main_window);
}
void gui_delay() {
@ -159,6 +176,10 @@ void gui_delay() {
gui.last_frame_tick = SDL_GetTicks();
}
TTF_Font *gui_get_font() {
return gui.font;
}
unsigned int gui_get_frame_delay() {
return gui.frame_delay;
}

View File

@ -6,6 +6,7 @@
#define NES_EMULATOR_GUI_H
#include <stdbool.h>
#include <SDL_ttf.h>
bool gui_init();
void gui_uninit();
@ -17,6 +18,7 @@ void gui_render();
void gui_present();
void gui_delay();
TTF_Font* gui_get_font();
unsigned int gui_get_frame_delay();
#endif //NES_EMULATOR_GUI_H

View File

@ -7,19 +7,37 @@
#include "log.h"
#include "char_map.h"
#include "gui.h"
#include "components/window_menu.h"
void main_window_init(NesMainWindow *window) {
window->sdl_context = window_init("NES Emulator", MAIN_WINDOW_WIDTH, MAIN_WINDOW_HEIGHT, MAIN_WINDOW_SCALE);
window->texture = SDL_CreateTexture(window->sdl_context.renderer, SDL_PIXELFORMAT_ARGB8888,
SDL_TEXTUREACCESS_STREAMING, MAIN_WINDOW_WIDTH, MAIN_WINDOW_HEIGHT);
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());
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_debug = menu_item_create("DEBUG", NULL);
menu_append(menu, mi_debug);
MenuItemComponent *mi_debug_nametable = menu_item_create("NAMETABLE", NULL);
menu_item_append(mi_debug, mi_debug_nametable);
MenuItemComponent *mi_debug_pattern = menu_item_create("PATTERN TABLE", NULL);
menu_item_append(mi_debug, mi_debug_pattern);
menu_build(menu);
window_add_component(&window->window, menu_as_component(menu));
}
void main_window_uninit(NesMainWindow *window) {
SDL_DestroyTexture(window->texture);
window_uninit(window->sdl_context);
window_destroy(&window->window);
}
#if DEBUG
void main_window_render_delay(SDL_Renderer *renderer) {
Uint32 delay = gui_get_frame_delay();
@ -32,18 +50,22 @@ void main_window_render_delay(SDL_Renderer *renderer) {
char_map_render(renderer, buffer);
}
#endif
void main_window_render(NesMainWindow *window, pixel *pixels) {
SDL_RenderClear(window->sdl_context.renderer);
SDL_UpdateTexture(window->texture, NULL, pixels, 256 * sizeof(pixel));
SDL_RenderCopy(window->sdl_context.renderer, window->texture, NULL, NULL);
#if DEBUG
main_window_render_delay(window->sdl_context.renderer);
#endif
window_render_texture(&window->window, window->texture);
window_render_components(&window->window);
window_present(&window->window);
}
void main_window_present(NesMainWindow *window) {
SDL_RenderPresent(window->sdl_context.renderer);
void main_window_mouse_motion(NesMainWindow *window, int x, int y) {
window_mouse_motion(&window->window, x, y);
}
void main_window_mouse_click(NesMainWindow *window) {
window_mouse_click(&window->window);
}

View File

@ -7,14 +7,14 @@
#include <SDL.h>
#include "../include/ppu.h"
#include "window.h"
#include "components/window.h"
#define MAIN_WINDOW_WIDTH 256
#define MAIN_WINDOW_HEIGHT 240
#define MAIN_WINDOW_SCALE 3
typedef struct nes_main_window {
NesSdlContext sdl_context;
Window window;
SDL_Texture *texture;
} NesMainWindow;
@ -23,10 +23,7 @@ void main_window_init(NesMainWindow *window);
void main_window_uninit(NesMainWindow *window);
void main_window_render(NesMainWindow *window, pixel* pixels);
void main_window_present(NesMainWindow *window);
#if DEBUG
void main_window_render_delay(SDL_Renderer *renderer);
#endif
void main_window_mouse_motion(NesMainWindow *window, int x, int y);
void main_window_mouse_click(NesMainWindow *window);
#endif //NES_EMULATOR_MAIN_WINDOW_H

View File

@ -10,15 +10,13 @@
#define NW_BUFFER_SIZE (NAMETABLE_ROW_WIDTH * NAMETABLE_COL_HEIGHT * PATTERN_DRAW_SIZE * PATTERN_DRAW_SIZE)
void nametable_window_init(NesNametableWindow *window) {
window->sdl_context = window_init("Nametable", NW_WIDTH, NW_HEIGHT, NW_SCALE);
window->texture = SDL_CreateTexture(window->sdl_context.renderer, SDL_PIXELFORMAT_ARGB8888,
SDL_TEXTUREACCESS_STREAMING, NW_WIDTH, NW_HEIGHT);
window->window = window_create("Nametable", NW_WIDTH, NW_HEIGHT, NW_SCALE);
window->texture = window_create_texture(&window->window, SDL_TEXTUREACCESS_STREAMING);
}
void nametable_window_uninit(NesNametableWindow *window) {
SDL_DestroyTexture(window->texture);
window_uninit(window->sdl_context);
window_destroy(&window->window);
}
void nametable_window_update_bank(NesNametableWindow *window, int bank, pixel *buffer) {
@ -44,10 +42,9 @@ void nametable_window_update(NesNametableWindow *window) {
}
void nametable_window_render(NesNametableWindow *window) {
SDL_RenderClear(window->sdl_context.renderer);
SDL_RenderCopy(window->sdl_context.renderer, window->texture, NULL, NULL);
window_render_texture(&window->window, window->texture);
}
void nametable_window_present(NesNametableWindow *window) {
SDL_RenderPresent(window->sdl_context.renderer);
window_present(&window->window);
}

View File

@ -5,7 +5,7 @@
#ifndef NES_EMULATOR_NAMETABLE_WINDOW_H
#define NES_EMULATOR_NAMETABLE_WINDOW_H
#include "window.h"
#include "components/window.h"
#include "../include/types.h"
#define NW_SCALE 1
@ -13,16 +13,18 @@
#define NW_ROW_TILE_COUNT 64
typedef struct nes_nametable_window {
NesSdlContext sdl_context;
Window window;
SDL_Texture *texture;
} NesNametableWindow;
void nametable_window_init(NesNametableWindow *window);
void nametable_window_uninit(NesNametableWindow *window);
void nametable_window_update(NesNametableWindow *window);
void nametable_window_render(NesNametableWindow *window);
void nametable_window_present(NesNametableWindow *window);
#endif //NES_EMULATOR_NAMETABLE_WINDOW_H

View File

@ -10,16 +10,14 @@
#define PW_BUFFER_SIZE (PW_WIDTH * PW_HEIGHT)
void pattern_window_init(NesPatternWindow *window) {
window->sdl_context = window_init("Pattern Table", PW_WIDTH, PW_HEIGHT, PW_SCALE);
window->texture = SDL_CreateTexture(window->sdl_context.renderer, SDL_PIXELFORMAT_ARGB8888,
SDL_TEXTUREACCESS_STATIC, PW_WIDTH, PW_HEIGHT);
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;
}
void pattern_window_uninit(NesPatternWindow *window) {
SDL_DestroyTexture(window->texture);
window_uninit(window->sdl_context);
window_destroy(&window->window);
}
void pattern_window_build_table(NesPatternWindow *window) {
@ -44,10 +42,9 @@ void pattern_window_key_up(NesPatternWindow *window, SDL_KeyCode keycode) {
}
void pattern_window_render(NesPatternWindow *window) {
SDL_RenderClear(window->sdl_context.renderer);
SDL_RenderCopy(window->sdl_context.renderer, window->texture, NULL, NULL);
window_render_texture(&window->window, window->texture);
}
void pattern_window_present(NesPatternWindow *window) {
SDL_RenderPresent(window->sdl_context.renderer);
window_present(&window->window);
}

View File

@ -6,14 +6,14 @@
#define NES_EMULATOR_PATTERN_WINDOW_H
#include "../include/types.h"
#include "window.h"
#include "components/window.h"
#define PW_SCALE 2
#define PW_ROW_TILE_COUNT 16
#define PW_PALETTE_MAX 3
typedef struct nes_pattern_window {
NesSdlContext sdl_context;
Window window;
SDL_Texture *texture;
byte palette;
} NesPatternWindow;

View File

@ -1,101 +0,0 @@
//
// Created by william on 17/05/24.
//
#include <SDL.h>
#include "window.h"
#include "log.h"
NesSdlContext window_init(char *title, int width, int height, int scale) {
NesSdlContext context;
int renderer_flags = SDL_RENDERER_ACCELERATED;
int window_flags = 0;
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
log_error("Couldn't initialize SDL: %s", SDL_GetError());
exit(-1);
}
int actual_width = width * scale;
int actual_height = height * scale;
context.window = SDL_CreateWindow(title, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, actual_width,
actual_height, window_flags);
if (!context.window) {
log_error("Failed to open %d x %d SDL window: %s", actual_width, actual_height, SDL_GetError());
exit(-1);
}
context.renderer = SDL_CreateRenderer(context.window, -1, renderer_flags);
if (!context.renderer) {
log_error("Failed to create renderer: %s", SDL_GetError());
SDL_DestroyWindow(context.window);
exit(-1);
}
return context;
}
void window_uninit(NesSdlContext context) {
SDL_DestroyRenderer(context.renderer);
SDL_DestroyWindow(context.window);
}
//NesWindow window_init(int width, int height, char *title) {
// NesWindow win;
// win.width = width;
// win.height = height;
//
// int renderer_flags = SDL_RENDERER_ACCELERATED;
// int window_flags = 0;
//
// if (SDL_Init(SDL_INIT_VIDEO) < 0) {
// log_error("Couldn't initialize SDL: %s", SDL_GetError());
// exit(-1);
// }
//
// win.window = SDL_CreateWindow(title, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, win.width, win.height,
// window_flags);
// if (!win.window) {
// log_error("Failed to open %d x %d sdl_window: %s", win.width, win.height, SDL_GetError());
// exit(-1);
// }
//
// win.renderer = SDL_CreateRenderer(win.window, -1, renderer_flags);
// if (!win.renderer) {
// log_error("Failed to create renderer: %s\n", SDL_GetError());
// exit(-1);
// }
//
// return win;
//}
//
//void window_uninit(NesWindow *window) {
// SDL_DestroyRenderer(window->renderer);
// SDL_DestroyWindow(window->window);
//}
//
//void window_render(NesWindow *window) {
// SDL_RenderClear(window->renderer);
//
//// for (int y = 0; y < window->canvas.height; y++) {
//// for (int x = 0; x < window->canvas.width; x++) {
//// int pixel_index = x + y * window->canvas.width;
//// Pixel pixel = window->canvas.pixels[pixel_index];
////
//// SDL_SetRenderDrawColor(window->renderer, pixel.r, pixel.g, pixel.b, 255);
////
//// for (int i = 0; i < window->scaling; i++) {
//// for (int j = 0; j < window->scaling; j++) {
//// int scaled_x = x * window->scaling + i;
//// int scaled_y = y * window->scaling + j;
//// SDL_RenderDrawPoint(window->renderer, scaled_x, scaled_y);
//// }
//// }
//// }
//// }
//}
//
//void window_present(NesWindow *window) {
// SDL_RenderPresent(window->renderer);
//}

View File

@ -1,18 +0,0 @@
//
// Created by william on 17/05/24.
//
#ifndef NES_EMULATOR_WINDOW_H
#define NES_EMULATOR_WINDOW_H
#include <SDL.h>
typedef struct nes_sdl_context {
SDL_Renderer *renderer;
SDL_Window *window;
} NesSdlContext;
NesSdlContext window_init(char *title, int width, int height, int scale);
void window_uninit(NesSdlContext context);
#endif //NES_EMULATOR_WINDOW_H

5
main.c
View File

@ -50,7 +50,7 @@ FILE *add_log_file(char *name, int level) {
}
void init_logging() {
log_set_level(LOG_DEBUG);
log_set_level(LOG_INFO);
// Print current working directory
char cwd[256];
@ -81,7 +81,10 @@ void init_logging() {
void close_logging() {
fclose(log_files.info);
#if DEBUG
fclose(log_files.debug);
#endif
}
int main() {

View File

@ -1,4 +0,0 @@
set(HEADERS linked_list.h)
set(SOURCE linked_list.c)
add_library(nes_utils ${HEADERS} ${SOURCE})

View File

@ -1,126 +0,0 @@
//
// Created by william on 1/16/24.
//
#include <stddef.h>
#include <malloc.h>
#include <stdlib.h>
#include <assert.h>
#include "linked_list.h"
LinkedList linked_list_init(bool circular) {
LinkedList list;
list.circular = circular;
list.size = 0;
list.head = NULL;
list.end = NULL;
list.current = NULL;
return list;
}
void linked_list_add(LinkedList *list, void *data) {
assert(list != NULL);
LinkedListNode *node = malloc(sizeof(LinkedListNode));
if (node == NULL) {
perror("Failed to allocate memory for linked list node");
exit(EXIT_FAILURE);
}
node->data = data;
node->previous = list->end;
if (list->head == NULL) {
list->head = node;
list->current = node;
}
if (list->end != NULL) {
list->end->next = node;
}
if (list->circular) {
node->next = list->head;
} else {
node->next = NULL;
}
list->end = node;
list->size++;
}
LinkedListNode *linked_list_next(LinkedList *list) {
assert(list != NULL);
if (list->head == NULL) {
return NULL;
}
LinkedListNode *next = list->current->next;
list->current = next;
return next;
}
void linked_list_cursor_reset(LinkedList *list) {
assert(list != NULL);
list->current = list->head;
}
LinkedListNode *linked_list_get_if(LinkedList *list, bool(*predicate)(void *, void *), void *userdata) {
assert(list != NULL);
assert(predicate != NULL);
LinkedListNode *node = list->head;
while (node != NULL) {
if (predicate(node->data, userdata)) {
return node;
}
node = node->next;
}
return NULL;
}
LinkedListNode *linked_list_get_near(LinkedList *list, int(*compute_distance)(void *, void *), void *userdata) {
assert(list != NULL);
assert(compute_distance != NULL);
LinkedListNode *near_node = list->head;
int current_distance = 0x7fffffff;
while (near_node->next != NULL && current_distance != 0) {
int next_distance = compute_distance(near_node->next->data, userdata);
if (next_distance > current_distance) {
break;
}
near_node = near_node->next;
current_distance = next_distance;
}
// After the loop, we have found the nearest node in the list, assuming there is only one point of convergence
return near_node;
}
void linked_list_uninit(LinkedList *list) {
assert(list != NULL);
LinkedListNode *node = list->head;
while (node != NULL) {
LinkedListNode *current_node = node;
node = node->next;
free(current_node->data);
free(current_node);
if (node == list->head) {
// The list may be circular, we don't want an infinite free loop
break;
}
}
}

View File

@ -1,82 +0,0 @@
//
// Created by william on 1/16/24.
//
#include <stdbool.h>
#ifndef NESEMULATOR_LINKED_LIST_H
#define NESEMULATOR_LINKED_LIST_H
typedef struct linked_list_node {
struct linked_list_node *previous;
struct linked_list_node *next;
void *data;
} LinkedListNode;
typedef struct linked_list {
bool circular;
unsigned int size;
LinkedListNode *head;
LinkedListNode *end;
LinkedListNode *current;
} LinkedList;
/**
* Initializes a new linked list.
*
* @param circular If the list is circular, meaning that the last node is linked to the first node.
* @return The linked list instance
*/
LinkedList linked_list_init(bool circular);
/**
* Adds data to a linked list.
*
* @param list The linked list
* @param data The data to add
*/
void linked_list_add(LinkedList *list, void *data);
/**
* Gets the next node in the list.
*
* @param list The linked list
* @return The next node in the list. Can be NULL if the list is empty or depleted.
*/
LinkedListNode *linked_list_next(LinkedList *list);
/**
* Resets the position of the cursor to the head of the list.
*/
void linked_list_cursor_reset(LinkedList *list);
/**
* Searches for data corresponding to a predicate.
* The search will stop after reaching the first node matching the given predicate.
*
* @param list The list to search in
* @param predicate The predicate to match the data
* @param userdata Parameter to pass to the predicate
* @return The first node in the list matching the predicate
*/
LinkedListNode *linked_list_get_if(LinkedList *list, bool(*predicate)(void *, void *), void *userdata);
/**
* Searches for data with the smallest distance computed from a function.
* The search will stop when a node increasing the distance is found. For this reason, the distance computing function should have a single minimum.
*
* @param list The list to search in
* @param compute_distance The function to compute the distance of a node's data
* @param userdata Parameter to pass to the function
* @return The node with the smallest distance
*/
LinkedListNode *linked_list_get_near(LinkedList *list, int(*compute_distance)(void *, void *), void *userdata);
/**
* Deinitializes a linked list.
*
* @param list The list to deinitialize
*/
void linked_list_uninit(LinkedList *list);
#endif //NESEMULATOR_LINKED_LIST_H