mirror of https://github.com/mgba-emu/mgba.git
mGUI: Refactor menu code into separate functions
This commit is contained in:
parent
c9e1b78426
commit
603de394d8
|
@ -10,6 +10,7 @@
|
|||
|
||||
CXX_GUARD_START
|
||||
|
||||
#include <mgba-util/gui.h>
|
||||
#include <mgba-util/vector.h>
|
||||
|
||||
#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, ...);
|
||||
|
|
|
@ -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,11 +71,49 @@ 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);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -97,18 +140,22 @@ enum GUIMenuExitReason GUIShowMenu(struct GUIParams* params, struct GUIMenu* men
|
|||
menu->index = GUIMenuItemListSize(&menu->items) - 1;
|
||||
}
|
||||
}
|
||||
if (cursor != GUI_CURSOR_NOT_PRESENT) {
|
||||
|
||||
// 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 + start < GUIMenuItemListSize(&menu->items)) {
|
||||
if (menu->index != index + start || !cursorOverItem) {
|
||||
cursorOverItem = 1;
|
||||
if (index >= 0 && index + state->start < GUIMenuItemListSize(&menu->items)) {
|
||||
if (menu->index != index + state->start || !state->cursorOverItem) {
|
||||
state->cursorOverItem = 1;
|
||||
}
|
||||
menu->index = index + start;
|
||||
menu->index = index + state->start;
|
||||
} else {
|
||||
cursorOverItem = 0;
|
||||
state->cursorOverItem = 0;
|
||||
}
|
||||
} else if (cursor == GUI_CURSOR_DOWN || cursor == GUI_CURSOR_DRAGGING) {
|
||||
} 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) {
|
||||
|
@ -121,38 +168,51 @@ enum GUIMenuExitReason GUIShowMenu(struct GUIParams* params, struct GUIMenu* men
|
|||
}
|
||||
}
|
||||
|
||||
if (menu->index < start) {
|
||||
start = menu->index;
|
||||
// Move view up if the active item is before the top of the view
|
||||
if (menu->index < state->start) {
|
||||
state->start = menu->index;
|
||||
}
|
||||
while ((menu->index - start + 4) * lineHeight > params->height) {
|
||||
++start;
|
||||
// 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)) {
|
||||
break;
|
||||
return GUI_MENU_EXIT_CANCEL;
|
||||
}
|
||||
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);
|
||||
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 (cursorOverItem == 1 && (cursor == GUI_CURSOR_UP || cursor == GUI_CURSOR_NOT_PRESENT)) {
|
||||
cursorOverItem = 2;
|
||||
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, GUIMenuItemListGetPointer(&menu->items, menu->index)->data.v.p);
|
||||
menu->background->draw(menu->background, GUIMenuItemListGetConstPointer(&menu->items, menu->index)->data.v.p);
|
||||
}
|
||||
if (params->guiPrepare) {
|
||||
params->guiPrepare();
|
||||
|
@ -167,13 +227,13 @@ enum GUIMenuExitReason GUIShowMenu(struct GUIParams* params, struct GUIMenu* men
|
|||
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) {
|
||||
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);
|
||||
}
|
||||
struct GUIMenuItem* item = GUIMenuItemListGetPointer(&menu->items, i);
|
||||
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]);
|
||||
|
@ -201,16 +261,23 @@ enum GUIMenuExitReason GUIShowMenu(struct GUIParams* params, struct GUIMenu* men
|
|||
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 (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;
|
||||
}
|
||||
return GUI_MENU_EXIT_CANCEL;
|
||||
GUIMenuDraw(params, menu, state);
|
||||
return GUI_MENU_CONTINUE;
|
||||
}
|
||||
|
||||
enum GUIMenuExitReason GUIShowMessageBox(struct GUIParams* params, int buttons, int frames, const char* format, ...) {
|
||||
|
|
Loading…
Reference in New Issue