all repos — mgba @ 9fb7485051241fa7939a6a88954dedbf69d7616a

mGBA Game Boy Advance Emulator

src/util/gui/menu.c (view raw)

  1/* Copyright (c) 2013-2015 Jeffrey Pfau
  2 *
  3 * This Source Code Form is subject to the terms of the Mozilla Public
  4 * License, v. 2.0. If a copy of the MPL was not distributed with this
  5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  6#include <mgba-util/gui/menu.h>
  7
  8#include <mgba-util/gui.h>
  9#include <mgba-util/gui/font.h>
 10
 11#ifdef _3DS
 12#include <3ds.h>
 13#elif defined(__SWITCH__)
 14#include <switch.h>
 15#endif
 16
 17DEFINE_VECTOR(GUIMenuItemList, struct GUIMenuItem);
 18
 19enum GUIMenuExitReason GUIShowMenu(struct GUIParams* params, struct GUIMenu* menu, struct GUIMenuItem** item) {
 20	size_t start = 0;
 21	size_t lineHeight = GUIFontHeight(params->font);
 22	size_t pageSize = params->height / lineHeight;
 23	if (pageSize > 4) {
 24		pageSize -= 4;
 25	} else {
 26		pageSize = 1;
 27	}
 28	int cursorOverItem = 0;
 29
 30	GUIInvalidateKeys(params);
 31	while (true) {
 32#ifdef _3DS
 33		if (!aptMainLoop()) {
 34			return GUI_MENU_EXIT_CANCEL;
 35		}
 36#elif defined(__SWITCH__)
 37		if (!appletMainLoop()) {
 38			return GUI_MENU_EXIT_CANCEL;
 39		}
 40#endif
 41		uint32_t newInput = 0;
 42		GUIPollInput(params, &newInput, 0);
 43		unsigned cx, cy;
 44		enum GUICursorState cursor = GUIPollCursor(params, &cx, &cy);
 45
 46		if (newInput & (1 << GUI_INPUT_UP) && menu->index > 0) {
 47			--menu->index;
 48		}
 49		if (newInput & (1 << GUI_INPUT_DOWN) && menu->index < GUIMenuItemListSize(&menu->items) - 1) {
 50			++menu->index;
 51		}
 52		if (newInput & (1 << GUI_INPUT_LEFT)) {
 53			struct GUIMenuItem* item = GUIMenuItemListGetPointer(&menu->items, menu->index);
 54			if (item->validStates) {
 55				if (item->state > 0) {
 56					unsigned oldState = item->state;
 57					do {
 58						--item->state;
 59					} while (!item->validStates[item->state] && item->state > 0);
 60					if (!item->validStates[item->state]) {
 61						item->state = oldState;
 62					}
 63				}
 64			} else if (menu->index >= pageSize) {
 65				menu->index -= pageSize;
 66			} else {
 67				menu->index = 0;
 68			}
 69		}
 70		if (newInput & (1 << GUI_INPUT_RIGHT)) {
 71			struct GUIMenuItem* item = GUIMenuItemListGetPointer(&menu->items, menu->index);
 72			if (item->validStates) {
 73				if (item->state < item->nStates - 1) {
 74					unsigned oldState = item->state;
 75					do {
 76						++item->state;
 77					} while (!item->validStates[item->state] && item->state < item->nStates - 1);
 78					if (!item->validStates[item->state]) {
 79						item->state = oldState;
 80					}
 81				}
 82			} else if (menu->index + pageSize < GUIMenuItemListSize(&menu->items)) {
 83				menu->index += pageSize;
 84			} else {
 85				menu->index = GUIMenuItemListSize(&menu->items) - 1;
 86			}
 87		}
 88		if (cursor != GUI_CURSOR_NOT_PRESENT) {
 89			if (cx < params->width - 16) {
 90				int index = (cy / lineHeight) - 2;
 91				if (index >= 0 && index + start < GUIMenuItemListSize(&menu->items)) {
 92					if (menu->index != index + start || !cursorOverItem) {
 93						cursorOverItem = 1;
 94					}
 95					menu->index = index + start;
 96				} else {
 97					cursorOverItem = 0;
 98				}
 99			} else if (cursor == GUI_CURSOR_DOWN || cursor == GUI_CURSOR_DRAGGING) {
100				if (cy <= 2 * lineHeight && cy > lineHeight && menu->index > 0) {
101					--menu->index;
102				} else if (cy <= params->height && cy > params->height - lineHeight && menu->index < GUIMenuItemListSize(&menu->items) - 1) {
103					++menu->index;
104				} else if (cy <= params->height - lineHeight && cy > 2 * lineHeight) {
105					size_t location = cy - 2 * lineHeight;
106					location *= GUIMenuItemListSize(&menu->items) - 1;
107					menu->index = location / (params->height - 3 * lineHeight);
108				}
109			}
110		}
111
112		if (menu->index < start) {
113			start = menu->index;
114		}
115		while ((menu->index - start + 4) * lineHeight > params->height) {
116			++start;
117		}
118		if (newInput & (1 << GUI_INPUT_CANCEL)) {
119			break;
120		}
121		if (newInput & (1 << GUI_INPUT_SELECT) || (cursorOverItem == 2 && cursor == GUI_CURSOR_CLICKED)) {
122			*item = GUIMenuItemListGetPointer(&menu->items, menu->index);
123			if ((*item)->submenu) {
124				enum GUIMenuExitReason reason = GUIShowMenu(params, (*item)->submenu, item);
125				if (reason != GUI_MENU_EXIT_BACK) {
126					return reason;
127				}
128			} else {
129				return GUI_MENU_EXIT_ACCEPT;
130			}
131		}
132		if (cursorOverItem == 1 && (cursor == GUI_CURSOR_UP || cursor == GUI_CURSOR_NOT_PRESENT)) {
133			cursorOverItem = 2;
134		}
135		if (newInput & (1 << GUI_INPUT_BACK)) {
136			return GUI_MENU_EXIT_BACK;
137		}
138
139		params->drawStart();
140		if (menu->background) {
141			menu->background->draw(menu->background, GUIMenuItemListGetPointer(&menu->items, menu->index)->data);
142		}
143		if (params->guiPrepare) {
144			params->guiPrepare();
145		}
146		unsigned y = lineHeight;
147		GUIFontPrint(params->font, 0, y, GUI_ALIGN_LEFT, 0xFFFFFFFF, menu->title);
148		if (menu->subtitle) {
149			GUIFontPrint(params->font, 0, y * 2, GUI_ALIGN_LEFT, 0xFFFFFFFF, menu->subtitle);
150		}
151		y += 2 * lineHeight;
152		size_t itemsPerScreen = (params->height - y) / lineHeight;
153		size_t i;
154		for (i = start; i < GUIMenuItemListSize(&menu->items); ++i) {
155			int color = 0xE0A0A0A0;
156			if (i == menu->index) {
157				color = 0xFFFFFFFF;
158				GUIFontDrawIcon(params->font, lineHeight * 0.8f, y, GUI_ALIGN_BOTTOM | GUI_ALIGN_RIGHT, GUI_ORIENT_0, 0xFFFFFFFF, GUI_ICON_POINTER);
159			}
160			struct GUIMenuItem* item = GUIMenuItemListGetPointer(&menu->items, i);
161			GUIFontPrint(params->font, lineHeight, y, GUI_ALIGN_LEFT, color, item->title);
162			if (item->validStates && item->validStates[item->state]) {
163				GUIFontPrintf(params->font, params->width, y, GUI_ALIGN_RIGHT, color, "%s ", item->validStates[item->state]);
164			}
165			y += lineHeight;
166			if (y + lineHeight > params->height) {
167				break;
168			}
169		}
170
171		if (itemsPerScreen < GUIMenuItemListSize(&menu->items)) {
172			size_t top = 2 * lineHeight;
173			size_t bottom = params->height - 8;
174			unsigned w;
175			unsigned right;
176			GUIFontIconMetrics(params->font, GUI_ICON_SCROLLBAR_BUTTON, &right, 0);
177			GUIFontIconMetrics(params->font, GUI_ICON_SCROLLBAR_TRACK, &w, 0);
178			right = (right - w) / 2;
179			GUIFontDrawIconSize(params->font, params->width - right - 8, top, 0, bottom - top, 0xA0FFFFFF, GUI_ICON_SCROLLBAR_TRACK);
180			GUIFontDrawIcon(params->font, params->width - 8, top, GUI_ALIGN_HCENTER | GUI_ALIGN_BOTTOM, GUI_ORIENT_VMIRROR, 0xFFFFFFFF, GUI_ICON_SCROLLBAR_BUTTON);
181			GUIFontDrawIcon(params->font, params->width - 8, bottom, GUI_ALIGN_HCENTER | GUI_ALIGN_TOP, GUI_ORIENT_0, 0xFFFFFFFF, GUI_ICON_SCROLLBAR_BUTTON);
182
183			y = menu->index * (bottom - top - 16) / GUIMenuItemListSize(&menu->items);
184			GUIFontDrawIcon(params->font, params->width - 8, top + y, GUI_ALIGN_HCENTER | GUI_ALIGN_TOP, GUI_ORIENT_0, 0xFFFFFFFF, GUI_ICON_SCROLLBAR_THUMB);
185		}
186
187		GUIDrawBattery(params);
188		GUIDrawClock(params);
189
190		if (cursor != GUI_CURSOR_NOT_PRESENT) {
191			GUIFontDrawIcon(params->font, cx, cy, GUI_ALIGN_HCENTER | GUI_ALIGN_TOP, GUI_ORIENT_0, 0xFFFFFFFF, GUI_ICON_CURSOR);
192		}
193
194		if (params->guiFinish) {
195			params->guiFinish();
196		}
197		params->drawEnd();
198	}
199	return GUI_MENU_EXIT_CANCEL;
200}
201
202enum GUIMenuExitReason GUIShowMessageBox(struct GUIParams* params, int buttons, int frames, const char* format, ...) {
203	va_list args;
204	va_start(args, format);
205	char message[256] = {0};
206	vsnprintf(message, sizeof(message) - 1, format, args);
207	va_end(args);
208
209	while (true) {
210		if (frames) {
211			--frames;
212			if (!frames) {
213				break;
214			}
215		}
216		params->drawStart();
217		if (params->guiPrepare) {
218			params->guiPrepare();
219		}
220		GUIFontPrint(params->font, params->width / 2, (GUIFontHeight(params->font) + params->height) / 2, GUI_ALIGN_HCENTER, 0xFFFFFFFF, message);
221		if (params->guiFinish) {
222			params->guiFinish();
223		}
224		params->drawEnd();
225
226		uint32_t input = 0;
227		GUIPollInput(params, &input, 0);
228		if (input) {
229			if (input & (1 << GUI_INPUT_SELECT)) {
230				if (buttons & GUI_MESSAGE_BOX_OK) {
231					return GUI_MENU_EXIT_ACCEPT;
232				}
233				if (buttons & GUI_MESSAGE_BOX_CANCEL) {
234					return GUI_MENU_EXIT_CANCEL;
235				}
236			}
237			if (input & (1 << GUI_INPUT_BACK)) {
238				if (buttons & GUI_MESSAGE_BOX_CANCEL) {
239					return GUI_MENU_EXIT_BACK;
240				}
241				if (buttons & GUI_MESSAGE_BOX_OK) {
242					return GUI_MENU_EXIT_ACCEPT;
243				}
244			}
245			if (input & (1 << GUI_INPUT_CANCEL)) {
246				if (buttons & GUI_MESSAGE_BOX_CANCEL) {
247					return GUI_MENU_EXIT_CANCEL;
248				}
249				if (buttons & GUI_MESSAGE_BOX_OK) {
250					return GUI_MENU_EXIT_ACCEPT;
251				}
252			}
253		}
254	}
255	return GUI_MENU_EXIT_CANCEL;
256}
257
258void GUIDrawBattery(struct GUIParams* params) {
259	if (!params->batteryState) {
260		return;
261	}
262	int state = params->batteryState();
263	if (state == BATTERY_NOT_PRESENT) {
264		return;
265	}
266	uint32_t color = 0xFF000000;
267	if (state == (BATTERY_CHARGING | BATTERY_FULL)) {
268		color |= 0xFFC060;
269	} else if (state & BATTERY_CHARGING) {
270		color |= 0x60FF60;
271	} else if (state >= BATTERY_HALF) {
272		color |= 0xFFFFFF;
273	} else if (state == BATTERY_LOW) {
274		color |= 0x30FFFF;
275	} else {
276		color |= 0x3030FF;
277	}
278
279	enum GUIIcon batteryIcon;
280	switch (state & ~BATTERY_CHARGING) {
281	case BATTERY_EMPTY:
282		batteryIcon = GUI_ICON_BATTERY_EMPTY;
283		break;
284	case BATTERY_LOW:
285		batteryIcon = GUI_ICON_BATTERY_LOW;
286		break;
287	case BATTERY_HALF:
288		batteryIcon = GUI_ICON_BATTERY_HALF;
289		break;
290	case BATTERY_HIGH:
291		batteryIcon = GUI_ICON_BATTERY_HIGH;
292		break;
293	case BATTERY_FULL:
294		batteryIcon = GUI_ICON_BATTERY_FULL;
295		break;
296	default:
297		batteryIcon = GUI_ICON_BATTERY_EMPTY;
298		break;
299	}
300
301	GUIFontDrawIcon(params->font, params->width, 0, GUI_ALIGN_RIGHT, GUI_ORIENT_0, color, batteryIcon);
302}
303
304void GUIDrawClock(struct GUIParams* params) {
305	char buffer[32];
306	time_t t = time(0);
307	struct tm tm;
308	localtime_r(&t, &tm);
309	strftime(buffer, sizeof(buffer), "%H:%M:%S", &tm);
310	GUIFontPrint(params->font, params->width / 2, GUIFontHeight(params->font), GUI_ALIGN_HCENTER, 0xFFFFFFFF, buffer);
311}