1 /*
2 	C-Dogs SDL
3 	A port of the legendary (and fun) action/arcade cdogs.
4 	Copyright (C) 1995 Ronny Wester
5 	Copyright (C) 2003 Jeremy Chin
6 	Copyright (C) 2003-2007 Lucas Martin-King
7 
8 	This program is free software; you can redistribute it and/or modify
9 	it under the terms of the GNU General Public License as published by
10 	the Free Software Foundation; either version 2 of the License, or
11 	(at your option) any later version.
12 
13 	This program is distributed in the hope that it will be useful,
14 	but WITHOUT ANY WARRANTY; without even the implied warranty of
15 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 	GNU General Public License for more details.
17 
18 	You should have received a copy of the GNU General Public License
19 	along with this program; if not, write to the Free Software
20 	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21 
22 	This file incorporates work covered by the following copyright and
23 	permission notice:
24 
25 	Copyright (c) 2013-2014, 2016-2021 Cong Xu
26 	All rights reserved.
27 
28 	Redistribution and use in source and binary forms, with or without
29 	modification, are permitted provided that the following conditions are met:
30 
31 	Redistributions of source code must retain the above copyright notice, this
32 	list of conditions and the following disclaimer.
33 	Redistributions in binary form must reproduce the above copyright notice,
34 	this list of conditions and the following disclaimer in the documentation
35 	and/or other materials provided with the distribution.
36 
37 	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
38 	AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
39 	IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
40 	ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
41 	LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
42 	CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
43 	SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
44 	INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
45 	CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
46 	ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
47 	POSSIBILITY OF SUCH DAMAGE.
48 */
49 #include "menu.h"
50 
51 #include <assert.h>
52 
53 #include <cdogs/draw/drawtools.h>
54 #include <cdogs/font.h>
55 #include <cdogs/gamedata.h>
56 #include <cdogs/grafx_bg.h>
57 #include <cdogs/log.h>
58 #include <cdogs/mission.h>
59 #include <cdogs/music.h>
60 #include <cdogs/pic_manager.h>
61 #include <cdogs/sounds.h>
62 #include <cdogs/utils.h>
63 
64 #define MS_CENTER_X(ms, w) CENTER_X((ms).pos, (ms).size, w)
65 #define MS_CENTER_Y(ms, h) CENTER_Y((ms).pos, (ms).size, h)
66 
MenuSystemInit(MenuSystem * ms,EventHandlers * handlers,GraphicsDevice * graphics,struct vec2i pos,struct vec2i size)67 void MenuSystemInit(
68 	MenuSystem *ms, EventHandlers *handlers, GraphicsDevice *graphics,
69 	struct vec2i pos, struct vec2i size)
70 {
71 	memset(ms, 0, sizeof *ms);
72 	ms->root = ms->current = NULL;
73 	CArrayInit(&ms->exitTypes, sizeof(menu_type_e));
74 	CArrayInit(&ms->customDisplayFuncs, sizeof(MenuCustomDisplayFunc));
75 	ms->handlers = handlers;
76 	ms->graphics = graphics;
77 	ms->pos = pos;
78 	ms->size = size;
79 	ms->align = MENU_ALIGN_CENTER;
80 }
81 
82 static void MenuTerminate(menu_t *menu);
83 
MenuSystemTerminate(MenuSystem * ms)84 void MenuSystemTerminate(MenuSystem *ms)
85 {
86 	MenuTerminate(ms->root);
87 	CFREE(ms->root);
88 	CArrayTerminate(&ms->exitTypes);
89 	CArrayTerminate(&ms->customDisplayFuncs);
90 	memset(ms, 0, sizeof *ms);
91 }
92 
MenuSetCreditsDisplayer(MenuSystem * menu,credits_displayer_t * creditsDisplayer)93 void MenuSetCreditsDisplayer(
94 	MenuSystem *menu, credits_displayer_t *creditsDisplayer)
95 {
96 	menu->creditsDisplayer = creditsDisplayer;
97 }
98 
MenuHasExitType(MenuSystem * menu,menu_type_e exitType)99 int MenuHasExitType(MenuSystem *menu, menu_type_e exitType)
100 {
101 	CA_FOREACH(menu_type_e, m, menu->exitTypes)
102 	if (*m == exitType)
103 	{
104 		return 1;
105 	}
106 	CA_FOREACH_END()
107 	return 0;
108 }
109 
MenuAddExitType(MenuSystem * menu,menu_type_e exitType)110 void MenuAddExitType(MenuSystem *menu, menu_type_e exitType)
111 {
112 	if (MenuHasExitType(menu, exitType))
113 	{
114 		return;
115 	}
116 	CArrayPushBack(&menu->exitTypes, &exitType);
117 }
118 
MenuSystemAddCustomDisplay(MenuSystem * ms,MenuDisplayFunc func,void * data)119 void MenuSystemAddCustomDisplay(
120 	MenuSystem *ms, MenuDisplayFunc func, void *data)
121 {
122 	MenuCustomDisplayFunc cdf;
123 	cdf.Func = func;
124 	cdf.Data = data;
125 	CArrayPushBack(&ms->customDisplayFuncs, &cdf);
126 }
127 
MenuIsExit(MenuSystem * ms)128 int MenuIsExit(MenuSystem *ms)
129 {
130 	return ms->current == NULL || MenuHasExitType(ms, ms->current->type);
131 }
132 
133 void MenuProcessChangeKey(menu_t *menu);
134 
MenuTypeHasSubMenus(const menu_type_e type)135 static bool MenuTypeHasSubMenus(const menu_type_e type)
136 {
137 	return type == MENU_TYPE_NORMAL || type == MENU_TYPE_OPTIONS;
138 }
139 
SubmenuGetSize(const MenuSystem * ms,const menu_t * menu,const int idx)140 static struct vec2i SubmenuGetSize(
141 	const MenuSystem *ms, const menu_t *menu, const int idx)
142 {
143 	const menu_t *subMenu = CArrayGet(&menu->u.normal.subMenus, idx);
144 	int maxWidth = 0;
145 	CA_FOREACH(const menu_t, subMenu2, menu->u.normal.subMenus)
146 	const int width = FontStrW(subMenu2->name);
147 	if (width > maxWidth)
148 	{
149 		maxWidth = width;
150 	}
151 	CA_FOREACH_END()
152 	// Limit max width if it is larger than the menu system size
153 	maxWidth = MIN(ms->size.x, maxWidth);
154 	// Add extra width for options menus
155 	switch (subMenu->type)
156 	{
157 	case MENU_TYPE_SET_OPTION_RANGE:
158 	case MENU_TYPE_SET_OPTION_SEED:					  // fallthrough
159 	case MENU_TYPE_SET_OPTION_UP_DOWN_VOID_FUNC_VOID: // fallthrough
160 	case MENU_TYPE_SET_OPTION_RANGE_GET_SET:		  // fallthrough
161 		switch (subMenu->u.option.displayStyle)
162 		{
163 		case MENU_OPTION_DISPLAY_STYLE_NONE:
164 			// Do nothing
165 			break;
166 		case MENU_OPTION_DISPLAY_STYLE_STR_FUNC:
167 		case MENU_OPTION_DISPLAY_STYLE_INT_TO_STR_FUNC: // fallthrough
168 			maxWidth += 80;
169 			break;
170 		default:
171 			CASSERT(false, "unknown menu display type");
172 			break;
173 		}
174 		break;
175 	case MENU_TYPE_SET_OPTION_TOGGLE:
176 	case MENU_TYPE_SET_OPTION_CHANGE_KEY: // fallthrough
177 		maxWidth += 80;
178 		break;
179 	default:
180 		// do nothing
181 		break;
182 	}
183 
184 	return svec2i(maxWidth, FontStrH(subMenu->name));
185 }
MenuGetSubmenuBounds(const MenuSystem * ms,const int idx)186 static Rect2i MenuGetSubmenuBounds(const MenuSystem *ms, const int idx)
187 {
188 	const menu_t *menu = ms->current;
189 	if (menu == NULL || !MenuTypeHasSubMenus(menu->type))
190 	{
191 		return Rect2iZero();
192 	}
193 	// Calculate first/last indices
194 	const int maxItems = menu->u.normal.maxItems;
195 	const int iStart = maxItems > 0 ? menu->u.normal.scroll : 0;
196 	if (idx < iStart)
197 	{
198 		return Rect2iZero();
199 	}
200 	const int maxIEnd =
201 		MIN(maxItems > 0 ? iStart + maxItems : 99,
202 			(int)menu->u.normal.subMenus.size);
203 	int numMenuLines = 0;
204 	// Count the number of menu items that can fit
205 	// This is to account for multi-line items
206 	int iEnd;
207 	for (iEnd = iStart; iEnd < maxIEnd; iEnd++)
208 	{
209 		const menu_t *subMenu = CArrayGet(&menu->u.normal.subMenus, iEnd);
210 		const int numLines = FontStrNumLines(subMenu->name);
211 		if (menu->u.normal.maxItems != 0 &&
212 			numMenuLines + numLines > menu->u.normal.maxItems)
213 		{
214 			break;
215 		}
216 		numMenuLines += numLines;
217 	}
218 	if (iEnd <= idx)
219 	{
220 		return Rect2iZero();
221 	}
222 
223 	int maxWidth = 0;
224 	CA_FOREACH(const menu_t, subMenu, menu->u.normal.subMenus)
225 	const int width = FontStrW(subMenu->name);
226 	if (width > maxWidth)
227 	{
228 		maxWidth = width;
229 	}
230 	CA_FOREACH_END()
231 	// Limit max width if it is larger than the menu system size
232 	maxWidth = MIN(ms->size.x, maxWidth);
233 
234 	struct vec2i pos;
235 	const bool isCentered = menu->type == MENU_TYPE_NORMAL;
236 	switch (ms->align)
237 	{
238 	case MENU_ALIGN_CENTER:
239 		pos.x = MS_CENTER_X(*ms, maxWidth);
240 		if (!isCentered)
241 		{
242 			pos.x -= 20;
243 		}
244 		break;
245 	case MENU_ALIGN_LEFT:
246 		pos.x = ms->pos.x;
247 		break;
248 	default:
249 		CASSERT(false, "unknown alignment");
250 		return Rect2iZero();
251 	}
252 
253 	pos.y = MS_CENTER_Y(*ms, numMenuLines * FontH());
254 	for (int i = iStart; i < idx; i++)
255 	{
256 		const struct vec2i size = SubmenuGetSize(ms, menu, i);
257 		pos.y += size.y;
258 	}
259 	return Rect2iNew(pos, SubmenuGetSize(ms, menu, idx));
260 }
261 
262 static GameLoopResult DefaultMenuUpdate(GameLoopData *data, LoopRunner *l);
263 static void DefaultMenuDraw(GameLoopData *data);
MenuLoop(MenuSystem * menu)264 GameLoopData *MenuLoop(MenuSystem *menu)
265 {
266 	return GameLoopDataNew(
267 		menu, NULL, NULL, NULL, NULL, DefaultMenuUpdate, DefaultMenuDraw);
268 }
DefaultMenuUpdate(GameLoopData * data,LoopRunner * l)269 static GameLoopResult DefaultMenuUpdate(GameLoopData *data, LoopRunner *l)
270 {
271 	UNUSED(l);
272 	return MenuUpdate(data->Data);
273 }
DefaultMenuDraw(GameLoopData * data)274 static void DefaultMenuDraw(GameLoopData *data)
275 {
276 	MenuDraw(data->Data);
277 }
MenuUpdate(MenuSystem * ms)278 GameLoopResult MenuUpdate(MenuSystem *ms)
279 {
280 	if (ms->current->type == MENU_TYPE_OPTIONS &&
281 		ms->current->u.normal.changeKeyMenu != NULL)
282 	{
283 		MenuProcessChangeKey(ms->current);
284 	}
285 	else
286 	{
287 		const int cmd = GetMenuCmd(ms->handlers);
288 		if (cmd)
289 		{
290 			MenuProcessCmd(ms, cmd);
291 		}
292 		else if (MouseHasMoved(&ms->handlers->mouse))
293 		{
294 			// Get mouse position and change menu
295 			menu_t *menu = ms->current;
296 			if (menu != NULL && MenuTypeHasSubMenus(menu->type))
297 			{
298 				for (int i = 0; i < (int)menu->u.normal.subMenus.size; i++)
299 				{
300 					const Rect2i bounds = MenuGetSubmenuBounds(ms, i);
301 					if (Rect2iIsInside(bounds, ms->handlers->mouse.currentPos))
302 					{
303 						if (menu->u.normal.index != i)
304 						{
305 							const menu_t *subMenu =
306 								CArrayGet(&menu->u.normal.subMenus, i);
307 							if (!subMenu->isDisabled)
308 							{
309 								menu->u.normal.index = i;
310 								MenuPlaySound(MENU_SOUND_SWITCH);
311 							}
312 						}
313 						break;
314 					}
315 				}
316 			}
317 		}
318 	}
319 	// Check if anyone pressed escape, or we need a hard exit
320 	int cmds[MAX_LOCAL_PLAYERS];
321 	memset(cmds, 0, sizeof cmds);
322 	GetPlayerCmds(ms->handlers, &cmds);
323 	const bool aborted =
324 		ms->allowAborts &&
325 		EventIsEscape(ms->handlers, cmds, GetMenuCmd(ms->handlers));
326 	if (aborted || ms->handlers->HasQuit)
327 	{
328 		ms->hasAbort = true;
329 		return UPDATE_RESULT_OK;
330 	}
331 	if (MenuIsExit(ms))
332 	{
333 		return UPDATE_RESULT_OK;
334 	}
335 	if (ms->current->customPostUpdateFunc)
336 	{
337 		ms->current->customPostUpdateFunc(
338 			ms->current, ms->current->customPostUpdateData);
339 	}
340 	return UPDATE_RESULT_DRAW;
341 }
MenuDraw(const MenuSystem * ms)342 void MenuDraw(const MenuSystem *ms)
343 {
344 	BlitClearBuf(ms->graphics);
345 	ShowControls();
346 	MenuDisplay(ms);
347 	BlitUpdateFromBuf(ms->graphics, ms->graphics->screen);
348 }
349 
MenuReset(MenuSystem * menu)350 void MenuReset(MenuSystem *menu)
351 {
352 	menu->current = menu->root;
353 }
354 
MoveIndexToNextEnabledSubmenu(menu_t * menu,const bool isDown)355 static void MoveIndexToNextEnabledSubmenu(menu_t *menu, const bool isDown)
356 {
357 	if (menu->u.normal.index >= (int)menu->u.normal.subMenus.size)
358 	{
359 		menu->u.normal.index = (int)menu->u.normal.subMenus.size - 1;
360 	}
361 	const int firstIndex = menu->u.normal.index;
362 	bool isFirst = true;
363 	// Move the selection to the next non-disabled submenu
364 	for (;;)
365 	{
366 		const menu_t *currentSubmenu =
367 			CArrayGet(&menu->u.normal.subMenus, menu->u.normal.index);
368 		if (!currentSubmenu->isDisabled)
369 		{
370 			break;
371 		}
372 		if (menu->u.normal.index == firstIndex && !isFirst)
373 		{
374 			break;
375 		}
376 		isFirst = false;
377 		if (isDown)
378 		{
379 			menu->u.normal.index++;
380 			if (menu->u.normal.index == (int)menu->u.normal.subMenus.size)
381 			{
382 				menu->u.normal.index = 0;
383 			}
384 		}
385 		else
386 		{
387 			menu->u.normal.index--;
388 			if (menu->u.normal.index == -1)
389 			{
390 				menu->u.normal.index = (int)menu->u.normal.subMenus.size - 1;
391 			}
392 		}
393 	}
394 }
395 
MenuDisableSubmenu(menu_t * menu,int idx)396 void MenuDisableSubmenu(menu_t *menu, int idx)
397 {
398 	menu_t *subMenu = CArrayGet(&menu->u.normal.subMenus, idx);
399 	MenuSetDisabled(subMenu, true);
400 }
MenuEnableSubmenu(menu_t * menu,int idx)401 void MenuEnableSubmenu(menu_t *menu, int idx)
402 {
403 	menu_t *subMenu = CArrayGet(&menu->u.normal.subMenus, idx);
404 	MenuSetDisabled(subMenu, false);
405 }
MenuSetDisabled(menu_t * menu,const bool isDisabled)406 void MenuSetDisabled(menu_t *menu, const bool isDisabled)
407 {
408 	menu->isDisabled = isDisabled;
409 	if (isDisabled && menu->parentMenu != NULL)
410 	{
411 		MoveIndexToNextEnabledSubmenu(menu->parentMenu, true);
412 	}
413 }
414 
MenuGetSubmenuByName(menu_t * menu,const char * name)415 menu_t *MenuGetSubmenuByName(menu_t *menu, const char *name)
416 {
417 	CA_FOREACH(menu_t, subMenu, menu->u.normal.subMenus)
418 	if (strcmp(subMenu->name, name) == 0)
419 	{
420 		return subMenu;
421 	}
422 	CA_FOREACH_END()
423 	return NULL;
424 }
425 
MenuGetNumMenuItemsShown(const menu_t * menu)426 int MenuGetNumMenuItemsShown(const menu_t *menu)
427 {
428 	CASSERT(MenuTypeHasSubMenus(menu->type), "invalid menu type");
429 	return menu->u.normal.maxItems > 0 ? MIN(menu->u.normal.maxItems,
430 											 (int)menu->u.normal.subMenus.size)
431 									   : (int)menu->u.normal.subMenus.size;
432 }
433 
ShowControls(void)434 void ShowControls(void)
435 {
436 	FontOpts opts = FontOptsNew();
437 	opts.HAlign = ALIGN_CENTER;
438 	opts.VAlign = ALIGN_END;
439 	opts.Area = gGraphicsDevice.cachedConfig.Res;
440 	opts.Pad.y = 10;
441 #ifdef __GCWZERO__
442 	FontStrOpt(
443 		"(use joystick or D pad + START + SELECT)", svec2i_zero(), opts);
444 #else
445 	FontStrOpt(
446 		"(use joystick 1 or arrow keys + Enter/Backspace)", svec2i_zero(),
447 		opts);
448 #endif
449 }
450 
DisplayMenuItem(GraphicsDevice * g,const Rect2i bounds,const char * s,const bool selected,const bool isDisabled,const color_t color)451 struct vec2i DisplayMenuItem(
452 	GraphicsDevice *g, const Rect2i bounds, const char *s, const bool selected,
453 	const bool isDisabled, const color_t color)
454 {
455 	if (isDisabled)
456 	{
457 		color_t dark = {64, 64, 64, 255};
458 		return FontStrMask(s, bounds.Pos, dark);
459 	}
460 	if (selected)
461 	{
462 		const color_t bg = {0, 255, 255, 64};
463 		// Add 1px padding
464 		const struct vec2i bgPos = svec2i_subtract(bounds.Pos, svec2i_one());
465 		const struct vec2i bgSize = svec2i_add(bounds.Size, svec2i(2, 2));
466 		DrawRectangle(g, bgPos, bgSize, bg, true);
467 		return FontStrMask(s, bounds.Pos, colorRed);
468 	}
469 	if (!ColorEquals(color, colorTransparent))
470 	{
471 		return FontStrMask(s, bounds.Pos, color);
472 	}
473 	return FontStr(s, bounds.Pos);
474 }
475 
MenuCreate(const char * name,menu_type_e type)476 menu_t *MenuCreate(const char *name, menu_type_e type)
477 {
478 	menu_t *menu;
479 	CCALLOC(menu, sizeof(menu_t));
480 	CSTRDUP(menu->name, name);
481 	menu->type = type;
482 	menu->parentMenu = NULL;
483 	menu->enterSound = MENU_SOUND_ENTER;
484 	return menu;
485 }
486 
MenuCreateNormal(const char * name,const char * title,menu_type_e type,int displayItems)487 menu_t *MenuCreateNormal(
488 	const char *name, const char *title, menu_type_e type, int displayItems)
489 {
490 	menu_t *menu = MenuCreate(name, type);
491 	strcpy(menu->u.normal.title, title);
492 	menu->u.normal.isSubmenusAlt = false;
493 	menu->u.normal.displayItems = displayItems;
494 	menu->u.normal.changeKeyMenu = NULL;
495 	menu->u.normal.index = 0;
496 	menu->u.normal.scroll = 0;
497 	menu->u.normal.maxItems = 0;
498 	menu->u.normal.align = MENU_ALIGN_LEFT;
499 	menu->u.normal.quitMenuIndex = -1;
500 	CArrayInit(&menu->u.normal.subMenus, sizeof(menu_t));
501 	return menu;
502 }
503 
UpdateSubmenuParentPtrs(menu_t * menu)504 static void UpdateSubmenuParentPtrs(menu_t *menu)
505 {
506 	CA_FOREACH(menu_t, subMenu, menu->u.normal.subMenus)
507 	subMenu->parentMenu = menu;
508 	if (MenuTypeHasSubMenus(subMenu->type))
509 	{
510 		UpdateSubmenuParentPtrs(subMenu);
511 	}
512 	CA_FOREACH_END()
513 }
MenuAddSubmenu(menu_t * menu,menu_t * subMenu)514 menu_t *MenuAddSubmenu(menu_t *menu, menu_t *subMenu)
515 {
516 	CArrayPushBack(&menu->u.normal.subMenus, subMenu);
517 	if (subMenu->type == MENU_TYPE_QUIT)
518 	{
519 		menu->u.normal.quitMenuIndex = (int)menu->u.normal.subMenus.size - 1;
520 	}
521 	CFREE(subMenu);
522 
523 	// update all parent pointers, in child menus
524 	UpdateSubmenuParentPtrs(menu);
525 
526 	// move cursor in case first menu item(s) are disabled
527 	MoveIndexToNextEnabledSubmenu(menu, true);
528 
529 	return CArrayGet(
530 		&menu->u.normal.subMenus, menu->u.normal.subMenus.size - 1);
531 }
532 
MenuSetPostInputFunc(menu_t * menu,MenuPostInputFunc func,void * data)533 void MenuSetPostInputFunc(menu_t *menu, MenuPostInputFunc func, void *data)
534 {
535 	menu->customPostInputFunc = func;
536 	menu->customPostInputData = data;
537 }
538 
MenuSetPostEnterFunc(menu_t * menu,MenuFunc func,void * data,const bool isDynamicData)539 void MenuSetPostEnterFunc(
540 	menu_t *menu, MenuFunc func, void *data, const bool isDynamicData)
541 {
542 	menu->customPostEnterFunc = func;
543 	menu->customPostEnterData = data;
544 	menu->isCustomPostEnterDataDynamic = isDynamicData;
545 }
546 
MenuSetPostUpdateFunc(menu_t * menu,MenuFunc func,void * data,const bool isDynamicData)547 void MenuSetPostUpdateFunc(
548 	menu_t *menu, MenuFunc func, void *data, const bool isDynamicData)
549 {
550 	menu->customPostUpdateFunc = func;
551 	menu->customPostUpdateData = data;
552 	menu->isCustomPostUpdateDataDynamic = isDynamicData;
553 }
554 
MenuSetCustomDisplay(menu_t * menu,MenuDisplayFunc func,const void * data)555 void MenuSetCustomDisplay(menu_t *menu, MenuDisplayFunc func, const void *data)
556 {
557 	menu->customDisplayFunc = func;
558 	menu->customDisplayData = data;
559 }
560 
MenuAddConfigOptionsItem(menu_t * menu,Config * c)561 void MenuAddConfigOptionsItem(menu_t *menu, Config *c)
562 {
563 	char nameBuf[256];
564 	CASSERT(strlen(c->Name) < sizeof nameBuf, "buffer too small");
565 	CamelToTitle(nameBuf, c->Name);
566 	switch (c->Type)
567 	{
568 	case CONFIG_TYPE_STRING:
569 		CASSERT(false, "Unimplemented");
570 		break;
571 	case CONFIG_TYPE_INT:
572 		MenuAddSubmenu(
573 			menu,
574 			MenuCreateOptionRange(
575 				nameBuf, (int *)&c->u.Int.Value, c->u.Int.Min, c->u.Int.Max,
576 				c->u.Int.Increment, MENU_OPTION_DISPLAY_STYLE_INT_TO_STR_FUNC,
577 				(void (*)(void))c->u.Int.IntToStr));
578 		break;
579 	case CONFIG_TYPE_FLOAT:
580 		CASSERT(false, "Unimplemented");
581 		break;
582 	case CONFIG_TYPE_BOOL:
583 		MenuAddSubmenu(
584 			menu, MenuCreateOptionToggle(nameBuf, &c->u.Bool.Value));
585 		break;
586 	case CONFIG_TYPE_ENUM:
587 		MenuAddSubmenu(
588 			menu,
589 			MenuCreateOptionRange(
590 				nameBuf, (int *)&c->u.Enum.Value, c->u.Enum.Min, c->u.Enum.Max,
591 				1, MENU_OPTION_DISPLAY_STYLE_INT_TO_STR_FUNC,
592 				(void (*)(void))c->u.Enum.EnumToStr));
593 		break;
594 	case CONFIG_TYPE_GROUP:
595 		// Do nothing
596 		break;
597 	default:
598 		CASSERT(false, "Unknown config type");
599 		break;
600 	}
601 }
602 
MenuCreateOptionToggle(const char * name,bool * config)603 menu_t *MenuCreateOptionToggle(const char *name, bool *config)
604 {
605 	menu_t *menu = MenuCreate(name, MENU_TYPE_SET_OPTION_TOGGLE);
606 	menu->u.option.uHook.optionToggle = config;
607 	menu->u.option.displayStyle = MENU_OPTION_DISPLAY_STYLE_NONE;
608 	return menu;
609 }
610 
MenuCreateOptionRange(const char * name,int * config,int low,int high,int increment,menu_option_display_style_e style,void (* func)(void))611 menu_t *MenuCreateOptionRange(
612 	const char *name, int *config, int low, int high, int increment,
613 	menu_option_display_style_e style, void (*func)(void))
614 {
615 	menu_t *menu = MenuCreate(name, MENU_TYPE_SET_OPTION_RANGE);
616 	menu->u.option.uHook.optionRange.option = config;
617 	menu->u.option.uHook.optionRange.low = low;
618 	menu->u.option.uHook.optionRange.high = high;
619 	menu->u.option.uHook.optionRange.increment = increment;
620 	menu->u.option.displayStyle = style;
621 	if (style == MENU_OPTION_DISPLAY_STYLE_STR_FUNC)
622 	{
623 		menu->u.option.uFunc.str = (char *(*)(void))func;
624 	}
625 	else if (style == MENU_OPTION_DISPLAY_STYLE_INT_TO_STR_FUNC)
626 	{
627 		menu->u.option.uFunc.intToStr = (const char *(*)(int))func;
628 	}
629 	return menu;
630 }
631 
MenuCreateOptionSeed(const char * name,unsigned int * seed)632 menu_t *MenuCreateOptionSeed(const char *name, unsigned int *seed)
633 {
634 	menu_t *menu = MenuCreate(name, MENU_TYPE_SET_OPTION_SEED);
635 	menu->u.option.uHook.seed = seed;
636 	menu->u.option.displayStyle = MENU_OPTION_DISPLAY_STYLE_INT_TO_STR_FUNC;
637 	return menu;
638 }
639 
MenuCreateOptionUpDownFunc(const char * name,void (* upFunc)(void),void (* downFunc)(void),menu_option_display_style_e style,char * (* strFunc)(void))640 menu_t *MenuCreateOptionUpDownFunc(
641 	const char *name, void (*upFunc)(void), void (*downFunc)(void),
642 	menu_option_display_style_e style, char *(*strFunc)(void))
643 {
644 	menu_t *menu =
645 		MenuCreate(name, MENU_TYPE_SET_OPTION_UP_DOWN_VOID_FUNC_VOID);
646 	menu->u.option.uHook.upDownFuncs.upFunc = upFunc;
647 	menu->u.option.uHook.upDownFuncs.downFunc = downFunc;
648 	menu->u.option.displayStyle = style;
649 	menu->u.option.uFunc.str = strFunc;
650 	return menu;
651 }
652 
MenuCreateVoidFunc(const char * name,void (* func)(void *),void * data)653 menu_t *MenuCreateVoidFunc(const char *name, void (*func)(void *), void *data)
654 {
655 	menu_t *menu = MenuCreate(name, MENU_TYPE_VOID_FUNC);
656 	menu->u.option.uHook.voidFunc.func = func;
657 	menu->u.option.uHook.voidFunc.data = data;
658 	menu->u.option.displayStyle = MENU_OPTION_DISPLAY_STYLE_NONE;
659 	return menu;
660 }
661 
MenuCreateOptionRangeGetSet(const char * name,int (* getFunc)(void),void (* setFunc)(int),int low,int high,int increment,menu_option_display_style_e style,void (* func)(void))662 menu_t *MenuCreateOptionRangeGetSet(
663 	const char *name, int (*getFunc)(void), void (*setFunc)(int), int low,
664 	int high, int increment, menu_option_display_style_e style,
665 	void (*func)(void))
666 {
667 	menu_t *menu = MenuCreate(name, MENU_TYPE_SET_OPTION_RANGE_GET_SET);
668 	menu->u.option.uHook.optionRangeGetSet.getFunc = getFunc;
669 	menu->u.option.uHook.optionRangeGetSet.setFunc = setFunc;
670 	menu->u.option.uHook.optionRangeGetSet.low = low;
671 	menu->u.option.uHook.optionRangeGetSet.high = high;
672 	menu->u.option.uHook.optionRangeGetSet.increment = increment;
673 	menu->u.option.displayStyle = style;
674 	// TODO: refactor saving of function based on style
675 	if (style == MENU_OPTION_DISPLAY_STYLE_INT_TO_STR_FUNC)
676 	{
677 		menu->u.option.uFunc.intToStr = (const char *(*)(int))func;
678 	}
679 	return menu;
680 }
681 
MenuCreateSeparator(const char * name)682 menu_t *MenuCreateSeparator(const char *name)
683 {
684 	menu_t *menu = MenuCreate(name, MENU_TYPE_BASIC);
685 	menu->isDisabled = 1;
686 	return menu;
687 }
688 
MenuCreateBack(const char * name)689 menu_t *MenuCreateBack(const char *name)
690 {
691 	return MenuCreate(name, MENU_TYPE_BACK);
692 }
693 
MenuCreateReturn(const char * name,int returnCode)694 menu_t *MenuCreateReturn(const char *name, int returnCode)
695 {
696 	menu_t *menu = MenuCreate(name, MENU_TYPE_RETURN);
697 	menu->u.returnCode = returnCode;
698 	return menu;
699 }
700 
MenuCreateCustom(const char * name,MenuDisplayFunc displayFunc,MenuInputFunc inputFunc,void * data)701 menu_t *MenuCreateCustom(
702 	const char *name, MenuDisplayFunc displayFunc, MenuInputFunc inputFunc,
703 	void *data)
704 {
705 	menu_t *menu = MenuCreate(name, MENU_TYPE_CUSTOM);
706 	menu->u.customData.displayFunc = displayFunc;
707 	menu->u.customData.inputFunc = inputFunc;
708 	menu->u.customData.data = data;
709 	return menu;
710 }
711 
712 static void MenuDisplayItems(const MenuSystem *ms);
713 static void MenuDisplaySubmenus(const MenuSystem *ms);
MenuDisplay(const MenuSystem * ms)714 void MenuDisplay(const MenuSystem *ms)
715 {
716 	const menu_t *menu = ms->current;
717 	if (menu != NULL)
718 	{
719 		if (menu->type == MENU_TYPE_CUSTOM)
720 		{
721 			menu->u.customData.displayFunc(
722 				menu, ms->graphics, ms->pos, ms->size,
723 				menu->u.customData.data);
724 		}
725 		else
726 		{
727 			MenuDisplayItems(ms);
728 
729 			if (strlen(menu->u.normal.title) != 0)
730 			{
731 				FontOpts opts = FontOptsNew();
732 				opts.HAlign = ALIGN_CENTER;
733 				opts.Area = ms->size;
734 				opts.Pad = svec2i(20, 20);
735 				FontStrOpt(menu->u.normal.title, ms->pos, opts);
736 			}
737 
738 			MenuDisplaySubmenus(ms);
739 		}
740 	}
741 	CA_FOREACH(MenuCustomDisplayFunc, cdf, ms->customDisplayFuncs)
742 	cdf->Func(NULL, ms->graphics, ms->pos, ms->size, cdf->Data);
743 	CA_FOREACH_END()
744 	if (menu != NULL && menu->customDisplayFunc)
745 	{
746 		menu->customDisplayFunc(
747 			menu, ms->graphics, ms->pos, ms->size, menu->customDisplayData);
748 	}
749 }
MenuDisplayItems(const MenuSystem * ms)750 static void MenuDisplayItems(const MenuSystem *ms)
751 {
752 	int d = ms->current->u.normal.displayItems;
753 	if ((d & MENU_DISPLAY_ITEMS_CREDITS) && ms->creditsDisplayer != NULL)
754 	{
755 		ShowCredits(ms->creditsDisplayer);
756 	}
757 	if (d & MENU_DISPLAY_ITEMS_AUTHORS)
758 	{
759 		const Pic *logo = PicManagerGetPic(&gPicManager, "logo");
760 		const struct vec2i pos = svec2i(
761 			MS_CENTER_X(*ms, logo->size.x), ms->pos.y + ms->size.y / 12);
762 		PicRender(
763 			logo, ms->graphics->gameWindow.renderer, pos, colorWhite, 0,
764 			svec2_one(), SDL_FLIP_NONE, Rect2iZero());
765 
766 		FontOpts opts = FontOptsNew();
767 		opts.HAlign = ALIGN_END;
768 		opts.Area = ms->size;
769 		opts.Pad = svec2i(20, 20);
770 		FontStrOpt("Version: " CDOGS_SDL_VERSION, ms->pos, opts);
771 	}
772 }
773 static int MenuOptionGetIntValue(const menu_t *menu);
MenuDisplaySubmenus(const MenuSystem * ms)774 static void MenuDisplaySubmenus(const MenuSystem *ms)
775 {
776 	const menu_t *menu = ms->current;
777 	if (!MenuTypeHasSubMenus(menu->type))
778 	{
779 		return;
780 	}
781 
782 	int maxWidth = 0;
783 	CA_FOREACH(const menu_t, subMenu, menu->u.normal.subMenus)
784 	const int width = FontStrW(subMenu->name);
785 	if (width > maxWidth)
786 	{
787 		maxWidth = width;
788 	}
789 	CA_FOREACH_END()
790 
791 	CA_FOREACH(const menu_t, subMenu, menu->u.normal.subMenus)
792 	const Rect2i bounds = MenuGetSubmenuBounds(ms, _ca_index);
793 	if (Rect2iIsZero(bounds))
794 	{
795 		continue;
796 	}
797 
798 	const int xOptions = bounds.Pos.x + maxWidth + 10;
799 	char nameBuf[512];
800 	if (subMenu->type == MENU_TYPE_NORMAL && subMenu->u.normal.isSubmenusAlt)
801 	{
802 		snprintf(nameBuf, sizeof(nameBuf), "%s " ARROW_RIGHT, subMenu->name);
803 	}
804 	else
805 	{
806 		snprintf(nameBuf, sizeof(nameBuf), "%s", subMenu->name);
807 	}
808 
809 	const bool isSelected = _ca_index == menu->u.normal.index;
810 	DisplayMenuItem(
811 		ms->graphics, bounds, nameBuf, isSelected,
812 		menu->isDisabled || subMenu->isDisabled, subMenu->color);
813 
814 	// display option value
815 	const int optionInt = MenuOptionGetIntValue(subMenu);
816 	const struct vec2i valuePos = svec2i(xOptions, bounds.Pos.y);
817 	const char *option = NULL;
818 	if (subMenu->type == MENU_TYPE_SET_OPTION_RANGE ||
819 		subMenu->type == MENU_TYPE_SET_OPTION_SEED ||
820 		subMenu->type == MENU_TYPE_SET_OPTION_UP_DOWN_VOID_FUNC_VOID ||
821 		subMenu->type == MENU_TYPE_SET_OPTION_RANGE_GET_SET)
822 	{
823 		switch (subMenu->u.option.displayStyle)
824 		{
825 		case MENU_OPTION_DISPLAY_STYLE_NONE:
826 			// Do nothing
827 			break;
828 		case MENU_OPTION_DISPLAY_STYLE_STR_FUNC:
829 			option = subMenu->u.option.uFunc.str();
830 			break;
831 		case MENU_OPTION_DISPLAY_STYLE_INT_TO_STR_FUNC:
832 			option = subMenu->u.option.uFunc.intToStr(optionInt);
833 			break;
834 		default:
835 			CASSERT(false, "unknown menu display type");
836 			break;
837 		}
838 	}
839 	else if (subMenu->type == MENU_TYPE_SET_OPTION_TOGGLE)
840 	{
841 		option = optionInt ? "Yes" : "No";
842 	}
843 	else if (subMenu->type == MENU_TYPE_SET_OPTION_CHANGE_KEY)
844 	{
845 		if (menu->u.normal.changeKeyMenu == subMenu)
846 		{
847 			option = "Press a key";
848 		}
849 		else
850 		{
851 			const int pi = subMenu->u.changeKey.playerIndex;
852 			const InputKeys *keys = &ms->handlers->keyboard.PlayerKeys[pi];
853 			const SDL_Scancode sc = KeyGet(keys, subMenu->u.changeKey.code);
854 			option = SDL_GetScancodeName(sc);
855 			if (sc == SDL_SCANCODE_UNKNOWN || option == NULL)
856 			{
857 				option = "Unset";
858 			}
859 		}
860 	}
861 	if (option != NULL)
862 	{
863 		char buf[256];
864 		if (isSelected)
865 		{
866 			sprintf(buf, ARROW_LEFT " %s " ARROW_RIGHT, option);
867 		}
868 		else
869 		{
870 			strcpy(buf, option);
871 		}
872 		FontStr(buf, valuePos);
873 	}
874 	CA_FOREACH_END()
875 }
876 
MenuPlaySound(MenuSound s)877 void MenuPlaySound(MenuSound s)
878 {
879 	switch (s)
880 	{
881 	case MENU_SOUND_ENTER:
882 		SoundPlay(&gSoundDevice, StrSound("menu_enter"));
883 		break;
884 	case MENU_SOUND_BACK:
885 		SoundPlay(&gSoundDevice, StrSound("menu_back"));
886 		break;
887 	case MENU_SOUND_SWITCH:
888 		SoundPlay(&gSoundDevice, StrSound("menu_switch"));
889 		break;
890 	case MENU_SOUND_START:
891 		SoundPlay(&gSoundDevice, StrSound("menu_start"));
892 		break;
893 	case MENU_SOUND_ERROR:
894 		SoundPlay(&gSoundDevice, StrSound("menu_error"));
895 		break;
896 	default:
897 		break;
898 	}
899 }
900 
901 static void MenuTerminateSubmenus(menu_t *menu);
MenuTerminate(menu_t * menu)902 static void MenuTerminate(menu_t *menu)
903 {
904 	if (menu == NULL)
905 	{
906 		return;
907 	}
908 	CFREE(menu->name);
909 	if (menu->isCustomPostUpdateDataDynamic)
910 	{
911 		CFREE(menu->customPostUpdateData);
912 	}
913 	if (menu->isCustomPostEnterDataDynamic)
914 	{
915 		CFREE(menu->customPostEnterData);
916 	}
917 	MenuTerminateSubmenus(menu);
918 }
MenuTerminateSubmenus(menu_t * menu)919 static void MenuTerminateSubmenus(menu_t *menu)
920 {
921 	if (!MenuTypeHasSubMenus(menu->type))
922 	{
923 		return;
924 	}
925 	CA_FOREACH(menu_t, subMenu, menu->u.normal.subMenus)
926 	MenuTerminate(subMenu);
927 	CA_FOREACH_END()
928 	CArrayTerminate(&menu->u.normal.subMenus);
929 }
930 
MenuClearSubmenus(menu_t * menu)931 void MenuClearSubmenus(menu_t *menu)
932 {
933 	if (!MenuTypeHasSubMenus(menu->type))
934 	{
935 		CASSERT(false, "attempt to clear submenus for invalid menu type");
936 		return;
937 	}
938 	MenuTerminateSubmenus(menu);
939 	CArrayInit(&menu->u.normal.subMenus, sizeof(menu_t));
940 }
941 
MenuOptionGetIntValue(const menu_t * menu)942 static int MenuOptionGetIntValue(const menu_t *menu)
943 {
944 	switch (menu->type)
945 	{
946 	case MENU_TYPE_SET_OPTION_TOGGLE:
947 		return *menu->u.option.uHook.optionToggle;
948 	case MENU_TYPE_SET_OPTION_RANGE:
949 		return *menu->u.option.uHook.optionRange.option;
950 	case MENU_TYPE_SET_OPTION_SEED:
951 		return (int)*menu->u.option.uHook.seed;
952 	case MENU_TYPE_SET_OPTION_RANGE_GET_SET:
953 		return menu->u.option.uHook.optionRangeGetSet.getFunc();
954 	default:
955 		return 0;
956 	}
957 }
958 
959 // returns menu to change to, NULL if no change
960 menu_t *MenuProcessEscCmd(menu_t *menu);
961 menu_t *MenuProcessButtonCmd(MenuSystem *ms, menu_t *menu, int cmd);
962 void MenuChangeIndex(menu_t *menu, int cmd);
963 
MenuProcessCmd(MenuSystem * ms,int cmd)964 void MenuProcessCmd(MenuSystem *ms, int cmd)
965 {
966 	menu_t *menu = ms->current;
967 	menu_t *menuToChange = NULL;
968 	if (cmd == CMD_ESC || (cmd & CMD_BUTTON2) ||
969 		((cmd & CMD_LEFT) && menu->u.normal.isSubmenusAlt))
970 	{
971 		menuToChange = MenuProcessEscCmd(menu);
972 		if (menuToChange != NULL)
973 		{
974 			MenuPlaySound(MENU_SOUND_BACK);
975 			ms->current = menuToChange;
976 			goto bail;
977 		}
978 	}
979 	if (menu->type == MENU_TYPE_CUSTOM)
980 	{
981 		if (menu->u.customData.inputFunc(cmd, menu->u.customData.data))
982 		{
983 			ms->current = menu->parentMenu;
984 			goto bail;
985 		}
986 	}
987 	else if (cmd != 0)
988 	{
989 		menuToChange = MenuProcessButtonCmd(ms, menu, cmd);
990 		if (menuToChange != NULL)
991 		{
992 			MenuPlaySound(menuToChange->enterSound);
993 			ms->current = menuToChange;
994 			goto bail;
995 		}
996 		MenuChangeIndex(menu, cmd);
997 	}
998 
999 bail:
1000 	if (menu->customPostInputFunc)
1001 	{
1002 		menu->customPostInputFunc(menu, cmd, menu->customPostInputData);
1003 	}
1004 	if (menuToChange && menuToChange->customPostEnterFunc)
1005 	{
1006 		menuToChange->customPostEnterFunc(
1007 			menuToChange, menuToChange->customPostEnterData);
1008 	}
1009 }
1010 
MenuProcessEscCmd(menu_t * menu)1011 menu_t *MenuProcessEscCmd(menu_t *menu)
1012 {
1013 	menu_t *menuToChange = NULL;
1014 	int quitMenuIndex = menu->u.normal.quitMenuIndex;
1015 	if (quitMenuIndex != -1)
1016 	{
1017 		if (menu->u.normal.index != quitMenuIndex)
1018 		{
1019 			MenuPlaySound(MENU_SOUND_SWITCH);
1020 			menu->u.normal.index = quitMenuIndex;
1021 		}
1022 		else if (menu->u.normal.subMenus.size > 0)
1023 		{
1024 			menuToChange = CArrayGet(&menu->u.normal.subMenus, quitMenuIndex);
1025 		}
1026 	}
1027 	else
1028 	{
1029 		menuToChange = menu->parentMenu;
1030 	}
1031 	return menuToChange;
1032 }
1033 
1034 void MenuActivate(MenuSystem *ms, menu_t *menu, int cmd);
1035 
MenuProcessButtonCmd(MenuSystem * ms,menu_t * menu,int cmd)1036 menu_t *MenuProcessButtonCmd(MenuSystem *ms, menu_t *menu, int cmd)
1037 {
1038 	if (AnyButton(cmd) || Left(cmd) || Right(cmd))
1039 	{
1040 		// Ignore if menu contains no submenus
1041 		if (menu->u.normal.subMenus.size == 0)
1042 		{
1043 			return NULL;
1044 		}
1045 		menu_t *subMenu =
1046 			CArrayGet(&menu->u.normal.subMenus, menu->u.normal.index);
1047 
1048 		// Only allow menu switching on button 1
1049 
1050 		switch (subMenu->type)
1051 		{
1052 		case MENU_TYPE_NORMAL:
1053 		case MENU_TYPE_OPTIONS:
1054 		case MENU_TYPE_CUSTOM:
1055 			if (subMenu->u.normal.isSubmenusAlt ? (cmd & CMD_RIGHT)
1056 												: (cmd & CMD_BUTTON1))
1057 			{
1058 				return subMenu;
1059 			}
1060 			break;
1061 		case MENU_TYPE_BACK:
1062 			if (cmd & CMD_BUTTON1)
1063 			{
1064 				return menu->parentMenu;
1065 			}
1066 			break;
1067 		case MENU_TYPE_QUIT:
1068 			if (cmd & CMD_BUTTON1)
1069 			{
1070 				return subMenu; // caller will check if subMenu type is QUIT
1071 			}
1072 			break;
1073 		case MENU_TYPE_RETURN:
1074 			if (cmd & CMD_BUTTON1)
1075 			{
1076 				return subMenu;
1077 			}
1078 			break;
1079 		default:
1080 			MenuActivate(ms, subMenu, cmd);
1081 			break;
1082 		}
1083 	}
1084 	return NULL;
1085 }
1086 
KeyAvailable(const SDL_Scancode key,const key_code_e code,const int playerIndex)1087 static bool KeyAvailable(
1088 	const SDL_Scancode key, const key_code_e code, const int playerIndex)
1089 {
1090 	if (key == SDL_SCANCODE_ESCAPE || key == SDL_SCANCODE_F9 ||
1091 		key == SDL_SCANCODE_F10)
1092 	{
1093 		return false;
1094 	}
1095 	if (key == SDL_SCANCODE_UNKNOWN)
1096 	{
1097 		return true;
1098 	}
1099 	if (key == (SDL_Scancode)ConfigGetInt(&gConfig, "Input.PlayerCodes0.map"))
1100 	{
1101 		return false;
1102 	}
1103 
1104 	// Check if the key is being used by another control
1105 	char buf[256];
1106 	sprintf(buf, "Input.PlayerCodes%d", playerIndex);
1107 	const InputKeys keys = KeyLoadPlayerKeys(ConfigGet(&gConfig, buf));
1108 	for (key_code_e i = 0; i < KEY_CODE_MAP; i++)
1109 	{
1110 		if (i != code && KeyGet(&keys, i) == key)
1111 		{
1112 			return false;
1113 		}
1114 	}
1115 
1116 	// Check if the other player is using the key
1117 	sprintf(buf, "Input.PlayerCodes%d", 1 - playerIndex);
1118 	const InputKeys keysOther = KeyLoadPlayerKeys(ConfigGet(&gConfig, buf));
1119 	if (keysOther.left == key || keysOther.right == key ||
1120 		keysOther.up == key || keysOther.down == key ||
1121 		keysOther.button1 == key || keysOther.button2 == key)
1122 	{
1123 		return false;
1124 	}
1125 
1126 	return true;
1127 }
1128 
1129 static void ChangeKey(
1130 	const SDL_Scancode key, const key_code_e code, const int playerIndex);
MenuProcessChangeKey(menu_t * menu)1131 void MenuProcessChangeKey(menu_t *menu)
1132 {
1133 	// wait until user has pressed a new button
1134 	SDL_Scancode key = KeyGetPressed(&gEventHandlers.keyboard);
1135 	if (key == SDL_SCANCODE_UNKNOWN)
1136 	{
1137 		return;
1138 	}
1139 	const menu_t *changeKeyMenu = menu->u.normal.changeKeyMenu;
1140 	const key_code_e code = changeKeyMenu->u.changeKey.code;
1141 	const int pi = changeKeyMenu->u.changeKey.playerIndex;
1142 	if (key == SDL_SCANCODE_ESCAPE)
1143 	{
1144 		if (changeKeyMenu->u.changeKey.isOptional)
1145 		{
1146 			// Unset the key
1147 			key = SDL_SCANCODE_UNKNOWN;
1148 		}
1149 		else
1150 		{
1151 			MenuPlaySound(MENU_SOUND_BACK);
1152 		}
1153 	}
1154 	ChangeKey(key, code, pi);
1155 	menu->u.normal.changeKeyMenu = NULL;
1156 }
ChangeKey(const SDL_Scancode key,const key_code_e code,const int playerIndex)1157 static void ChangeKey(
1158 	const SDL_Scancode key, const key_code_e code, const int playerIndex)
1159 {
1160 	if (!KeyAvailable(key, code, playerIndex))
1161 	{
1162 		MenuPlaySound(MENU_SOUND_ERROR);
1163 		return;
1164 	}
1165 	// Players share map key
1166 	const int changePlayerIndex = code == KEY_CODE_MAP ? 0 : playerIndex;
1167 	char buf[256];
1168 	sprintf(
1169 		buf, "Input.PlayerCodes%d.%s", changePlayerIndex, KeycodeStr(code));
1170 	ConfigGet(&gConfig, buf)->u.Int.Value = key;
1171 	sprintf(buf, "Input.PlayerCodes%d", changePlayerIndex);
1172 	gEventHandlers.keyboard.PlayerKeys[changePlayerIndex] =
1173 		KeyLoadPlayerKeys(ConfigGet(&gConfig, buf));
1174 	MenuPlaySound(MENU_SOUND_ENTER);
1175 }
1176 
MenuChangeIndex(menu_t * menu,int cmd)1177 void MenuChangeIndex(menu_t *menu, int cmd)
1178 {
1179 	// Ignore if no submenus
1180 	if (menu->u.normal.subMenus.size == 0)
1181 	{
1182 		return;
1183 	}
1184 
1185 	if (Up(cmd))
1186 	{
1187 		menu->u.normal.index--;
1188 		if (menu->u.normal.index == -1)
1189 		{
1190 			menu->u.normal.index = (int)menu->u.normal.subMenus.size - 1;
1191 		}
1192 		MoveIndexToNextEnabledSubmenu(menu, false);
1193 		MenuPlaySound(MENU_SOUND_SWITCH);
1194 	}
1195 	else if (Down(cmd))
1196 	{
1197 		menu->u.normal.index++;
1198 		if (menu->u.normal.index == (int)menu->u.normal.subMenus.size)
1199 		{
1200 			menu->u.normal.index = 0;
1201 		}
1202 		MoveIndexToNextEnabledSubmenu(menu, true);
1203 		MenuPlaySound(MENU_SOUND_SWITCH);
1204 	}
1205 	const int nMenuItems = MenuGetNumMenuItemsShown(menu);
1206 	menu->u.normal.scroll = CLAMP(
1207 		menu->u.normal.scroll, MAX(0, menu->u.normal.index - nMenuItems + 1),
1208 		MIN((int)menu->u.normal.subMenus.size - 1,
1209 			menu->u.normal.index + nMenuItems - 1));
1210 	if (menu->u.normal.index < menu->u.normal.scroll)
1211 	{
1212 		menu->u.normal.scroll = menu->u.normal.index;
1213 	}
1214 }
1215 
MenuActivate(MenuSystem * ms,menu_t * menu,int cmd)1216 void MenuActivate(MenuSystem *ms, menu_t *menu, int cmd)
1217 {
1218 	UNUSED(ms);
1219 	MenuPlaySound(MENU_SOUND_SWITCH);
1220 	switch (menu->type)
1221 	{
1222 	case MENU_TYPE_BASIC:
1223 		// do nothing
1224 		return;
1225 	case MENU_TYPE_SET_OPTION_TOGGLE:
1226 		*menu->u.option.uHook.optionToggle =
1227 			!*menu->u.option.uHook.optionToggle;
1228 		break;
1229 	case MENU_TYPE_SET_OPTION_RANGE: {
1230 		int option = *menu->u.option.uHook.optionRange.option;
1231 		int increment = menu->u.option.uHook.optionRange.increment;
1232 		int low = menu->u.option.uHook.optionRange.low;
1233 		int high = menu->u.option.uHook.optionRange.high;
1234 		if (Left(cmd))
1235 		{
1236 			if (low == option)
1237 			{
1238 				option = high;
1239 			}
1240 			else if (low + increment > option)
1241 			{
1242 				option = low;
1243 			}
1244 			else
1245 			{
1246 				option -= increment;
1247 			}
1248 		}
1249 		else if (Right(cmd))
1250 		{
1251 			if (high == option)
1252 			{
1253 				option = low;
1254 			}
1255 			else if (high - increment < option)
1256 			{
1257 				option = high;
1258 			}
1259 			else
1260 			{
1261 				option += increment;
1262 			}
1263 		}
1264 		*menu->u.option.uHook.optionRange.option = option;
1265 	}
1266 	break;
1267 	case MENU_TYPE_SET_OPTION_SEED: {
1268 		unsigned int seed = *menu->u.option.uHook.seed;
1269 		unsigned int increment = 1;
1270 		if (Button1(cmd))
1271 		{
1272 			increment *= 10;
1273 		}
1274 		if (Button2(cmd))
1275 		{
1276 			increment *= 100;
1277 		}
1278 		if (Left(cmd))
1279 		{
1280 			if (increment > seed)
1281 			{
1282 				seed = 0;
1283 			}
1284 			else
1285 			{
1286 				seed -= increment;
1287 			}
1288 		}
1289 		else if (Right(cmd))
1290 		{
1291 			if (UINT_MAX - increment < seed)
1292 			{
1293 				seed = UINT_MAX;
1294 			}
1295 			else
1296 			{
1297 				seed += increment;
1298 			}
1299 		}
1300 		*menu->u.option.uHook.seed = seed;
1301 	}
1302 	break;
1303 	case MENU_TYPE_SET_OPTION_UP_DOWN_VOID_FUNC_VOID:
1304 		if (Left(cmd))
1305 		{
1306 			menu->u.option.uHook.upDownFuncs.upFunc();
1307 		}
1308 		else if (Right(cmd))
1309 		{
1310 			menu->u.option.uHook.upDownFuncs.downFunc();
1311 		}
1312 		break;
1313 	case MENU_TYPE_SET_OPTION_RANGE_GET_SET: {
1314 		int option = menu->u.option.uHook.optionRangeGetSet.getFunc();
1315 		int increment = menu->u.option.uHook.optionRangeGetSet.increment;
1316 		if (Left(cmd))
1317 		{
1318 			if (menu->u.option.uHook.optionRangeGetSet.low + increment >
1319 				option)
1320 			{
1321 				option = menu->u.option.uHook.optionRangeGetSet.low;
1322 			}
1323 			else
1324 			{
1325 				option -= increment;
1326 			}
1327 		}
1328 		else if (Right(cmd))
1329 		{
1330 			if (menu->u.option.uHook.optionRangeGetSet.high - increment <
1331 				option)
1332 			{
1333 				option = menu->u.option.uHook.optionRangeGetSet.high;
1334 			}
1335 			else
1336 			{
1337 				option += increment;
1338 			}
1339 		}
1340 		menu->u.option.uHook.optionRangeGetSet.setFunc(option);
1341 	}
1342 	break;
1343 	case MENU_TYPE_VOID_FUNC:
1344 		if (AnyButton(cmd))
1345 		{
1346 			menu->u.option.uHook.voidFunc.func(
1347 				menu->u.option.uHook.voidFunc.data);
1348 		}
1349 		break;
1350 	case MENU_TYPE_SET_OPTION_CHANGE_KEY:
1351 		menu->parentMenu->u.normal.changeKeyMenu = menu;
1352 		break;
1353 	default:
1354 		printf("Error unhandled menu type %d\n", menu->type);
1355 		assert(0);
1356 		break;
1357 	}
1358 }
1359