diff --git a/Makefile b/Makefile index c56e69d1..d57b52ff 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,7 @@ SRCS = \ menu/menu.c \ utils/fs.c \ libs/toml/toml.c \ + libs/menu_utils/menu.c \ main.c OBJS = $(addprefix $(BUILD_DIR)/, $(addsuffix .o,$(basename $(SRCS)))) diff --git a/src/libs/menu_utils/README.md b/src/libs/menu_utils/README.md new file mode 100644 index 00000000..856c589a --- /dev/null +++ b/src/libs/menu_utils/README.md @@ -0,0 +1,11 @@ +# Source +https://github.com/stefanmielke/libdragon-extensions + +# License +MIT + +# Description +Used for generating the menu GUI + +# Notes +It might be preferable to change to a submodule. diff --git a/src/libs/menu_utils/mem_pool.c b/src/libs/menu_utils/mem_pool.c new file mode 100644 index 00000000..378e0676 --- /dev/null +++ b/src/libs/menu_utils/mem_pool.c @@ -0,0 +1,42 @@ +#include "../include/mem_pool.h" + +#include + +#include + +void mem_zone_init(MemZone *z, size_t size) { + disable_interrupts(); + + void *ptr = malloc(size); + if (ptr == NULL) { + abort(); // Put your error handling here. + } + z->pos = (char *)ptr; + z->start = z->pos; + z->end = z->start + size; + + memset(z->start, 0, size); + + enable_interrupts(); +} + +void *mem_zone_alloc(MemZone *z, size_t size) { + if (size == 0) { + return NULL; + } + // Round up to multiple of 16 bytes. + size = (size + 15) & ~(size_t)15; + // How much free space remaining in zone? + size_t rem = z->end - z->pos; + if (rem < size) { + return NULL; // Out of memory. Put your error handling here. + } + + void *ptr = (void *)z->pos; + z->pos += size; + return ptr; +} + +void mem_zone_free_all(MemZone *z) { + z->pos = z->start; +} diff --git a/src/libs/menu_utils/mem_pool.h b/src/libs/menu_utils/mem_pool.h new file mode 100644 index 00000000..8b86a8b1 --- /dev/null +++ b/src/libs/menu_utils/mem_pool.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief A contiguous zone where memory can be allocated. + */ +typedef struct { + /// Pointer to current free space position. + char *pos; + /// Pointer to start of zone. + char *start; + /// Pointer to end of zone. + char *end; +} MemZone; + +/** + * @brief Allocate a memory zone with the given size. + * + * @param memory_zone MemZone to use. + * @param size Size in bytes that the MemZone will have. (eg.: '1*1024*1024' for 1MB) + */ +void mem_zone_init(MemZone *memory_zone, size_t size); + +/** + * @brief Allocate memory from the zone. + * + * @param memory_zone MemZone to use. + * @param size Size in bytes of the memory. (eg.: sizeof(int)) + * + * @return the memory allocated. + */ +void *mem_zone_alloc(MemZone *memory_zone, size_t size); + +/** + * @brief Free all objects in the zone. + * + * @param memory_zone MemZone to use. + */ +void mem_zone_free_all(MemZone *memory_zone); + +#ifdef __cplusplus +} +#endif diff --git a/src/libs/menu_utils/memory_alloc.h b/src/libs/menu_utils/memory_alloc.h new file mode 100644 index 00000000..ba7624f1 --- /dev/null +++ b/src/libs/menu_utils/memory_alloc.h @@ -0,0 +1,6 @@ +#include "mem_pool.h" + +/** + * @brief Used to allocate an object using either a MemZone or malloc according to the parameters. + */ +#define MEM_ALLOC(SIZE, MEMPOOL) ((MEMPOOL) ? mem_zone_alloc((MEMPOOL), (SIZE)) : malloc(SIZE)) diff --git a/src/libs/menu_utils/menu.c b/src/libs/menu_utils/menu.c new file mode 100644 index 00000000..5daf341a --- /dev/null +++ b/src/libs/menu_utils/menu.c @@ -0,0 +1,415 @@ +#include "../include/menu.h" + +#include "../include/memory_alloc.h" + +void menu_scroll_up(Menu *menu); +void menu_scroll_down(Menu *menu); + +uint32_t MENU_C_SELECTED; +uint32_t MENU_C_ENABLED; +uint32_t MENU_C_DISABLED; +uint32_t MENU_C_BACKGROUND; +uint32_t MENU_C_MENU_BACKGROUND; +uint32_t MENU_C_OUT_OF_BOUNDS; + +sprite_t *menu_background_sprite; +sprite_t *menu_hand_sprite; +uint8_t menu_hand_sprite_offset; + +void menu_global_init() { + MENU_C_SELECTED = graphics_make_color(0, 0, 255, 255); + MENU_C_ENABLED = graphics_make_color(255, 255, 255, 255); + MENU_C_DISABLED = graphics_make_color(100, 100, 255, 255); + MENU_C_BACKGROUND = graphics_make_color(0, 0, 0, 0); + MENU_C_OUT_OF_BOUNDS = graphics_make_color(0, 0, 0, 255); + MENU_C_MENU_BACKGROUND = graphics_make_color(255, 255, 255, 255); +} + +void menu_global_set_default_colors(uint32_t selected, uint32_t enabled, uint32_t disabled, + uint32_t background, uint32_t out_of_bounds, + uint32_t menu_background) { + MENU_C_SELECTED = selected; + MENU_C_ENABLED = enabled; + MENU_C_DISABLED = disabled; + MENU_C_BACKGROUND = background; + MENU_C_OUT_OF_BOUNDS = out_of_bounds; + MENU_C_MENU_BACKGROUND = menu_background; +} + +void menu_global_set_sprites(sprite_t *menu_sprite, sprite_t *hand_sprite, + uint8_t hand_sprite_offset) { + menu_background_sprite = menu_sprite; + menu_hand_sprite = hand_sprite; + menu_hand_sprite_offset = hand_sprite_offset; +} + +Menu *menu_init(MemZone *memory_pool, uint8_t total_items, uint8_t max_items, int top, int left, + uint8_t item_height, fnMenuCallback callback) { + Menu *menu = MEM_ALLOC(sizeof(Menu), memory_pool); + menu->total_items = total_items; + menu->items = MEM_ALLOC(sizeof(MenuItem) * total_items, memory_pool); + menu->current_menu_option = 0; + menu_reset_items(menu); + + menu->active_submenu = -1; + menu->submenus = NULL; + + menu->display_when_on_submenu = false; + + menu->display_background = false; + menu->menu_width = 0; + + menu->item_vertical_limit = max_items; + menu->item_height = item_height; + menu->top = top; + menu->left = left; + menu->cur_top_item = 0; + menu->cur_bottom_item = 0; + + menu->callback = callback; + + menu->display_hand = false; + menu->hand_position_x = 0; + menu->hand_position_y_offset = 0; + + return menu; +} + +void menu_set_background(Menu *menu, int menu_width) { + menu->display_background = true; + menu->menu_width = menu_width; +} + +void menu_reset_items(Menu *menu) { + menu->current_add_index = 0; + for (uint8_t i = 0; i < menu->total_items; ++i) { + menu->items[i].enabled = false; + menu->items[i].text = NULL; + menu->items[i].x = 0; + menu->items[i].y = 0; + } +} + +void menu_set_hand(Menu *menu, int hand_position_x, int hand_position_y_offset) { + menu->display_hand = true; + menu->hand_position_x = hand_position_x; + menu->hand_position_y_offset = hand_position_y_offset; +} + +void menu_add_item(Menu *menu, char *text, bool enabled, void *object) { + if (menu->current_add_index >= menu->total_items) + return; + + MenuItem *item = &menu->items[menu->current_add_index]; + item->text = text; + item->x = menu->left; + item->y = menu->top + (menu->item_height * menu->current_add_index); + item->enabled = enabled; + item->has_custom_colors = false; + item->sprite_index = -1; + item->object = object; + + if (menu->current_add_index == menu->item_vertical_limit) + item->state = MSIS_OnBorder; + else if (menu->current_add_index > menu->item_vertical_limit) + item->state = MSIS_Outside; + else + item->state = MSIS_Inside; + + menu->current_add_index++; + + menu->cur_bottom_item = menu->current_add_index >= menu->item_vertical_limit + ? menu->item_vertical_limit - 1 + : menu->current_add_index - 1; +} + +void menu_add_item_image(Menu *menu, sprite_t *sprite, int sprite_index, bool enabled, + void *object) { + if (menu->current_add_index >= menu->total_items) + return; + + MenuItem *item = &menu->items[menu->current_add_index]; + item->sprite = sprite; + item->sprite_index = sprite_index; + item->x = menu->left; + item->y = menu->top + (menu->item_height * menu->current_add_index); + item->enabled = enabled; + item->has_custom_colors = false; + item->object = object; + + if (menu->current_add_index == menu->item_vertical_limit) + item->state = MSIS_OnBorder; + else if (menu->current_add_index > menu->item_vertical_limit) + item->state = MSIS_Outside; + else + item->state = MSIS_Inside; + + menu->current_add_index++; + + menu->cur_bottom_item = menu->current_add_index >= menu->item_vertical_limit + ? menu->item_vertical_limit - 1 + : menu->current_add_index - 1; +} + +void menu_add_item_colored(Menu *menu, char *text, bool enabled, uint32_t color_selected, + uint32_t color_enabled, uint32_t color_disabled, void *object) { + if (menu->current_add_index >= menu->total_items) + return; + + MenuItem *item = &menu->items[menu->current_add_index]; + item->text = text; + item->x = menu->left; + item->y = menu->top + (menu->item_height * menu->current_add_index); + item->enabled = enabled; + item->has_custom_colors = true; + item->sprite_index = -1; + item->object = object; + + item->color_selected = color_selected; + item->color_enabled = color_enabled; + item->color_disabled = color_disabled; + + if (menu->current_add_index == menu->item_vertical_limit) + item->state = MSIS_OnBorder; + else if (menu->current_add_index > menu->item_vertical_limit) + item->state = MSIS_Outside; + else + item->state = MSIS_Inside; + + menu->current_add_index++; + + menu->cur_bottom_item = menu->current_add_index >= menu->item_vertical_limit + ? menu->item_vertical_limit - 1 + : menu->current_add_index - 1; +} + +int menu_tick(Menu *menu, struct controller_data *keys_down) { + if (menu->active_submenu >= 0 && menu->submenus) { + Menu **menus = menu->submenus; + Menu *active_submenu = menus[menu->active_submenu]; + int option = menu_tick(active_submenu, keys_down); + + // go back from submenu + if (option == -2) { + menu->active_submenu = -1; + return -1; + } + + return option; + } + if (menu->current_add_index <= 0) + return -1; + + if (keys_down->c[0].up) { + if (menu->current_menu_option > 0) { + menu->current_menu_option -= 1; + menu_scroll_up(menu); + } + } else if (keys_down->c[0].down) { + if (menu->current_menu_option < menu->current_add_index - 1) { + menu->current_menu_option += 1; + menu_scroll_down(menu); + } + } else if (keys_down->c[0].A || keys_down->c[0].start) { + if (menu->items[menu->current_menu_option].enabled) { + if (menu->callback) { + menu->callback(menu->current_menu_option, &menu->items[menu->current_menu_option]); + } + return menu->current_menu_option; + } + } else if (keys_down->c[0].B) { + return -2; + } + + return -1; +} + +void menu_render(Menu *menu, display_context_t disp) { + if (menu->active_submenu >= 0 && menu->submenus) { + Menu *active_submenu = menu->submenus[menu->active_submenu]; + + menu_render(active_submenu, disp); + + if (!menu->display_when_on_submenu) + return; + } + + // render window background + if (menu->display_background) { + const int item_count = menu->current_add_index < menu->item_vertical_limit + ? menu->current_add_index + : menu->item_vertical_limit; + + const int menu_left = menu->left - 8; + const int menu_top = menu->top - menu->item_height; + const int menu_bottom = menu->top + (menu->item_height * item_count); + const int menu_right = menu->left + menu->menu_width; + + menu_draw_background_center(disp, menu_top, menu_left, menu_bottom, menu_right); + } + + for (uint8_t i = 0; i < menu->current_add_index; ++i) { + if (menu->items[i].state == MSIS_Outside) + continue; + + if (menu->items[i].sprite_index == -1) { + if (menu->items[i].state == MSIS_Inside) { + if (i == menu->current_menu_option) { + if (menu->items[i].has_custom_colors) { + graphics_set_color(menu->items[i].color_selected, MENU_C_BACKGROUND); + } else { + graphics_set_color(MENU_C_SELECTED, MENU_C_BACKGROUND); + } + } else if (menu->items[i].enabled) { + if (menu->items[i].has_custom_colors) { + graphics_set_color(menu->items[i].color_enabled, MENU_C_BACKGROUND); + } else { + graphics_set_color(MENU_C_ENABLED, MENU_C_BACKGROUND); + } + } else { + if (menu->items[i].has_custom_colors) { + graphics_set_color(menu->items[i].color_disabled, MENU_C_BACKGROUND); + } else { + graphics_set_color(MENU_C_DISABLED, MENU_C_BACKGROUND); + } + } + } else { + graphics_set_color(MENU_C_OUT_OF_BOUNDS, MENU_C_BACKGROUND); + } + + graphics_draw_text(disp, menu->items[i].x, menu->items[i].y, menu->items[i].text); + } else { + graphics_draw_sprite_trans_stride(disp, menu->items[i].x, menu->items[i].y, + menu->items[i].sprite, menu->items[i].sprite_index); + } + } + + if (menu->display_background) { + const int item_count = menu->current_add_index < menu->item_vertical_limit + ? menu->current_add_index + : menu->item_vertical_limit; + + const int menu_left = menu->left - 8; + const int menu_top = menu->top - menu->item_height; + const int menu_bottom = menu->top + (menu->item_height * item_count); + const int menu_right = menu->left + menu->menu_width; + + menu_draw_background_borders(disp, menu_top, menu_left, menu_bottom, menu_right); + } + + // render hand icon + if (menu->display_hand && menu->current_add_index > 0) { + int option = menu->current_menu_option; + if (menu->current_add_index > 0) + graphics_draw_sprite_trans_stride(disp, menu->hand_position_x, + menu->items[option].y + menu->hand_position_y_offset, + menu_hand_sprite, menu_hand_sprite_offset); + } +} + +void menu_init_submenus(Menu *menu, MemZone *memory_pool, uint8_t total_submenus, + bool display_when_on_submenu) { + // do not recreate submenus + if (menu->submenus) + return; + + menu->submenus = MEM_ALLOC(sizeof(Menu *) * total_submenus, memory_pool); + menu->display_when_on_submenu = display_when_on_submenu; +} + +void menu_draw_background_borders(display_context_t disp, int top, int left, int bottom, + int right) { + graphics_draw_sprite_trans_stride(disp, left, top, menu_background_sprite, + SPRITE_menu_top_left); + + graphics_draw_sprite_trans_stride(disp, left, bottom, menu_background_sprite, + SPRITE_menu_bottom_left); + + graphics_draw_sprite_trans_stride(disp, right, top, menu_background_sprite, + SPRITE_menu_top_right); + + graphics_draw_sprite_trans_stride(disp, right, bottom, menu_background_sprite, + SPRITE_menu_bottom_right); + + const int repeat_x = (right - left - 8) / 8; + for (size_t i = 0; i < repeat_x; ++i) { + graphics_draw_sprite_trans_stride(disp, left + 8 + (i * 8), top, menu_background_sprite, + SPRITE_menu_top); + graphics_draw_sprite_trans_stride(disp, left + 8 + (i * 8), bottom, menu_background_sprite, + SPRITE_menu_bottom); + } + const int repeat_y = ((bottom - top) / 8) - 1; + for (size_t i = 0; i < repeat_y; ++i) { + graphics_draw_sprite_trans_stride(disp, left, top + 8 + (i * 8), menu_background_sprite, + SPRITE_menu_left); + graphics_draw_sprite_trans_stride(disp, right, top + 8 + (i * 8), menu_background_sprite, + SPRITE_menu_right); + } +} + +void menu_draw_background_center(display_context_t disp, int top, int left, int bottom, int right) { + // TODO: remove this "hack" and properly render the texture. Cause: libdragon issue with 32 bits + left += 8; + top += 8; + + rdp_sync(SYNC_PIPE); + + rdp_attach_display(disp); + rdp_enable_blend_fill(); + rdp_set_default_clipping(); + + rdp_sync(SYNC_PIPE); + rdp_set_blend_color(MENU_C_MENU_BACKGROUND); + rdp_draw_filled_triangle(left, top, right, top, right, bottom); + rdp_draw_filled_triangle(left, top, left, bottom, right, bottom); + + rdp_detach_display(); +} + +void menu_scroll_fix_y(Menu *menu) { + if (menu->cur_top_item > 0) { + menu->items[menu->cur_top_item - 1].y = menu->top - menu->item_height; + } + for (size_t ix = menu->cur_top_item, i = 0; i <= menu->cur_bottom_item; ++i, ++ix) { + menu->items[ix].y = menu->top + (i * menu->item_height); + } + if (menu->cur_bottom_item + 1 < menu->current_add_index) { + menu->items[menu->cur_bottom_item + 1].y = menu->top + + (menu->item_vertical_limit * menu->item_height); + } +} + +void menu_scroll_up(Menu *menu) { + if (menu->current_menu_option < menu->cur_top_item) { + if (menu->cur_bottom_item + 1 < menu->current_add_index) + menu->items[menu->cur_bottom_item + 1].state = MSIS_Outside; + menu->items[menu->cur_bottom_item].state = MSIS_OnBorder; + + if (menu->cur_top_item - 2 >= 0) + menu->items[menu->cur_top_item - 2].state = MSIS_OnBorder; + menu->items[menu->current_menu_option].state = MSIS_Inside; + + menu->cur_top_item--; + menu->cur_bottom_item--; + + menu_scroll_fix_y(menu); + } +} + +void menu_scroll_down(Menu *menu) { + if (menu->current_menu_option > menu->cur_bottom_item) { + if (menu->cur_top_item > 0) + menu->items[menu->cur_top_item - 1].state = MSIS_Outside; + menu->items[menu->cur_top_item].state = MSIS_OnBorder; + + menu->items[menu->current_menu_option].state = MSIS_Inside; + + if (menu->current_menu_option + 1 < menu->current_add_index) + menu->items[menu->current_menu_option + 1].state = MSIS_OnBorder; + + menu->cur_top_item++; + menu->cur_bottom_item++; + + menu_scroll_fix_y(menu); + } +} diff --git a/src/libs/menu_utils/menu.h b/src/libs/menu_utils/menu.h new file mode 100644 index 00000000..96611137 --- /dev/null +++ b/src/libs/menu_utils/menu.h @@ -0,0 +1,331 @@ +#pragma once + +#include + +#include "mem_pool.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Enum with the order of the background sprites in a texture. Any texture has to have this + * order. + */ +typedef enum MenuBackgroundSprite { + SPRITE_menu_bottom, + SPRITE_menu_bottom_left, + SPRITE_menu_bottom_right, + SPRITE_menu_center, + SPRITE_menu_left, + SPRITE_menu_right, + SPRITE_menu_top, + SPRITE_menu_top_left, + SPRITE_menu_top_right, + SPRITE_menu_SPRITES_MAX +} MenuBackgroundSprite; + +/** + * @brief Enum that contains the state of a given item in relation with the current selected item. + */ +typedef enum MenuItemState { + /// The item is inside the menu. + MSIS_Inside, + /// The item is on the edge of the menu. + MSIS_OnBorder, + /// The item is outside of the range of displayed items and will not be rendered. + MSIS_Outside, +} MenuItemState; + +/** + * @brief Struct that represents an item inside the menu. Used internally when using + * 'menu_add_item*' functions. + */ +typedef struct MenuItem { + /// Text that will be rendered. Not used when is an image. + char *text; + /// Sprite that will be rendered. Used only when an image. + sprite_t *sprite; + /// Index of the sprite that will be rendered. Used only when an image. + int sprite_index; + + /// X position of this item. Calculated whenever the state of the menu changes. + int x; + /// Y position of this item. Calculated whenever the state of the menu changes. + int y; + /// If the item is enabled. An enabled item can be used. + bool enabled; + /// If the item has custom colors. If false, will use the default colors. Default colors can be + /// changed using 'menu_global_set_default_colors'. + bool has_custom_colors; + /// Color for when this item is enabled. Used when 'has_custom_colors' is true. + uint32_t color_enabled; + /// Color for when this item is disabled ('enabled' == false). Used when 'has_custom_colors' is + /// true. + uint32_t color_disabled; + /// Color for when this item is selected. Used when 'has_custom_colors' is true. + uint32_t color_selected; + + /// State of the item in relation to the menu. @see MenuItemState + MenuItemState state; + + /// Custom object that is set by the user when creating the item. + void *object; +} MenuItem; + +/** + * @brief Optional callback for when an item is used. + * + * @param[in] option + * The index of the used item. + * @param[in] menu_item + * A reference to the used menu item. @see MenuItem + */ +typedef void (*fnMenuCallback)(int option, MenuItem *menu_item); + +/** + * @brief Struct used to hold a Menu. Created by the 'menu_init' function. + */ +typedef struct Menu { + /// Array of items of this menu. + MenuItem *items; + /// Maximum amount of items this menu can have. Also the size of 'items'. + uint8_t total_items; + /// Current index when inserting new MenuItems. Has the current amount of used items. + uint8_t current_add_index; + /// Current selected menu item index. + int current_menu_option; + + /// Current active submenu. If '-1', no submenu is active. + int active_submenu; + /// Array with references to all submenus. + struct Menu **submenus; + + /// If this menu should be rendered even when a submenu is active. + bool display_when_on_submenu; + + /// If this menu should display a background. Configured using 'menu_global_set_sprites' and + /// 'menu_set_background'. + bool display_background; + /// Width of the background. Configured using 'menu_set_background'. + int menu_width; + + /// If the hand should be displayed for this menu + bool display_hand; + /// X position of the hand cursor. Configured using 'menu_global_set_sprites' and + /// 'menu_set_hand'. + int hand_position_x; + /// Y offset for the hand cursor. Configured using 'menu_global_set_sprites' and + /// 'menu_set_hand'. + int hand_position_y_offset; + + /// Amount of items that can be displayed at once. + uint8_t item_vertical_limit; + /// Height of each item inside the menu. Used to calculate Y position of items. + uint8_t item_height; + /// Y position of the menu. + int top; + /// X position of the menu. + int left; + /// Index of the item currently at the top of rendered menu. + uint8_t cur_top_item; + /// Index of the item currently at the bottom of rendered menu. + uint8_t cur_bottom_item; + + /// Callback function for when an item is used. @see fnMenuCallback + fnMenuCallback callback; +} Menu; + +/** + * @brief Allocates and returns a new Menu object. + * + * @param memory_pool + * The memory pool to be used. If NULL will use malloc. + * @param total_items + * Total items that can be created inside the Menu. + * @param max_items + * Count of items that will be displayed at once. + * @param top + * Y position of the menu. + * @param left + * X position of the menu. + * @param item_height + * Height of each item inside the menu. + * @param callback + * Callback function for when an item is used. @see fnMenuCallback + * + * @return A new Menu object. + */ +Menu *menu_init(MemZone *memory_pool, uint8_t total_items, uint8_t max_items, int top, int left, + uint8_t item_height, fnMenuCallback callback); +/** + * @brief Set the background for the Menu. Should only be called after calling + * 'menu_global_set_sprites' at least once in the game. + * + * @param menu + * Reference to the menu object. + * @param menu_width + * Width of the background. + */ +void menu_set_background(Menu *menu, int menu_width); +/** + * @brief Resets the items inside a menu. After calling this method you can add items to refill it. + * + * @param menu + * Reference to the menu object. + */ +void menu_reset_items(Menu *menu); +/** + * @brief Set the hand cursor for the Menu. Should only be called after calling + * 'menu_global_set_sprites' at least once in the game. + * + * @param menu + * Reference to the menu object. + * @param hand_position_x + * X position for the hand cursor. + * @param hand_position_y_offset + * Y position offset for the hand cursor. + */ +void menu_set_hand(Menu *menu, int hand_position_x, int hand_position_y_offset); + +/** + * @brief Ticks the menu. Should be called every frame. + * + * @param menu + * Reference to the menu object. + * @param keys_down + * Reference to the controller data for the current frame. You can get this data using the + * 'get_keys_down()' function. + * + * @return Index of the item used, or '-1' is none. + */ +int menu_tick(Menu *menu, struct controller_data *keys_down); + +/** + * @brief Add a new text item to the menu without any custom colors. + * + * @param menu + * Reference to the menu object. + * @param text + * Text that will be rendered. + * @param enabled + * If the item will start enabled. + * @param object + * Reference to a custom object that can be used later by the user. + */ +void menu_add_item(Menu *menu, char *text, bool enabled, void *object); +/** + * @brief Add a new image item to the menu. + * + * @param menu + * Reference to the menu object. + * @param sprite + * Sprite that will be used by this item. + * @param sprite_index + * Index inside the sprite. + * @param enabled + * If the item will start enabled. + * @param object + * Reference to a custom object that can be used later by the user. + */ +void menu_add_item_image(Menu *menu, sprite_t *sprite, int sprite_index, bool enabled, + void *object); +/** + * @brief Add a new text item to the menu with custom colors. + * + * @param menu + * Reference to the menu object. + * @param text + * Text that will be rendered. + * @param enabled + * If the item will start enabled. + * @param color_selected + * Color for this object when selected. + * @param color_enabled + * Color for this object when enabled and not selected. + * @param color_disabled + * Color for this object when disabled and not selected. + * @param object + * Reference to a custom object that can be used later by the user. + */ +void menu_add_item_colored(Menu *menu, char *text, bool enabled, uint32_t color_selected, + uint32_t color_enabled, uint32_t color_disabled, void *object); + +/** + * @brief Render the menu. + * + * @param menu + * Reference to the menu object. + * @param disp + * The display context used in the game. + */ +void menu_render(Menu *menu, display_context_t disp); + +/** + * @brief Allocates and initializes submenus for the menu. + * + * @param menu + * Reference to the menu object. + * @param memory_pool + * The memory pool to be used. If NULL will use malloc. + * @param total_submenus + * Total count of submenus that will be created. + * @param display_when_on_submenu + * If the menu will be displayed even when a submenu is active. + */ +void menu_init_submenus(Menu *menu, MemZone *memory_pool, uint8_t total_submenus, + bool display_when_on_submenu); + +/** + * @brief Method that will render the borders of the menu. + */ +void menu_draw_background_borders(display_context_t disp, int top, int left, int bottom, int right); +/** + * @brief Method that will render the center of the menu. + */ +void menu_draw_background_center(display_context_t disp, int top, int left, int bottom, int right); + +/** + * @brief Initializes the menu default colors + */ +void menu_global_init(); + +/** + * @brief Set the sprite used by the background menu + * + * @param menu_sprite + * Sprite that will be used. See `sample_assets/menu.png` for example. + * Order should be according to enum `MenuBackgroundSprite`. + * @param hand_sprite + * Sprite that will be used as the 'hand'. + * @param hand_sprite_offset + * Offset of the sprite (used by `graphics_draw_sprite_trans_stride`). + */ +void menu_global_set_sprites(sprite_t *menu_sprite, sprite_t *hand_sprite, + uint8_t hand_sprite_offset); + +/** + * @brief Set the default colors for all menus when the item doesn't have any set. + * + * @param selected + * Color used when the item is the current selected one. Default is {0,0,255,255} + * @param enabled + * Color used when the item is enabled but not selected. Default is {255,255,255,255} + * @param disabled + * Color used when the item is disabled. Default is {100,100,255,255} + * @param background + * Color used for the background of the text. Default is {0,0,0,0} + * @param out_of_bounds + * Color used when the item is out of the bounds of the menu (when scrolling). Default is + * {0,0,0,255} + * @param menu_background + * Color used for the background. Due to 32 bits issue, we do not use the background from the + * sprite. + */ +void menu_global_set_default_colors(uint32_t selected, uint32_t enabled, uint32_t disabled, + uint32_t background, uint32_t out_of_bounds, + uint32_t menu_background); + +#ifdef __cplusplus +} +#endif diff --git a/src/libs/menu_utils/menu.png b/src/libs/menu_utils/menu.png new file mode 100644 index 00000000..d34c0572 Binary files /dev/null and b/src/libs/menu_utils/menu.png differ