254 lines
8.3 KiB
C
254 lines
8.3 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, 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;
|
|
|
|
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, int click_action_type) {
|
|
MenuItemComponent *menu_item = malloc(sizeof(MenuItemComponent));
|
|
|
|
menu_item->label = label;
|
|
menu_item->click_action_type = click_action_type;
|
|
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;
|
|
}
|
|
|
|
int action_type = menu->highlight_item->click_action_type;
|
|
menu->action_processor(action_type);
|
|
|
|
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;
|
|
} |