nesemu/gui/components/window_menu.c

251 lines
8.1 KiB
C

//
// 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;
}