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