// // Created by william on 8/23/24. // #include #include #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; }