1 /*
2 * Copyright (C) 2013 Neverball authors
3 *
4 * NEVERBALL is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published
6 * by the Free Software Foundation; either version 2 of the License,
7 * or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
13 */
14
15 #include <SDL.h>
16
17
18 #include "config.h"
19 #include "audio.h"
20 #include "video.h"
21 #include "geom.h"
22 #include "lang.h"
23 #include "gui.h"
24
25 #include "st_common.h"
26
27 #define AUD_MENU "snd/menu.ogg"
28
29 /*---------------------------------------------------------------------------*/
30
31 /*
32 * Conf screen GUI helpers.
33 */
34
conf_slider(int id,const char * text,int token,int value,int * ids,int num)35 void conf_slider(int id, const char *text,
36 int token, int value,
37 int *ids, int num)
38 {
39 int jd, kd, i;
40
41 if ((jd = gui_harray(id)) && (kd = gui_harray(jd)))
42 {
43 /* A series of empty buttons forms a "slider". */
44
45 for (i = num - 1; i >= 0; i--)
46 {
47 ids[i] = gui_state(kd, NULL, GUI_SML, token, i);
48
49 gui_set_hilite(ids[i], (i == value));
50 }
51
52 gui_label(jd, text, GUI_SML, 0, 0);
53 }
54 }
55
conf_state(int id,const char * label,const char * text,int token)56 int conf_state(int id, const char *label, const char *text, int token)
57 {
58 int jd, kd, rd = 0;
59
60 if ((jd = gui_harray(id)) && (kd = gui_harray(jd)))
61 {
62 rd = gui_state(kd, text, GUI_SML, token, 0);
63 gui_label(jd, label, GUI_SML, 0, 0);
64 }
65
66 return rd;
67 }
68
conf_toggle(int id,const char * label,int token,int value,const char * text1,int value1,const char * text0,int value0)69 void conf_toggle(int id, const char *label, int token, int value,
70 const char *text1, int value1,
71 const char *text0, int value0)
72 {
73 int jd, kd;
74
75 if ((jd = gui_harray(id)) && (kd = gui_harray(jd)))
76 {
77 int btn0, btn1;
78
79 btn0 = gui_state(kd, text0, GUI_SML, token, value0);
80 btn1 = gui_state(kd, text1, GUI_SML, token, value1);
81
82 gui_set_hilite(btn0, (value == value0));
83 gui_set_hilite(btn1, (value == value1));
84
85 gui_label(jd, label, GUI_SML, 0, 0);
86 }
87 }
88
conf_header(int id,const char * text,int token)89 void conf_header(int id, const char *text, int token)
90 {
91 int jd;
92
93 if ((jd = gui_harray(id)))
94 {
95 gui_label(jd, text, GUI_SML, 0, 0);
96 gui_space(jd);
97 gui_start(jd, _("Back"), GUI_SML, token, 0);
98 }
99
100 gui_space(id);
101 }
102
conf_select(int id,const char * text,int token,int value,const struct conf_option * opts,int num)103 void conf_select(int id, const char *text, int token, int value,
104 const struct conf_option *opts, int num)
105 {
106 int jd, kd, ld;
107 int i;
108
109 if ((jd = gui_harray(id)) && (kd = gui_harray(jd)))
110 {
111 for (i = 0; i < num; i++)
112 {
113 ld = gui_state(kd, _(opts[i].text), GUI_SML,
114 token, opts[i].value);
115
116 gui_set_hilite(ld, (opts[i].value == value));
117 }
118
119 gui_label(jd, text, GUI_SML, 0, 0);
120 }
121 }
122
123 /*---------------------------------------------------------------------------*/
124
125 /*
126 * Code shared by most screens (not just conf screens).
127 *
128 * FIXME This probably makes ball/st_shared.c obsolete.
129 */
130
131 static int (*common_action)(int tok, int val);
132
common_init(int (* action_fn)(int,int))133 void common_init(int (*action_fn)(int, int))
134 {
135 common_action = action_fn;
136 }
137
common_leave(struct state * st,struct state * next,int id)138 void common_leave(struct state *st, struct state *next, int id)
139 {
140 gui_delete(id);
141 }
142
common_paint(int id,float st)143 void common_paint(int id, float st)
144 {
145 gui_paint(id);
146 }
147
common_timer(int id,float dt)148 void common_timer(int id, float dt)
149 {
150 gui_timer(id, dt);
151 }
152
common_point(int id,int x,int y,int dx,int dy)153 void common_point(int id, int x, int y, int dx, int dy)
154 {
155 gui_pulse(gui_point(id, x, y), 1.2f);
156 }
157
common_stick(int id,int a,float v,int bump)158 void common_stick(int id, int a, float v, int bump)
159 {
160 gui_pulse(gui_stick(id, a, v, bump), 1.2f);
161 }
162
common_click(int b,int d)163 int common_click(int b, int d)
164 {
165 if (gui_click(b, d))
166 {
167 int active = gui_active();
168 return common_action(gui_token(active), gui_value(active));
169 }
170 return 1;
171 }
172
common_keybd(int c,int d)173 int common_keybd(int c, int d)
174 {
175 return (d && c == KEY_EXIT) ? common_action(GUI_BACK, 0) : 1;
176 }
177
common_buttn(int b,int d)178 int common_buttn(int b, int d)
179 {
180 if (d)
181 {
182 int active = gui_active();
183
184 if (config_tst_d(CONFIG_JOYSTICK_BUTTON_A, b))
185 return common_action(gui_token(active), gui_value(active));
186 if (config_tst_d(CONFIG_JOYSTICK_BUTTON_B, b))
187 return common_action(GUI_BACK, 0);
188 }
189 return 1;
190 }
191
192 /*---------------------------------------------------------------------------*/
193
194 /*
195 * Code shared by conf screens.
196 */
197
conf_common_init(int (* action_fn)(int,int))198 void conf_common_init(int (*action_fn)(int, int))
199 {
200 back_init("back/gui.png");
201 audio_music_fade_to(0.5f, "bgm/inter.ogg");
202
203 common_init(action_fn);
204 }
205
conf_common_leave(struct state * st,struct state * next,int id)206 void conf_common_leave(struct state *st, struct state *next, int id)
207 {
208 back_free();
209
210 gui_delete(id);
211 }
212
conf_common_paint(int id,float t)213 void conf_common_paint(int id, float t)
214 {
215 video_push_persp((float) config_get_d(CONFIG_VIEW_FOV), 0.1f, FAR_DIST);
216 {
217 back_draw_easy();
218 }
219 video_pop_matrix();
220
221 gui_paint(id);
222 }
223
224 /*---------------------------------------------------------------------------*/
225
226 enum
227 {
228 VIDEO_FULLSCREEN = GUI_LAST,
229 VIDEO_DISPLAY,
230 VIDEO_RESOLUTION,
231 VIDEO_REFLECTION,
232 VIDEO_BACKGROUND,
233 VIDEO_SHADOW,
234 VIDEO_VSYNC,
235 VIDEO_HMD,
236 VIDEO_MULTISAMPLE
237 };
238
239 static struct state *video_back;
240
video_action(int tok,int val)241 static int video_action(int tok, int val)
242 {
243 int f = config_get_d(CONFIG_FULLSCREEN);
244 int w = config_get_d(CONFIG_WIDTH);
245 int h = config_get_d(CONFIG_HEIGHT);
246 int r = 1;
247
248 audio_play(AUD_MENU, 1.0f);
249
250 switch (tok)
251 {
252 case GUI_BACK:
253 goto_state(video_back);
254 video_back = NULL;
255 break;
256
257 case VIDEO_FULLSCREEN:
258 goto_state(&st_null);
259 r = video_mode(val, w, h);
260 goto_state(&st_video);
261 break;
262
263 case VIDEO_DISPLAY:
264 goto_state(&st_display);
265 break;
266
267 case VIDEO_REFLECTION:
268 goto_state(&st_null);
269 config_set_d(CONFIG_REFLECTION, val);
270 r = video_mode(f, w, h);
271 goto_state(&st_video);
272 break;
273
274 case VIDEO_BACKGROUND:
275 goto_state(&st_null);
276 config_set_d(CONFIG_BACKGROUND, val);
277 goto_state(&st_video);
278 break;
279
280 case VIDEO_SHADOW:
281 goto_state(&st_null);
282 config_set_d(CONFIG_SHADOW, val);
283 goto_state(&st_video);
284 break;
285
286 case VIDEO_RESOLUTION:
287 goto_state(&st_resol);
288 break;
289
290 case VIDEO_VSYNC:
291 goto_state(&st_null);
292 config_set_d(CONFIG_VSYNC, val);
293 r = video_mode(f, w, h);
294 goto_state(&st_video);
295 break;
296
297 case VIDEO_HMD:
298 goto_state(&st_null);
299 config_set_d(CONFIG_HMD, val);
300 r = video_mode(f, w, h);
301 goto_state(&st_video);
302 break;
303
304 case VIDEO_MULTISAMPLE:
305 goto_state(&st_null);
306 config_set_d(CONFIG_MULTISAMPLE, val);
307 r = video_mode(f, w, h);
308 goto_state(&st_video);
309 break;
310 }
311
312 return r;
313 }
314
video_gui(void)315 static int video_gui(void)
316 {
317 static const struct conf_option multisample_opts[] = {
318 { N_("Off"), 0 },
319 { N_("2x"), 2 },
320 { N_("4x"), 4 },
321 { N_("8x"), 8 },
322 };
323
324 int id, jd;
325
326 if ((id = gui_vstack(0)))
327 {
328 char resolution[sizeof ("12345678 x 12345678")];
329 const char *display;
330 int dpy = config_get_d(CONFIG_DISPLAY);
331
332 sprintf(resolution, "%d x %d",
333 config_get_d(CONFIG_WIDTH),
334 config_get_d(CONFIG_HEIGHT));
335
336 if (!(display = SDL_GetDisplayName(dpy)))
337 display = _("Unknown Display");
338
339 conf_header(id, _("Graphics"), GUI_BACK);
340
341 if ((jd = conf_state(id, _("Display"), "Longest Name", VIDEO_DISPLAY)))
342 {
343 gui_set_trunc(jd, TRUNC_TAIL);
344 gui_set_label(jd, display);
345 }
346
347 conf_toggle(id, _("Fullscreen"), VIDEO_FULLSCREEN,
348 config_get_d(CONFIG_FULLSCREEN), _("On"), 1, _("Off"), 0);
349
350 if ((jd = conf_state (id, _("Resolution"), resolution,
351 VIDEO_RESOLUTION)))
352 {
353 /*
354 * Because we always use the desktop display mode, disable
355 * display mode switching in fullscreen.
356 */
357
358 if (config_get_d(CONFIG_FULLSCREEN))
359 {
360 gui_set_state(jd, GUI_NONE, 0);
361 gui_set_color(jd, gui_gry, gui_gry);
362 }
363 }
364 #if ENABLE_HMD
365 conf_toggle(id, _("HMD"), VIDEO_HMD,
366 config_get_d(CONFIG_HMD), _("On"), 1, _("Off"), 0);
367 #endif
368
369 gui_space(id);
370
371 conf_toggle(id, _("V-Sync"), VIDEO_VSYNC,
372 config_get_d(CONFIG_VSYNC), _("On"), 1, _("Off"), 0);
373 conf_select(id, _("Antialiasing"), VIDEO_MULTISAMPLE,
374 config_get_d(CONFIG_MULTISAMPLE),
375 multisample_opts, ARRAYSIZE(multisample_opts));
376
377 gui_space(id);
378
379 conf_toggle(id, _("Reflection"), VIDEO_REFLECTION,
380 config_get_d(CONFIG_REFLECTION), _("On"), 1, _("Off"), 0);
381 conf_toggle(id, _("Background"), VIDEO_BACKGROUND,
382 config_get_d(CONFIG_BACKGROUND), _("On"), 1, _("Off"), 0);
383 conf_toggle(id, _("Shadow"), VIDEO_SHADOW,
384 config_get_d(CONFIG_SHADOW), _("On"), 1, _("Off"), 0);
385
386 gui_layout(id, 0, 0);
387 }
388
389 return id;
390 }
391
video_enter(struct state * st,struct state * prev)392 static int video_enter(struct state *st, struct state *prev)
393 {
394 if (!video_back)
395 video_back = prev;
396
397 conf_common_init(video_action);
398 return video_gui();
399 }
400
401 /*---------------------------------------------------------------------------*/
402
403 enum
404 {
405 DISPLAY_SELECT = GUI_LAST
406 };
407
408 static struct state *display_back;
409
display_action(int tok,int val)410 static int display_action(int tok, int val)
411 {
412 int r = 1;
413
414 audio_play(AUD_MENU, 1.0f);
415
416 switch (tok)
417 {
418 case GUI_BACK:
419 goto_state(display_back);
420 display_back = NULL;
421 break;
422
423 case DISPLAY_SELECT:
424 if (val != config_get_d(CONFIG_DISPLAY))
425 {
426 goto_state(&st_null);
427 config_set_d(CONFIG_DISPLAY, val);
428 r = video_mode(config_get_d(CONFIG_FULLSCREEN),
429 config_get_d(CONFIG_WIDTH),
430 config_get_d(CONFIG_HEIGHT));
431 goto_state(&st_display);
432 }
433 break;
434 }
435
436 return r;
437 }
438
display_gui(void)439 static int display_gui(void)
440 {
441 int id, jd;
442
443 int i, n = SDL_GetNumVideoDisplays();
444
445 if ((id = gui_vstack(0)))
446 {
447 conf_header(id, _("Display"), GUI_BACK);
448
449 for (i = 0; i < n; i++)
450 {
451 const char *name = SDL_GetDisplayName(i);
452
453 jd = gui_state(id, name, GUI_SML, DISPLAY_SELECT, i);
454 gui_set_hilite(jd, (i == config_get_d(CONFIG_DISPLAY)));
455 }
456
457 gui_layout(id, 0, 0);
458 }
459
460 return id;
461 }
462
display_enter(struct state * st,struct state * prev)463 static int display_enter(struct state *st, struct state *prev)
464 {
465 if (!display_back)
466 display_back = prev;
467
468 conf_common_init(display_action);
469 return display_gui();
470 }
471
472 /*---------------------------------------------------------------------------*/
473
474 struct mode
475 {
476 int w;
477 int h;
478 };
479
480 static const struct mode modes[] = {
481 { 2560, 1440 },
482 { 1920, 1200 },
483 { 1920, 1080 },
484 { 1680, 1050 },
485 { 1600, 1200 },
486 { 1600, 900 },
487 { 1440, 900 },
488 { 1366, 768 },
489 { 1280, 1024 },
490 { 1280, 800 },
491 { 1280, 720 },
492 { 1024, 768 },
493 { 800, 600 },
494 { 640, 480 },
495 { 480, 320 },
496 { 320, 240 }
497 };
498
499 enum
500 {
501 RESOL_MODE = GUI_LAST
502 };
503
504 static struct state *resol_back;
505
resol_action(int tok,int val)506 static int resol_action(int tok, int val)
507 {
508 int r = 1;
509
510 audio_play(AUD_MENU, 1.0f);
511
512 switch (tok)
513 {
514 case GUI_BACK:
515 goto_state(resol_back);
516 resol_back = NULL;
517 break;
518
519 case RESOL_MODE:
520 goto_state(&st_null);
521 r = video_mode(config_get_d(CONFIG_FULLSCREEN),
522 modes[val].w,
523 modes[val].h);
524 goto_state(&st_resol);
525 break;
526 }
527
528 return r;
529 }
530
resol_gui(void)531 static int resol_gui(void)
532 {
533 int id, jd, kd;
534
535 if ((id = gui_vstack(0)))
536 {
537 const int W = config_get_d(CONFIG_WIDTH);
538 const int H = config_get_d(CONFIG_HEIGHT);
539
540 int i, j, n = ARRAYSIZE(modes);
541
542 char buff[sizeof ("1234567890 x 1234567890")] = "";
543
544 conf_header(id, _("Resolution"), GUI_BACK);
545
546 for (i = 0; i < n; i += 4)
547 {
548 if ((jd = gui_harray(id)))
549 {
550 for (j = 3; j >= 0; j--)
551 {
552 int m = i + j;
553
554 if (m < n)
555 {
556 sprintf(buff, "%d x %d", modes[m].w, modes[m].h);
557 kd = gui_state(jd, buff, GUI_SML, RESOL_MODE, m);
558 gui_set_hilite(kd, (modes[m].w == W &&
559 modes[m].h == H));
560 }
561 else
562 {
563 gui_space(jd);
564 }
565 }
566 }
567 }
568
569 gui_layout(id, 0, 0);
570 }
571
572 return id;
573 }
574
resol_enter(struct state * st,struct state * prev)575 static int resol_enter(struct state *st, struct state *prev)
576 {
577 if (!resol_back)
578 resol_back = prev;
579
580 conf_common_init(resol_action);
581 return resol_gui();
582 }
583
584 /*---------------------------------------------------------------------------*/
585
586 #define LANG_STEP 10
587
588 static Array langs;
589 static int first;
590
591 enum
592 {
593 LANG_DEFAULT = GUI_LAST,
594 LANG_SELECT
595 };
596
597 static struct state *lang_back;
598
lang_action(int tok,int val)599 static int lang_action(int tok, int val)
600 {
601 int r = 1;
602
603 struct lang_desc *desc;
604
605 audio_play(AUD_MENU, 1.0f);
606
607 switch (tok)
608 {
609 case GUI_BACK:
610 goto_state(lang_back);
611 lang_back = NULL;
612 break;
613
614 case GUI_PREV:
615 first -= LANG_STEP;
616 goto_state(&st_lang);
617 break;
618
619 case GUI_NEXT:
620 first += LANG_STEP;
621 goto_state(&st_lang);
622 break;
623
624 case LANG_DEFAULT:
625 /* HACK: Reload resources to load the localized font. */
626 goto_state(&st_null);
627 config_set_s(CONFIG_LANGUAGE, "");
628 lang_init();
629 goto_state(&st_lang);
630 break;
631
632 case LANG_SELECT:
633 desc = LANG_GET(langs, val);
634 goto_state(&st_null);
635 config_set_s(CONFIG_LANGUAGE, desc->code);
636 lang_init();
637 goto_state(&st_lang);
638 break;
639 }
640
641 return r;
642 }
643
lang_gui(void)644 static int lang_gui(void)
645 {
646 const int step = (first == 0 ? LANG_STEP - 1 : LANG_STEP);
647
648 int id, jd;
649 int i;
650
651 if ((id = gui_vstack(0)))
652 {
653 if ((jd = gui_hstack(id)))
654 {
655 gui_label(jd, _("Language"), GUI_SML, 0, 0);
656 gui_space(jd);
657 gui_space(jd);
658 gui_navig(jd, array_len(langs), first, LANG_STEP);
659 }
660
661 gui_space(id);
662
663 if (step < LANG_STEP)
664 {
665 int default_id;
666 default_id = gui_state(id, _("Default"), GUI_SML, LANG_DEFAULT, 0);
667 gui_set_hilite(default_id, !*config_get_s(CONFIG_LANGUAGE));
668 }
669
670 for (i = first; i < first + step; i++)
671 {
672 if (i < array_len(langs))
673 {
674 struct lang_desc *desc = LANG_GET(langs, i);
675
676 int lang_id;
677
678 lang_id = gui_state(id, " ", GUI_SML, LANG_SELECT, i);
679
680 gui_set_hilite(lang_id, (strcmp(config_get_s(CONFIG_LANGUAGE),
681 desc->code) == 0));
682
683 /* Set font and rebuild texture. */
684
685 gui_set_font(lang_id, desc->font);
686 gui_set_label(lang_id, lang_name(desc));
687 }
688 else
689 {
690 gui_label(id, " ", GUI_SML, 0, 0);
691 }
692 }
693
694 gui_layout(id, 0, 0);
695 }
696
697 return id;
698 }
699
lang_enter(struct state * st,struct state * prev)700 static int lang_enter(struct state *st, struct state *prev)
701 {
702 if (!langs)
703 {
704 langs = lang_dir_scan();
705 first = 0;
706 }
707
708 if (!lang_back)
709 lang_back = prev;
710
711 conf_common_init(lang_action);
712 return lang_gui();
713 }
714
lang_leave(struct state * st,struct state * next,int id)715 void lang_leave(struct state *st, struct state *next, int id)
716 {
717 if (!(next == &st_lang || next == &st_null))
718 {
719 lang_dir_free(langs);
720 langs = NULL;
721 }
722
723 conf_common_leave(st, next, id);
724 }
725
726 /*---------------------------------------------------------------------------*/
727
728 struct state st_video = {
729 video_enter,
730 conf_common_leave,
731 conf_common_paint,
732 common_timer,
733 common_point,
734 common_stick,
735 NULL,
736 common_click,
737 common_keybd,
738 common_buttn
739 };
740
741 struct state st_display = {
742 display_enter,
743 conf_common_leave,
744 conf_common_paint,
745 common_timer,
746 common_point,
747 common_stick,
748 NULL,
749 common_click,
750 common_keybd,
751 common_buttn
752 };
753
754 struct state st_resol = {
755 resol_enter,
756 conf_common_leave,
757 conf_common_paint,
758 common_timer,
759 common_point,
760 common_stick,
761 NULL,
762 common_click,
763 common_keybd,
764 common_buttn
765 };
766
767 struct state st_lang = {
768 lang_enter,
769 lang_leave,
770 conf_common_paint,
771 common_timer,
772 common_point,
773 common_stick,
774 NULL,
775 common_click,
776 common_keybd,
777 common_buttn
778 };
779
780 /*---------------------------------------------------------------------------*/
781