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