From 603de394d8bb58dac250cc90f5aa1db74b42155d Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 29 Nov 2021 23:11:17 -0800 Subject: [PATCH] mGUI: Refactor menu code into separate functions --- include/mgba-util/gui/menu.h | 24 +++ src/util/gui/menu.c | 369 +++++++++++++++++++++-------------- 2 files changed, 242 insertions(+), 151 deletions(-) diff --git a/include/mgba-util/gui/menu.h b/include/mgba-util/gui/menu.h index 8ac9d6846..55cbad5d3 100644 --- a/include/mgba-util/gui/menu.h +++ b/include/mgba-util/gui/menu.h @@ -10,6 +10,7 @@ CXX_GUARD_START +#include #include #define GUI_V_V (struct GUIVariant) { .type = GUI_VARIANT_VOID } @@ -73,10 +74,29 @@ struct GUIMenu { struct GUIBackground* background; }; +struct GUIMenuSavedState { + struct GUIMenu* menu; + size_t start; +}; + +DECLARE_VECTOR(GUIMenuSavedList, struct GUIMenuSavedState); + +struct GUIMenuState { + size_t start; + int cursorOverItem; + enum GUICursorState cursor; + unsigned cx, cy; + struct GUIMenuSavedList stack; + + struct GUIMenuItem* resultItem; +}; + enum GUIMenuExitReason { + GUI_MENU_CONTINUE = 0, GUI_MENU_EXIT_ACCEPT, GUI_MENU_EXIT_BACK, GUI_MENU_EXIT_CANCEL, + GUI_MENU_ENTER, }; enum GUIMessageBoxButtons { @@ -85,7 +105,11 @@ enum GUIMessageBoxButtons { }; struct GUIParams; +void GUIMenuStateInit(struct GUIMenuState*); +void GUIMenuStateDeinit(struct GUIMenuState*); + enum GUIMenuExitReason GUIShowMenu(struct GUIParams* params, struct GUIMenu* menu, struct GUIMenuItem** item); +enum GUIMenuExitReason GUIMenuRun(struct GUIParams* params, struct GUIMenu* menu, struct GUIMenuState* state); ATTRIBUTE_FORMAT(printf, 4, 5) enum GUIMenuExitReason GUIShowMessageBox(struct GUIParams* params, int buttons, int frames, const char* format, ...); diff --git a/src/util/gui/menu.c b/src/util/gui/menu.c index 13fcbd9c9..9e9227540 100644 --- a/src/util/gui/menu.c +++ b/src/util/gui/menu.c @@ -15,6 +15,7 @@ #endif DEFINE_VECTOR(GUIMenuItemList, struct GUIMenuItem); +DEFINE_VECTOR(GUIMenuSavedList, struct GUIMenuSavedState); void _itemNext(struct GUIMenuItem* item, bool wrap) { if (item->state < item->nStates - 1) { @@ -44,17 +45,21 @@ void _itemPrev(struct GUIMenuItem* item, bool wrap) { } } -enum GUIMenuExitReason GUIShowMenu(struct GUIParams* params, struct GUIMenu* menu, struct GUIMenuItem** item) { - size_t start = 0; - size_t lineHeight = GUIFontHeight(params->font); - size_t pageSize = params->height / lineHeight; - if (pageSize > 4) { - pageSize -= 4; - } else { - pageSize = 1; - } - int cursorOverItem = 0; +void GUIMenuStateInit(struct GUIMenuState* state) { + state->start = 0; + state->cursorOverItem = 0; + state->cursor = GUI_CURSOR_NOT_PRESENT; + state->resultItem = NULL; + GUIMenuSavedListInit(&state->stack, 0); +} +void GUIMenuStateDeinit(struct GUIMenuState* state) { + GUIMenuSavedListDeinit(&state->stack); +} + +enum GUIMenuExitReason GUIShowMenu(struct GUIParams* params, struct GUIMenu* menu, struct GUIMenuItem** item) { + struct GUIMenuState state; + GUIMenuStateInit(&state); GUIInvalidateKeys(params); while (true) { #ifdef _3DS @@ -66,151 +71,213 @@ enum GUIMenuExitReason GUIShowMenu(struct GUIParams* params, struct GUIMenu* men return GUI_MENU_EXIT_CANCEL; } #endif - uint32_t newInput = 0; - GUIPollInput(params, &newInput, 0); - unsigned cx, cy; - enum GUICursorState cursor = GUIPollCursor(params, &cx, &cy); - - if (newInput & (1 << GUI_INPUT_UP) && menu->index > 0) { - --menu->index; - } - if (newInput & (1 << GUI_INPUT_DOWN) && menu->index < GUIMenuItemListSize(&menu->items) - 1) { - ++menu->index; - } - if (newInput & (1 << GUI_INPUT_LEFT)) { - struct GUIMenuItem* item = GUIMenuItemListGetPointer(&menu->items, menu->index); - if (item->validStates) { - _itemPrev(item, false); - } else if (menu->index >= pageSize) { - menu->index -= pageSize; - } else { - menu->index = 0; - } - } - if (newInput & (1 << GUI_INPUT_RIGHT)) { - struct GUIMenuItem* item = GUIMenuItemListGetPointer(&menu->items, menu->index); - if (item->validStates) { - _itemNext(item, false); - } else if (menu->index + pageSize < GUIMenuItemListSize(&menu->items)) { - menu->index += pageSize; - } else { - menu->index = GUIMenuItemListSize(&menu->items) - 1; - } - } - if (cursor != GUI_CURSOR_NOT_PRESENT) { - if (cx < params->width - 16) { - int index = (cy / lineHeight) - 2; - if (index >= 0 && index + start < GUIMenuItemListSize(&menu->items)) { - if (menu->index != index + start || !cursorOverItem) { - cursorOverItem = 1; - } - menu->index = index + start; - } else { - cursorOverItem = 0; - } - } else if (cursor == GUI_CURSOR_DOWN || cursor == GUI_CURSOR_DRAGGING) { - if (cy <= 2 * lineHeight && cy > lineHeight && menu->index > 0) { - --menu->index; - } else if (cy <= params->height && cy > params->height - lineHeight && menu->index < GUIMenuItemListSize(&menu->items) - 1) { - ++menu->index; - } else if (cy <= params->height - lineHeight && cy > 2 * lineHeight) { - size_t location = cy - 2 * lineHeight; - location *= GUIMenuItemListSize(&menu->items) - 1; - menu->index = location / (params->height - 3 * lineHeight); - } - } - } - - if (menu->index < start) { - start = menu->index; - } - while ((menu->index - start + 4) * lineHeight > params->height) { - ++start; - } - if (newInput & (1 << GUI_INPUT_CANCEL)) { - break; - } - if (newInput & (1 << GUI_INPUT_SELECT) || (cursorOverItem == 2 && cursor == GUI_CURSOR_CLICKED)) { - *item = GUIMenuItemListGetPointer(&menu->items, menu->index); - if ((*item)->submenu) { - enum GUIMenuExitReason reason = GUIShowMenu(params, (*item)->submenu, item); - if (reason != GUI_MENU_EXIT_BACK) { - return reason; - } - } else if ((*item)->validStates && GUIVariantIsString((*item)->data)) { - _itemNext(*item, true); - } else { - return GUI_MENU_EXIT_ACCEPT; - } - } - if (cursorOverItem == 1 && (cursor == GUI_CURSOR_UP || cursor == GUI_CURSOR_NOT_PRESENT)) { - cursorOverItem = 2; - } - if (newInput & (1 << GUI_INPUT_BACK)) { - return GUI_MENU_EXIT_BACK; - } - - params->drawStart(); - if (menu->background) { - menu->background->draw(menu->background, GUIMenuItemListGetPointer(&menu->items, menu->index)->data.v.p); - } - if (params->guiPrepare) { - params->guiPrepare(); - } - unsigned y = lineHeight; - GUIFontPrint(params->font, 0, y, GUI_ALIGN_LEFT, 0xFFFFFFFF, menu->title); - if (menu->subtitle) { - GUIFontPrint(params->font, 0, y * 2, GUI_ALIGN_LEFT, 0xFFFFFFFF, menu->subtitle); - } - y += 2 * lineHeight; - unsigned right; - GUIFontIconMetrics(params->font, GUI_ICON_SCROLLBAR_BUTTON, &right, 0); - size_t itemsPerScreen = (params->height - y) / lineHeight; - size_t i; - for (i = start; i < GUIMenuItemListSize(&menu->items); ++i) { - int color = 0xE0A0A0A0; - if (i == menu->index) { - color = 0xFFFFFFFF; - GUIFontDrawIcon(params->font, lineHeight * 0.8f, y, GUI_ALIGN_BOTTOM | GUI_ALIGN_RIGHT, GUI_ORIENT_0, 0xFFFFFFFF, GUI_ICON_POINTER); - } - struct GUIMenuItem* item = GUIMenuItemListGetPointer(&menu->items, i); - GUIFontPrint(params->font, lineHeight, y, GUI_ALIGN_LEFT, color, item->title); - if (item->validStates && item->validStates[item->state]) { - GUIFontPrintf(params->font, params->width - right - 8, y, GUI_ALIGN_RIGHT, color, "%s ", item->validStates[item->state]); - } - y += lineHeight; - if (y + lineHeight > params->height) { + enum GUIMenuExitReason reason = GUIMenuRun(params, menu, &state); + switch (reason) { + case GUI_MENU_EXIT_BACK: + if (GUIMenuSavedListSize(&state.stack) > 0) { + struct GUIMenuSavedState* last = GUIMenuSavedListGetPointer(&state.stack, GUIMenuSavedListSize(&state.stack) - 1); + state.start = last->start; + menu = last->menu; + GUIMenuSavedListResize(&state.stack, -1); break; } + // Fall through + case GUI_MENU_EXIT_CANCEL: + case GUI_MENU_EXIT_ACCEPT: + *item = state.resultItem; + GUIMenuStateDeinit(&state); + return reason; + case GUI_MENU_ENTER: + *GUIMenuSavedListAppend(&state.stack) = (struct GUIMenuSavedState) { + .start = state.start, + .menu = menu + }; + menu = state.resultItem->submenu; + state.start = 0; + break; + case GUI_MENU_CONTINUE: + break; } - - if (itemsPerScreen < GUIMenuItemListSize(&menu->items)) { - size_t top = 2 * lineHeight; - size_t bottom = params->height - 8; - unsigned w; - GUIFontIconMetrics(params->font, GUI_ICON_SCROLLBAR_TRACK, &w, 0); - right = (right - w) / 2; - GUIFontDrawIconSize(params->font, params->width - right - 8, top, 0, bottom - top, 0xA0FFFFFF, GUI_ICON_SCROLLBAR_TRACK); - GUIFontDrawIcon(params->font, params->width - 8, top, GUI_ALIGN_HCENTER | GUI_ALIGN_BOTTOM, GUI_ORIENT_VMIRROR, 0xFFFFFFFF, GUI_ICON_SCROLLBAR_BUTTON); - GUIFontDrawIcon(params->font, params->width - 8, bottom, GUI_ALIGN_HCENTER | GUI_ALIGN_TOP, GUI_ORIENT_0, 0xFFFFFFFF, GUI_ICON_SCROLLBAR_BUTTON); - - y = menu->index * (bottom - top - 16) / GUIMenuItemListSize(&menu->items); - GUIFontDrawIcon(params->font, params->width - 8, top + y, GUI_ALIGN_HCENTER | GUI_ALIGN_TOP, GUI_ORIENT_0, 0xFFFFFFFF, GUI_ICON_SCROLLBAR_THUMB); - } - - GUIDrawBattery(params); - GUIDrawClock(params); - - if (cursor != GUI_CURSOR_NOT_PRESENT) { - GUIFontDrawIcon(params->font, cx, cy, GUI_ALIGN_HCENTER | GUI_ALIGN_TOP, GUI_ORIENT_0, 0xFFFFFFFF, GUI_ICON_CURSOR); - } - - if (params->guiFinish) { - params->guiFinish(); - } - params->drawEnd(); } - return GUI_MENU_EXIT_CANCEL; +} + +static enum GUIMenuExitReason GUIMenuPollInput(struct GUIParams* params, struct GUIMenu* menu, struct GUIMenuState* state) { + size_t lineHeight = GUIFontHeight(params->font); + size_t pageSize = params->height / lineHeight; + if (pageSize > 4) { + pageSize -= 4; + } else { + pageSize = 1; + } + uint32_t newInput = 0; + GUIPollInput(params, &newInput, NULL); + state->cursor = GUIPollCursor(params, &state->cx, &state->cy); + + // Check for new direction presses + if (newInput & (1 << GUI_INPUT_UP) && menu->index > 0) { + --menu->index; + } + if (newInput & (1 << GUI_INPUT_DOWN) && menu->index < GUIMenuItemListSize(&menu->items) - 1) { + ++menu->index; + } + if (newInput & (1 << GUI_INPUT_LEFT)) { + struct GUIMenuItem* item = GUIMenuItemListGetPointer(&menu->items, menu->index); + if (item->validStates) { + _itemPrev(item, false); + } else if (menu->index >= pageSize) { + menu->index -= pageSize; + } else { + menu->index = 0; + } + } + if (newInput & (1 << GUI_INPUT_RIGHT)) { + struct GUIMenuItem* item = GUIMenuItemListGetPointer(&menu->items, menu->index); + if (item->validStates) { + _itemNext(item, false); + } else if (menu->index + pageSize < GUIMenuItemListSize(&menu->items)) { + menu->index += pageSize; + } else { + menu->index = GUIMenuItemListSize(&menu->items) - 1; + } + } + + // Handle cursor movement + if (state->cursor != GUI_CURSOR_NOT_PRESENT) { + unsigned cx = state->cx; + unsigned cy = state->cy; + if (cx < params->width - 16) { + int index = (cy / lineHeight) - 2; + if (index >= 0 && index + state->start < GUIMenuItemListSize(&menu->items)) { + if (menu->index != index + state->start || !state->cursorOverItem) { + state->cursorOverItem = 1; + } + menu->index = index + state->start; + } else { + state->cursorOverItem = 0; + } + } else if (state->cursor == GUI_CURSOR_DOWN || state->cursor == GUI_CURSOR_DRAGGING) { + if (cy <= 2 * lineHeight && cy > lineHeight && menu->index > 0) { + --menu->index; + } else if (cy <= params->height && cy > params->height - lineHeight && menu->index < GUIMenuItemListSize(&menu->items) - 1) { + ++menu->index; + } else if (cy <= params->height - lineHeight && cy > 2 * lineHeight) { + size_t location = cy - 2 * lineHeight; + location *= GUIMenuItemListSize(&menu->items) - 1; + menu->index = location / (params->height - 3 * lineHeight); + } + } + } + + // Move view up if the active item is before the top of the view + if (menu->index < state->start) { + state->start = menu->index; + } + // Move the view down if the active item is after the bottom of the view + while ((menu->index - state->start + 4) * lineHeight > params->height) { + // TODO: Should this loop be replaced with division? + ++state->start; + } + + // Handle action inputs + if (newInput & (1 << GUI_INPUT_CANCEL)) { + return GUI_MENU_EXIT_CANCEL; + } + if (newInput & (1 << GUI_INPUT_SELECT) || (state->cursorOverItem == 2 && state->cursor == GUI_CURSOR_CLICKED)) { + struct GUIMenuItem* item = GUIMenuItemListGetPointer(&menu->items, menu->index); + if (item->submenu) { + // Selected menus get shown inline + state->resultItem = item; + return GUI_MENU_ENTER; + } else if (item->validStates && GUIVariantIsString(item->data)) { + // Selected items with multiple (named) states get scrolled through + _itemNext(item, true); + } else { + // Otherwise tell caller item was accepted + state->resultItem = item; + return GUI_MENU_EXIT_ACCEPT; + } + } + if (state->cursorOverItem == 1 && (state->cursor == GUI_CURSOR_UP || state->cursor == GUI_CURSOR_NOT_PRESENT)) { + state->cursorOverItem = 2; + } + if (newInput & (1 << GUI_INPUT_BACK)) { + return GUI_MENU_EXIT_BACK; + } + + // No action taken + return GUI_MENU_CONTINUE; +} + +static void GUIMenuDraw(struct GUIParams* params, const struct GUIMenu* menu, const struct GUIMenuState* state) { + size_t lineHeight = GUIFontHeight(params->font); + params->drawStart(); + if (menu->background) { + menu->background->draw(menu->background, GUIMenuItemListGetConstPointer(&menu->items, menu->index)->data.v.p); + } + if (params->guiPrepare) { + params->guiPrepare(); + } + unsigned y = lineHeight; + GUIFontPrint(params->font, 0, y, GUI_ALIGN_LEFT, 0xFFFFFFFF, menu->title); + if (menu->subtitle) { + GUIFontPrint(params->font, 0, y * 2, GUI_ALIGN_LEFT, 0xFFFFFFFF, menu->subtitle); + } + y += 2 * lineHeight; + unsigned right; + GUIFontIconMetrics(params->font, GUI_ICON_SCROLLBAR_BUTTON, &right, 0); + size_t itemsPerScreen = (params->height - y) / lineHeight; + size_t i; + for (i = state->start; i < GUIMenuItemListSize(&menu->items); ++i) { + int color = 0xE0A0A0A0; + if (i == menu->index) { + color = 0xFFFFFFFF; + GUIFontDrawIcon(params->font, lineHeight * 0.8f, y, GUI_ALIGN_BOTTOM | GUI_ALIGN_RIGHT, GUI_ORIENT_0, 0xFFFFFFFF, GUI_ICON_POINTER); + } + const struct GUIMenuItem* item = GUIMenuItemListGetConstPointer(&menu->items, i); + GUIFontPrint(params->font, lineHeight, y, GUI_ALIGN_LEFT, color, item->title); + if (item->validStates && item->validStates[item->state]) { + GUIFontPrintf(params->font, params->width - right - 8, y, GUI_ALIGN_RIGHT, color, "%s ", item->validStates[item->state]); + } + y += lineHeight; + if (y + lineHeight > params->height) { + break; + } + } + + if (itemsPerScreen < GUIMenuItemListSize(&menu->items)) { + size_t top = 2 * lineHeight; + size_t bottom = params->height - 8; + unsigned w; + GUIFontIconMetrics(params->font, GUI_ICON_SCROLLBAR_TRACK, &w, 0); + right = (right - w) / 2; + GUIFontDrawIconSize(params->font, params->width - right - 8, top, 0, bottom - top, 0xA0FFFFFF, GUI_ICON_SCROLLBAR_TRACK); + GUIFontDrawIcon(params->font, params->width - 8, top, GUI_ALIGN_HCENTER | GUI_ALIGN_BOTTOM, GUI_ORIENT_VMIRROR, 0xFFFFFFFF, GUI_ICON_SCROLLBAR_BUTTON); + GUIFontDrawIcon(params->font, params->width - 8, bottom, GUI_ALIGN_HCENTER | GUI_ALIGN_TOP, GUI_ORIENT_0, 0xFFFFFFFF, GUI_ICON_SCROLLBAR_BUTTON); + + y = menu->index * (bottom - top - 16) / GUIMenuItemListSize(&menu->items); + GUIFontDrawIcon(params->font, params->width - 8, top + y, GUI_ALIGN_HCENTER | GUI_ALIGN_TOP, GUI_ORIENT_0, 0xFFFFFFFF, GUI_ICON_SCROLLBAR_THUMB); + } + + GUIDrawBattery(params); + GUIDrawClock(params); + + if (state->cursor != GUI_CURSOR_NOT_PRESENT) { + GUIFontDrawIcon(params->font, state->cx, state->cy, GUI_ALIGN_HCENTER | GUI_ALIGN_TOP, GUI_ORIENT_0, 0xFFFFFFFF, GUI_ICON_CURSOR); + } + + if (params->guiFinish) { + params->guiFinish(); + } + params->drawEnd(); +} + +enum GUIMenuExitReason GUIMenuRun(struct GUIParams* params, struct GUIMenu* menu, struct GUIMenuState* state) { + enum GUIMenuExitReason reason = GUIMenuPollInput(params, menu, state); + if (reason != GUI_MENU_CONTINUE) { + return reason; + } + GUIMenuDraw(params, menu, state); + return GUI_MENU_CONTINUE; } enum GUIMenuExitReason GUIShowMessageBox(struct GUIParams* params, int buttons, int frames, const char* format, ...) {