all repos — mgba @ 0511d0a69e4e86f111cdd5bf60ad46f7bdb0ff5f

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 "menu.h"
  7
  8#include "util/gui.h"
  9#include "util/gui/font.h"
 10
 11DEFINE_VECTOR(GUIMenuItemList, struct GUIMenuItem);
 12
 13enum GUIMenuExitReason GUIShowMenu(struct GUIParams* params, struct GUIMenu* menu, struct GUIMenuItem** item) {
 14	size_t start = 0;
 15	size_t lineHeight = GUIFontHeight(params->font);
 16	size_t pageSize = params->height / lineHeight;
 17	if (pageSize > 4) {
 18		pageSize -= 4;
 19	} else {
 20		pageSize = 1;
 21	}
 22	int cursorOverItem = 0;
 23
 24	GUIInvalidateKeys(params);
 25	while (true) {
 26		uint32_t newInput = 0;
 27		GUIPollInput(params, &newInput, 0);
 28		unsigned cx, cy;
 29		enum GUICursorState cursor = GUIPollCursor(params, &cx, &cy);
 30
 31		if (newInput & (1 << GUI_INPUT_UP) && menu->index > 0) {
 32			--menu->index;
 33		}
 34		if (newInput & (1 << GUI_INPUT_DOWN) && menu->index < GUIMenuItemListSize(&menu->items) - 1) {
 35			++menu->index;
 36		}
 37		if (newInput & (1 << GUI_INPUT_LEFT)) {
 38			struct GUIMenuItem* item = GUIMenuItemListGetPointer(&menu->items, menu->index);
 39			if (item->validStates) {
 40				if (item->state > 0) {
 41					unsigned oldState = item->state;
 42					do {
 43						--item->state;
 44					} while (!item->validStates[item->state] && item->state > 0);
 45					if (!item->validStates[item->state]) {
 46						item->state = oldState;
 47					}
 48				}
 49			} else if (menu->index >= pageSize) {
 50				menu->index -= pageSize;
 51			} else {
 52				menu->index = 0;
 53			}
 54		}
 55		if (newInput & (1 << GUI_INPUT_RIGHT)) {
 56			struct GUIMenuItem* item = GUIMenuItemListGetPointer(&menu->items, menu->index);
 57			if (item->validStates) {
 58				if (item->state < item->nStates - 1) {
 59					unsigned oldState = item->state;
 60					do {
 61						++item->state;
 62					} while (!item->validStates[item->state] && item->state < item->nStates - 1);
 63					if (!item->validStates[item->state]) {
 64						item->state = oldState;
 65					}
 66				}
 67			} else if (menu->index + pageSize < GUIMenuItemListSize(&menu->items)) {
 68				menu->index += pageSize;
 69			} else {
 70				menu->index = GUIMenuItemListSize(&menu->items) - 1;
 71			}
 72		}
 73		if (cursor != GUI_CURSOR_NOT_PRESENT) {
 74			if (cx < params->width - 16) {
 75				int index = (cy / lineHeight) - 2;
 76				if (index >= 0 && index + start < GUIMenuItemListSize(&menu->items)) {
 77					if (menu->index != index + start || !cursorOverItem) {
 78						cursorOverItem = 1;
 79					}
 80					menu->index = index + start;
 81				} else {
 82					cursorOverItem = 0;
 83				}
 84			} else if (cursor == GUI_CURSOR_DOWN || cursor == GUI_CURSOR_DRAGGING) {
 85				if (cy <= 2 * lineHeight && cy > lineHeight && menu->index > 0) {
 86					--menu->index;
 87				} else if (cy <= params->height && cy > params->height - lineHeight && menu->index < GUIMenuItemListSize(&menu->items) - 1) {
 88					++menu->index;
 89				} else if (cy <= params->height - lineHeight && cy > 2 * lineHeight) {
 90					size_t location = cy - 2 * lineHeight;
 91					location *= GUIMenuItemListSize(&menu->items);
 92					menu->index = location / (params->height - 3 * lineHeight);
 93				}
 94			}
 95		}
 96
 97		if (menu->index < start) {
 98			start = menu->index;
 99		}
100		while ((menu->index - start + 4) * lineHeight > params->height) {
101			++start;
102		}
103		if (newInput & (1 << GUI_INPUT_CANCEL)) {
104			break;
105		}
106		if (newInput & (1 << GUI_INPUT_SELECT) || (cursorOverItem == 2 && cursor == GUI_CURSOR_CLICKED)) {
107			*item = GUIMenuItemListGetPointer(&menu->items, menu->index);
108			if ((*item)->submenu) {
109				enum GUIMenuExitReason reason = GUIShowMenu(params, (*item)->submenu, item);
110				if (reason != GUI_MENU_EXIT_BACK) {
111					return reason;
112				}
113			} else {
114				return GUI_MENU_EXIT_ACCEPT;
115			}
116		}
117		if (cursorOverItem == 1 && (cursor == GUI_CURSOR_UP || cursor == GUI_CURSOR_NOT_PRESENT)) {
118			cursorOverItem = 2;
119		}
120		if (newInput & (1 << GUI_INPUT_BACK)) {
121			return GUI_MENU_EXIT_BACK;
122		}
123
124		params->drawStart();
125		if (menu->background) {
126			menu->background->draw(menu->background, GUIMenuItemListGetPointer(&menu->items, menu->index)->data);
127		}
128		if (params->guiPrepare) {
129			params->guiPrepare();
130		}
131		unsigned y = lineHeight;
132		GUIFontPrint(params->font, 0, y, GUI_ALIGN_LEFT, 0xFFFFFFFF, menu->title);
133		if (menu->subtitle) {
134			GUIFontPrint(params->font, 0, y * 2, GUI_ALIGN_LEFT, 0xFFFFFFFF, menu->subtitle);
135		}
136		y += 2 * lineHeight;
137		size_t itemsPerScreen = (params->height - y) / lineHeight;
138		size_t i;
139		for (i = start; i < GUIMenuItemListSize(&menu->items); ++i) {
140			int color = 0xE0A0A0A0;
141			if (i == menu->index) {
142				color = 0xFFFFFFFF;
143				GUIFontDrawIcon(params->font, 2, y, GUI_ALIGN_BOTTOM | GUI_ALIGN_LEFT, GUI_ORIENT_0, 0xFFFFFFFF, GUI_ICON_POINTER);
144			}
145			struct GUIMenuItem* item = GUIMenuItemListGetPointer(&menu->items, i);
146			GUIFontPrintf(params->font, 0, y, GUI_ALIGN_LEFT, color, "  %s", item->title);
147			if (item->validStates && item->validStates[item->state]) {
148				GUIFontPrintf(params->font, params->width, y, GUI_ALIGN_RIGHT, color, "%s ", item->validStates[item->state]);
149			}
150			y += lineHeight;
151			if (y + lineHeight > params->height) {
152				break;
153			}
154		}
155
156		if (itemsPerScreen < GUIMenuItemListSize(&menu->items)) {
157			y = 2 * lineHeight;
158			GUIFontDrawIcon(params->font, params->width - 8, y, GUI_ALIGN_HCENTER | GUI_ALIGN_BOTTOM, GUI_ORIENT_VMIRROR, 0xFFFFFFFF, GUI_ICON_SCROLLBAR_BUTTON);
159			for (; y < params->height - 16; y += 16) {
160				GUIFontDrawIcon(params->font, params->width - 8, y, GUI_ALIGN_HCENTER | GUI_ALIGN_TOP, GUI_ORIENT_0, 0xFFFFFFFF, GUI_ICON_SCROLLBAR_TRACK);
161			}
162			GUIFontDrawIcon(params->font, params->width - 8, y, GUI_ALIGN_HCENTER | GUI_ALIGN_TOP, GUI_ORIENT_0, 0xFFFFFFFF, GUI_ICON_SCROLLBAR_BUTTON);
163
164			size_t top = 2 * lineHeight;
165			y = menu->index * (y - top - 16) / GUIMenuItemListSize(&menu->items);
166			GUIFontDrawIcon(params->font, params->width - 8, top + y, GUI_ALIGN_HCENTER | GUI_ALIGN_TOP, GUI_ORIENT_0, 0xFFFFFFFF, GUI_ICON_SCROLLBAR_THUMB);
167		}
168
169		GUIDrawBattery(params);
170		GUIDrawClock(params);
171
172		if (cursor != GUI_CURSOR_NOT_PRESENT) {
173			GUIFontDrawIcon(params->font, cx, cy, GUI_ALIGN_HCENTER | GUI_ALIGN_TOP, GUI_ORIENT_0, 0xFFFFFFFF, GUI_ICON_CURSOR);
174		}
175
176		if (params->guiFinish) {
177			params->guiFinish();
178		}
179		params->drawEnd();
180	}
181	return GUI_MENU_EXIT_CANCEL;
182}
183
184enum GUICursorState GUIPollCursor(struct GUIParams* params, unsigned* x, unsigned* y) {
185	if (!params->pollCursor) {
186		return GUI_CURSOR_NOT_PRESENT;
187	}
188	enum GUICursorState state = params->pollCursor(x, y);
189	if (params->cursorState == GUI_CURSOR_DOWN) {
190		int dragX = *x - params->cx;
191		int dragY = *y - params->cy;
192		if (dragX * dragX + dragY * dragY > 25) {
193			params->cursorState = GUI_CURSOR_DRAGGING;
194			return GUI_CURSOR_DRAGGING;
195		}
196		if (state == GUI_CURSOR_UP || state == GUI_CURSOR_NOT_PRESENT) {
197			params->cursorState = GUI_CURSOR_UP;
198			return GUI_CURSOR_CLICKED;
199		}
200	} else {
201		params->cx = *x;
202		params->cy = *y;
203	}
204	if (params->cursorState == GUI_CURSOR_DRAGGING) {
205		if (state == GUI_CURSOR_UP || state == GUI_CURSOR_NOT_PRESENT) {
206			params->cursorState = GUI_CURSOR_UP;
207			return GUI_CURSOR_UP;
208		}
209		return GUI_CURSOR_DRAGGING;
210	}
211	params->cursorState = state;
212	return params->cursorState;
213}
214
215void GUIInvalidateKeys(struct GUIParams* params) {
216	for (int i = 0; i < GUI_INPUT_MAX; ++i) {
217		params->inputHistory[i] = 0;
218	}
219}
220
221void GUIDrawBattery(struct GUIParams* params) {
222	if (!params->batteryState) {
223		return;
224	}
225	int state = params->batteryState();
226	uint32_t color = 0xFF000000;
227	if (state == (BATTERY_CHARGING | BATTERY_FULL)) {
228		color |= 0xFFC060;
229	} else if (state & BATTERY_CHARGING) {
230		color |= 0x60FF60;
231	} else if (state >= BATTERY_HALF) {
232		color |= 0xFFFFFF;
233	} else if (state == BATTERY_LOW) {
234		color |= 0x30FFFF;
235	} else {
236		color |= 0x3030FF;
237	}
238
239	enum GUIIcon batteryIcon;
240	switch (state & ~BATTERY_CHARGING) {
241	case BATTERY_EMPTY:
242		batteryIcon = GUI_ICON_BATTERY_EMPTY;
243		break;
244	case BATTERY_LOW:
245		batteryIcon = GUI_ICON_BATTERY_LOW;
246		break;
247	case BATTERY_HALF:
248		batteryIcon = GUI_ICON_BATTERY_HALF;
249		break;
250	case BATTERY_HIGH:
251		batteryIcon = GUI_ICON_BATTERY_HIGH;
252		break;
253	case BATTERY_FULL:
254		batteryIcon = GUI_ICON_BATTERY_FULL;
255		break;
256	default:
257		batteryIcon = GUI_ICON_BATTERY_EMPTY;
258		break;
259	}
260
261	GUIFontDrawIcon(params->font, params->width, 0, GUI_ALIGN_RIGHT, GUI_ORIENT_0, color, batteryIcon);
262}
263
264void GUIDrawClock(struct GUIParams* params) {
265	char buffer[32];
266	time_t t = time(0);
267	struct tm tm;
268	localtime_r(&t, &tm);
269	strftime(buffer, sizeof(buffer), "%H:%M:%S", &tm);
270	GUIFontPrint(params->font, params->width / 2, GUIFontHeight(params->font), GUI_ALIGN_HCENTER, 0xFFFFFFFF, buffer);
271}