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 uint32_t color = 0xFF000000;
264 if (state == (BATTERY_CHARGING | BATTERY_FULL)) {
265 color |= 0xFFC060;
266 } else if (state & BATTERY_CHARGING) {
267 color |= 0x60FF60;
268 } else if (state >= BATTERY_HALF) {
269 color |= 0xFFFFFF;
270 } else if (state == BATTERY_LOW) {
271 color |= 0x30FFFF;
272 } else {
273 color |= 0x3030FF;
274 }
275
276 enum GUIIcon batteryIcon;
277 switch (state & ~BATTERY_CHARGING) {
278 case BATTERY_EMPTY:
279 batteryIcon = GUI_ICON_BATTERY_EMPTY;
280 break;
281 case BATTERY_LOW:
282 batteryIcon = GUI_ICON_BATTERY_LOW;
283 break;
284 case BATTERY_HALF:
285 batteryIcon = GUI_ICON_BATTERY_HALF;
286 break;
287 case BATTERY_HIGH:
288 batteryIcon = GUI_ICON_BATTERY_HIGH;
289 break;
290 case BATTERY_FULL:
291 batteryIcon = GUI_ICON_BATTERY_FULL;
292 break;
293 default:
294 batteryIcon = GUI_ICON_BATTERY_EMPTY;
295 break;
296 }
297
298 GUIFontDrawIcon(params->font, params->width, 0, GUI_ALIGN_RIGHT, GUI_ORIENT_0, color, batteryIcon);
299}
300
301void GUIDrawClock(struct GUIParams* params) {
302 char buffer[32];
303 time_t t = time(0);
304 struct tm tm;
305 localtime_r(&t, &tm);
306 strftime(buffer, sizeof(buffer), "%H:%M:%S", &tm);
307 GUIFontPrint(params->font, params->width / 2, GUIFontHeight(params->font), GUI_ALIGN_HCENTER, 0xFFFFFFFF, buffer);
308}