1 
2 // menu.cpp
3 
4 // Copyright 2004-2006 Jasmine Langridge, jas@jareiko.net
5 // License: GPL version 2 (see included gpl.txt)
6 
7 #include <sstream>
8 #include "main.h"
9 
10 const int MAX_RACES_ON_SCREEN   = 12;
11 const int MAX_TIMES_ON_SCREEN   = 13;
12 
13 // Best Times table font information, must be kept updated
14 const float fa      = 8.0f / 12.0f;     // Font Aspect
15 const float fs      = 20.0f;            // Font Size
16 const float fw      = fa * fs;          // Font Width
17 
18 // X coordinate values for Best Times table labels
19 const float XTIMES_PLAYERNAME   = 100.0f + 4 * fw; // 150.0f;
20 const float XTIMES_CARNAME      = 350.0f;
21 const float XTIMES_CARCLASS     = 475.0f;
22 const float XTIMES_TOTALTIME    = 700.0f;
23 
levelScreenAction(int action,int index)24 void MainApp::levelScreenAction(int action, int index)
25 {
26   appstate = AS_LEVEL_SCREEN;
27 
28   switch (action) {
29   case AA_INIT:
30     lss.state = AM_TOP;
31     break;
32   case AA_RESUME:
33     // lss.state should be AM_TOP_EVT_PREP, continuing event
34     break;
35   case AA_GO_TOP:
36     lss.state = AM_TOP;
37     break;
38   case AA_GO_EVT:
39     if (lss.state == AM_TOP_EVT_PREP &&
40       lss.currentlevel > 0 &&
41       lss.currentlevel < (int)events[lss.currentevent].levels.size()) {
42       lss.state = AM_TOP_EVT_ABANDON;
43     } else {
44       lss.currentevent = index;
45       lss.state = AM_TOP_EVT;
46     }
47     break;
48   case AA_PICK_EVT:
49     lss.currentevent = index;
50     lss.currentlevel = 0;
51     lss.livesleft = 3;
52     lss.leveltimes.clear();
53     lss.totaltime = 0.0f;
54     lss.state = AM_TOP_EVT_PREP;
55     break;
56   case AA_RESUME_EVT:
57     lss.state = AM_TOP_EVT_PREP;
58     break;
59   case AA_RESTART_EVT:
60     lss.currentlevel = 0;
61     lss.livesleft = 3;
62     lss.leveltimes.clear();
63     lss.totaltime = 0.0f;
64     lss.state = AM_TOP_EVT_PREP;
65     break;
66   case AA_GO_PRAC:
67     lss.currentevent = index;
68     lss.state = AM_TOP_PRAC;
69     break;
70   case AA_PICK_PRAC:
71     lss.currentevent = index;
72     lss.state = AM_TOP_PRAC_SEL;
73     break;
74   case AA_PICK_PRAC_LVL:
75     lss.currentlevel = index;
76     lss.state = AM_TOP_PRAC_SEL_PREP;
77     break;
78   case AA_GO_LVL:
79     lss.currentlevel = index;
80     lss.state = AM_TOP_LVL;
81     break;
82   case AA_PICK_LVL:
83     lss.currentlevel = index;
84     lss.state = AM_TOP_LVL_PREP;
85     break;
86   case AA_GO_QUIT:
87     lss.state = AM_TOP_QUIT;
88     break;
89   case AA_QUIT_CONFIRM:
90     quitGame();
91     break;
92 
93   case AA_START_EVT:
94     startGame(events[lss.currentevent].levels[lss.currentlevel].filename);
95     return;
96   case AA_START_PRAC:
97     startGame(events[lss.currentevent].levels[lss.currentlevel].filename);
98     return;
99   case AA_START_LVL:
100     startGame(levels[lss.currentlevel].filename);
101     return;
102 
103     case AA_SHOWTIMES_LVL:
104         lss.currentplayer = index;
105         break;
106 
107     case AA_SHOWTIMES_PRAC:
108         lss.currentplayer = index;
109         break;
110 
111     case AA_BSHOWTIMES_LVL:
112         lss.currentplayer = index;
113         lss.state = AM_TOP_LVL_BTIMES;
114         current_times = best_times.getCurrentTimes(
115             levels[lss.currentlevel].filename,
116             HISCORE1_SORT::BY_TOTALTIME_ASC);
117         break;
118 
119     case AA_BSHOWTIMES_PRAC:
120         lss.currentplayer = index;
121         lss.state = AM_TOP_PRAC_BTIMES;
122         current_times = best_times.getCurrentTimes(
123             events[lss.currentevent].levels[lss.currentlevel].filename,
124             HISCORE1_SORT::BY_TOTALTIME_ASC);
125         break;
126 
127     case AA_SORT_BY_PLAYERNAME:
128     {
129         lss.currentplayer = 0;
130 
131         if (hs_sort_method == HISCORE1_SORT::BY_PLAYERNAME_ASC)
132             hs_sort_method = HISCORE1_SORT::BY_PLAYERNAME_DESC;
133         else
134             hs_sort_method = HISCORE1_SORT::BY_PLAYERNAME_ASC;
135 
136         break;
137     }
138 
139     case AA_SORT_BY_CARNAME:
140     {
141         lss.currentplayer = 0;
142 
143         if (hs_sort_method == HISCORE1_SORT::BY_CARNAME_ASC)
144             hs_sort_method = HISCORE1_SORT::BY_CARNAME_DESC;
145         else
146             hs_sort_method = HISCORE1_SORT::BY_CARNAME_ASC;
147 
148         break;
149     }
150 
151     case AA_SORT_BY_CARCLASS:
152     {
153         lss.currentplayer = 0;
154 
155         if (hs_sort_method == HISCORE1_SORT::BY_CARCLASS_ASC)
156             hs_sort_method = HISCORE1_SORT::BY_CARCLASS_DESC;
157         else
158             hs_sort_method = HISCORE1_SORT::BY_CARCLASS_ASC;
159 
160         break;
161     }
162 
163     case AA_SORT_BY_TOTALTIME:
164     {
165         lss.currentplayer = 0;
166 
167         if (hs_sort_method == HISCORE1_SORT::BY_TOTALTIME_ASC)
168             hs_sort_method = HISCORE1_SORT::BY_TOTALTIME_DESC;
169         else
170             hs_sort_method = HISCORE1_SORT::BY_TOTALTIME_ASC;
171 
172         break;
173     }
174 
175   default:
176     PUtil::outLog() << "ERROR: invalid action code " << action << std::endl;
177     requestExit();
178     return;
179   }
180 
181   gui.setSSRender(getSSRender());
182   gui.setFont(tex_fontSourceCodeShadowed);
183   grabMouse(false);
184   gui.clear();
185   gui.addLabel(10.0f,570.0f, "Trigger Rally", PTEXT_HZA_LEFT | PTEXT_VTA_CENTER, 30.0f, LabelStyle::Weak);
186 
187   switch (lss.state) {
188   case AM_TOP:
189     gui.makeClickable(
190       gui.addLabel(400.0f,350.0f, "events", PTEXT_HZA_CENTER | PTEXT_VTA_CENTER, 40.0f), AA_GO_EVT, 0);
191     gui.makeClickable(
192       gui.addLabel(400.0f,300.0f, "practice", PTEXT_HZA_CENTER | PTEXT_VTA_CENTER, 40.0f), AA_GO_PRAC, 0);
193     gui.makeClickable(
194       gui.addLabel(400.0f,250.0f, "single race", PTEXT_HZA_CENTER | PTEXT_VTA_CENTER, 40.0f), AA_GO_LVL, 0);
195     gui.makeClickable(
196       gui.addLabel(10.0f,30.0f, "quit", PTEXT_HZA_LEFT | PTEXT_VTA_CENTER, 40.0f), AA_GO_QUIT, 0);
197 
198     gui.addLabel(790.0f, 570.0f, "version " PACKAGE_VERSION, PTEXT_HZA_RIGHT | PTEXT_VTA_CENTER, 30.0f, LabelStyle::Weak);
199     gui.addLabel(790.0f, 30.0f, "Build: " __DATE__ " at " __TIME__, PTEXT_HZA_RIGHT | PTEXT_VTA_CENTER, 20.0f, LabelStyle::Weak);
200     break;
201   case AM_TOP_EVT:
202   {
203     gui.makeClickable(
204       gui.addLabel(10.0f,30.0f, "back", PTEXT_HZA_LEFT | PTEXT_VTA_CENTER, 40.0f), AA_GO_TOP, 0);
205     gui.addLabel(100.0f,470.0f, "Choose Event:", PTEXT_HZA_LEFT | PTEXT_VTA_CENTER, 30.0f, LabelStyle::Header);
206 
207       int firstraceindex = index;
208       const int prevbutton = gui.addGraphic(20.0f, 275.0f, 50.0f, 50.0f, tex_button_prev, GraphicStyle::Button);
209       const int nextbutton = gui.addGraphic(730.0f, 275.0f, 50.0f, 50.0f, tex_button_next, GraphicStyle::Button);
210 
211       if (firstraceindex > 0) { // FIXME: originally was AA_GO_PRAC?
212         gui.makeClickable(prevbutton, AA_GO_EVT, firstraceindex - MAX_RACES_ON_SCREEN);
213       }
214 
215       int racesonscreencount = events.size() - firstraceindex;
216 
217       if (racesonscreencount > MAX_RACES_ON_SCREEN) {
218         racesonscreencount = MAX_RACES_ON_SCREEN;
219         gui.makeClickable(nextbutton, AA_GO_EVT, firstraceindex + MAX_RACES_ON_SCREEN);
220       }
221 
222       std::stringstream racecountmsg;
223       racecountmsg << "events " << firstraceindex + 1 << '-' << firstraceindex + racesonscreencount << '/' << events.size();
224       gui.addLabel(790.0f, 570.0f, racecountmsg.str(), PTEXT_HZA_RIGHT | PTEXT_VTA_CENTER, 20.0f, LabelStyle::Weak);
225         gui.addLabel(700, 470, "races (timelimit)", PTEXT_HZA_RIGHT | PTEXT_VTA_CENTER, 20);
226 
227     for (int i = firstraceindex; i < firstraceindex + racesonscreencount; i++) {
228 
229         const int eventlabel = gui.addLabel(100.0f,440.0f - (float)(i - firstraceindex) * 30.0f,
230             events[i].name, PTEXT_HZA_LEFT | PTEXT_VTA_TOP, 25.0f, LabelStyle::List);
231 
232         if (!events[i].locked || player_unlocks.count(events[i].filename) != 0)
233             gui.makeClickable(eventlabel, AA_PICK_EVT, i);
234 
235       gui.addLabel(700.0f, 440.0f - (float)(i - firstraceindex) * 30.0f,
236             PUtil::formatInt(events[i].levels.size()) + " (" + events[i].totaltime + ')',
237             PTEXT_HZA_RIGHT | PTEXT_VTA_TOP, 25.0f, LabelStyle::List);
238     }
239     break;
240   }
241   case AM_TOP_EVT_PREP:
242     gui.makeClickable(
243       gui.addLabel(10.0f, 10.0f, "back", PTEXT_HZA_LEFT | PTEXT_VTA_BOTTOM, 40.0f),
244       AA_GO_EVT, 0);
245     gui.addLabel(790.0f, 570.0f, events[lss.currentevent].name, PTEXT_HZA_RIGHT | PTEXT_VTA_CENTER, 20.0f, LabelStyle::Weak);
246     gui.addLabel(100.0f,470.0f, "Races:", PTEXT_HZA_LEFT | PTEXT_VTA_CENTER, 30.0f, LabelStyle::Header);
247     gui.addLabel(700, 470, "status/time", PTEXT_HZA_RIGHT | PTEXT_VTA_CENTER, 20);
248 
249     for (unsigned int i = 0; i < events[lss.currentevent].levels.size(); i++) {
250 
251       LabelStyle namestyle = LabelStyle::List;
252 
253       if (lss.currentlevel > (int)i)
254         namestyle = LabelStyle::Strong;
255       else
256       if (lss.currentlevel == (int)i)
257         namestyle = LabelStyle::Marked;
258 
259       gui.addLabel(100.0f,440.0f - (float)i * 30.0f,
260         events[lss.currentevent].levels[i].name, PTEXT_HZA_LEFT | PTEXT_VTA_TOP, 25.0f, namestyle);
261 
262       std::string infotext = "not yet raced";
263       LabelStyle infostyle = LabelStyle::List;
264 
265       if (lss.currentlevel > (int)i)
266       {
267         infotext = PUtil::formatTime(lss.leveltimes[i]);
268         infostyle = LabelStyle::Strong;
269       }
270       else if (lss.currentlevel == (int)i)
271       {
272         infotext = "next";
273         infostyle = LabelStyle::Marked;
274       }
275       gui.addLabel(700.0f,440.0f - (float)i * 30.0f,
276         infotext, PTEXT_HZA_RIGHT | PTEXT_VTA_TOP, 25.0f, infostyle);
277     }
278     gui.addLabel(700.0f,430.0f - (float)events[lss.currentevent].levels.size() * 30.0f,
279       "Total: " + PUtil::formatTime(lss.totaltime), PTEXT_HZA_RIGHT | PTEXT_VTA_TOP, 25.0f, LabelStyle::Strong);
280     if (lss.livesleft <= 0) {
281       gui.addLabel(400.0f, 10.0f, "no tries remaining", PTEXT_HZA_CENTER | PTEXT_VTA_BOTTOM, 20.0f, LabelStyle::Strong);
282       gui.makeClickable(
283         gui.addLabel(790.0f,10.0f, "restart", PTEXT_HZA_RIGHT | PTEXT_VTA_BOTTOM, 40.0f),
284         AA_RESTART_EVT, 0);
285     } else if (lss.currentlevel >= (int)events[lss.currentevent].levels.size()) {
286       gui.addLabel(400.0f,10.0f, "EVENT COMPLETED!", PTEXT_HZA_CENTER | PTEXT_VTA_BOTTOM, 30.0f, LabelStyle::Marked);
287     } else {
288       gui.addLabel(400.0f,10.0f, PUtil::formatInt(lss.livesleft) + " tries remaining",
289         PTEXT_HZA_CENTER | PTEXT_VTA_BOTTOM, 20.0f, LabelStyle::Strong);
290 
291       for (int i = 0; i < lss.livesleft; i++) {
292         gui.addGraphic(325.0f + i * 50.0f,30.0f, 50.0f,50.0f, tex_hud_life);
293       }
294       gui.makeDefault(
295         gui.makeClickable(
296           gui.addLabel(790.0f,10.0f, "race", PTEXT_HZA_RIGHT | PTEXT_VTA_BOTTOM, 40.0f),
297           AA_START_EVT, 0));
298     }
299     break;
300   case AM_TOP_EVT_ABANDON:
301     gui.addLabel(400.0f,350.0f, "Really leave Event?", PTEXT_HZA_CENTER | PTEXT_VTA_TOP, 40.0f, LabelStyle::Marked);
302     gui.makeClickable(
303       gui.addLabel(300.0f,250.0f, "Yes", PTEXT_HZA_CENTER | PTEXT_VTA_TOP, 40.0f),
304       AA_GO_EVT, 0);
305     gui.makeClickable(
306       gui.addLabel(500.0f,250.0f, "No", PTEXT_HZA_CENTER | PTEXT_VTA_TOP, 40.0f),
307       AA_RESUME_EVT, 0);
308     break;
309   case AM_TOP_PRAC:
310   {
311     gui.makeClickable(
312       gui.addLabel(10.0f, 10.0f, "back", PTEXT_HZA_LEFT | PTEXT_VTA_BOTTOM, 40.0f), AA_GO_TOP, 0);
313     gui.addLabel(100.0f,470.0f, "Practice Event:", PTEXT_HZA_LEFT | PTEXT_VTA_CENTER, 30.0f, LabelStyle::Header);
314 
315       int firstraceindex = index;
316       const int prevbutton = gui.addGraphic(20.0f, 275.0f, 50.0f, 50.0f, tex_button_prev, GraphicStyle::Button);
317       const int nextbutton = gui.addGraphic(730.0f, 275.0f, 50.0f, 50.0f, tex_button_next, GraphicStyle::Button);
318 
319       if (firstraceindex > 0) {
320         gui.makeClickable(prevbutton, AA_GO_PRAC, firstraceindex - MAX_RACES_ON_SCREEN);
321       }
322 
323       int racesonscreencount = events.size() - firstraceindex;
324 
325       if (racesonscreencount > MAX_RACES_ON_SCREEN) {
326         racesonscreencount = MAX_RACES_ON_SCREEN;
327         gui.makeClickable(nextbutton, AA_GO_PRAC, firstraceindex + MAX_RACES_ON_SCREEN);
328       }
329 
330       std::stringstream racecountmsg;
331       racecountmsg << "events " << firstraceindex + 1 << '-' << firstraceindex + racesonscreencount << '/' << events.size();
332       gui.addLabel(790.0f, 570.0f, racecountmsg.str(), PTEXT_HZA_RIGHT | PTEXT_VTA_CENTER, 20.0f, LabelStyle::Weak);
333       gui.addLabel(700, 470, "races (timelimit)", PTEXT_HZA_RIGHT | PTEXT_VTA_CENTER, 20);
334 
335     for (int i = firstraceindex; i < firstraceindex + racesonscreencount; i++) {
336 
337         const int eventlabel = gui.addLabel(100.0f,440.0f - (float)(i - firstraceindex) * 30.0f,
338             events[i].name, PTEXT_HZA_LEFT | PTEXT_VTA_TOP, 25.0f, LabelStyle::List);
339 
340         if (!events[i].locked || player_unlocks.count(events[i].filename) != 0)
341             gui.makeClickable(eventlabel, AA_PICK_PRAC, i);
342 
343       gui.addLabel(700.0f, 440.0f - (float)(i - firstraceindex) * 30.0f,
344             PUtil::formatInt(events[i].levels.size()) + " (" + events[i].totaltime + ')',
345             PTEXT_HZA_RIGHT | PTEXT_VTA_TOP, 25.0f, LabelStyle::List);
346     }
347     break;
348   }
349   case AM_TOP_PRAC_SEL:
350   {
351     gui.makeClickable(
352       gui.addLabel(10.0f, 10.0f, "back", PTEXT_HZA_LEFT | PTEXT_VTA_BOTTOM, 40.0f),
353       AA_GO_PRAC, 0);
354     gui.addLabel(790.0f, 570.0f, events[lss.currentevent].name, PTEXT_HZA_RIGHT | PTEXT_VTA_CENTER, 20.0f, LabelStyle::Weak);
355     gui.addLabel(100.0f,470.0f, "Choose Race:", PTEXT_HZA_LEFT | PTEXT_VTA_CENTER, 30.0f, LabelStyle::Header);
356     gui.addLabel(700, 470, "timelimit", PTEXT_HZA_RIGHT | PTEXT_VTA_CENTER, 20);
357 
358     for (unsigned int i = 0; i < events[lss.currentevent].levels.size(); i++) {
359         gui.makeClickable(
360           gui.addLabel(100.0f, 440.0f - (float)i * 30.0f,
361           events[lss.currentevent].levels[i].name, PTEXT_HZA_LEFT | PTEXT_VTA_TOP, 25.0f, LabelStyle::List),
362           AA_PICK_PRAC_LVL, i);
363 
364         gui.addLabel(700.0f, 440.0f - (float)i * 30.0f,
365             events[lss.currentevent].levels[i].targettimeshort, PTEXT_HZA_RIGHT | PTEXT_VTA_TOP, 25.0f, LabelStyle::List);
366     }
367     break;
368   }
369   case AM_TOP_PRAC_SEL_PREP:
370   {
371       const int prevbutton = gui.addGraphic(20.0f, 275.0f, 50.0f, 50.0f, tex_button_prev, GraphicStyle::Button);
372       const int nextbutton = gui.addGraphic(730.0f, 275.0f, 50.0f, 50.0f, tex_button_next, GraphicStyle::Button);
373 
374       int idxnext = lss.currentlevel + 1;
375       int idxprev = lss.currentlevel - 1;
376 
377       CLAMP(idxnext, 0, static_cast<int> (events[lss.currentevent].levels.size() - 1));
378       CLAMP(idxprev, 0, static_cast<int> (events[lss.currentevent].levels.size() - 1));
379 
380     if (lss.currentlevel < static_cast<int> (events[lss.currentevent].levels.size() - 1))
381       gui.makeClickable(nextbutton, AA_PICK_PRAC_LVL, idxnext);
382 
383     if (lss.currentlevel > 0)
384       gui.makeClickable(prevbutton, AA_PICK_PRAC_LVL, idxprev);
385 
386     gui.makeClickable(
387       gui.addLabel(10.0f, 10.0f, "back", PTEXT_HZA_LEFT | PTEXT_VTA_BOTTOM, 40.0f),
388       AA_PICK_PRAC, lss.currentevent);
389 
390     gui.makeClickable(
391         gui.addLabel(400.0f, 10.0f, "best times", PTEXT_HZA_CENTER | PTEXT_VTA_BOTTOM, 40.0f),
392         AA_BSHOWTIMES_PRAC, 0);
393 
394     gui.addLabel(790.0f, 570.0f, events[lss.currentevent].name + " (" +
395         PUtil::formatInt(lss.currentlevel + 1) + '/' + PUtil::formatInt(events[lss.currentevent].levels.size()) + ')',
396         PTEXT_HZA_RIGHT | PTEXT_VTA_CENTER, 20.0f, LabelStyle::Weak);
397     gui.addLabel(100.0f,500.0f, events[lss.currentevent].levels[lss.currentlevel].name,
398         PTEXT_HZA_LEFT | PTEXT_VTA_TOP, 35.0f, LabelStyle::Header);
399     gui.addLabel(100.0f,462.5f,
400         std::string("by ") + events[lss.currentevent].levels[lss.currentlevel].author,
401         PTEXT_HZA_LEFT | PTEXT_VTA_TOP, 20.0f, LabelStyle::Weak);
402     gui.addLabel(700.0f, 462.5f, events[lss.currentevent].levels[lss.currentlevel].targettimeshort,
403         PTEXT_HZA_RIGHT | PTEXT_VTA_TOP, 20.0f);
404 
405     if (events[lss.currentevent].levels[lss.currentlevel].tex_screenshot != nullptr)
406         gui.addGraphic(100, 175, 250.0f * 4/3, 250, events[lss.currentevent].levels[lss.currentlevel].tex_screenshot);
407     else
408         gui.addGraphic(100, 175, 250.0f * 4/3, 250, tex_race_no_screenshot);
409 
410     if (events[lss.currentevent].levels[lss.currentlevel].tex_minimap != nullptr)
411         gui.addGraphic(450, 175, 250, 250, events[lss.currentevent].levels[lss.currentlevel].tex_minimap);
412     else
413         gui.addGraphic(450, 175, 250, 250, tex_race_no_minimap);
414 
415     gui.addLabel(100.0f,150.0f, events[lss.currentevent].levels[lss.currentlevel].description,
416         PTEXT_HZA_LEFT | PTEXT_VTA_TOP, 20.0f);
417     gui.makeDefault(
418       gui.makeClickable(
419         gui.addLabel(790.0f,10.0f, "race", PTEXT_HZA_RIGHT | PTEXT_VTA_BOTTOM, 40.0f),
420         AA_START_PRAC, 0));
421     break;
422   }
423   case AM_TOP_LVL:
424     {
425       gui.makeClickable(
426         gui.addLabel(10.0f, 10.0f, "back", PTEXT_HZA_LEFT | PTEXT_VTA_BOTTOM, 40.0f),
427         AA_GO_TOP, 0);
428       gui.addLabel(100.0f,470.0f, "Choose Race:", PTEXT_HZA_LEFT | PTEXT_VTA_CENTER, 30.0f, LabelStyle::Header);
429 
430       int firstraceindex = index;
431       const int prevbutton = gui.addGraphic(20.0f, 275.0f, 50.0f, 50.0f, tex_button_prev, GraphicStyle::Button);
432       const int nextbutton = gui.addGraphic(730.0f, 275.0f, 50.0f, 50.0f, tex_button_next, GraphicStyle::Button);
433 
434       if (firstraceindex > 0) {
435         gui.makeClickable(prevbutton, AA_GO_LVL, firstraceindex - MAX_RACES_ON_SCREEN);
436       }
437 
438       int racesonscreencount = levels.size() - firstraceindex;
439 
440       if (racesonscreencount > MAX_RACES_ON_SCREEN) {
441         racesonscreencount = MAX_RACES_ON_SCREEN;
442         gui.makeClickable(nextbutton, AA_GO_LVL, firstraceindex + MAX_RACES_ON_SCREEN);
443       }
444       std::stringstream racecountmsg;
445       racecountmsg << "single races " << firstraceindex + 1 << '-' << firstraceindex + racesonscreencount << '/' << levels.size();
446       gui.addLabel(790.0f, 570.0f, racecountmsg.str(), PTEXT_HZA_RIGHT | PTEXT_VTA_CENTER, 20.0f, LabelStyle::Weak);
447 
448         gui.addLabel(700, 470, "timelimit", PTEXT_HZA_RIGHT | PTEXT_VTA_CENTER, 20);
449 
450       for (int i = firstraceindex; i < firstraceindex + racesonscreencount; i++) {
451         gui.makeClickable(
452           gui.addLabel(100.0f, 440.0f - (float)(i - firstraceindex) * 30.0f,
453           levels[i].name, PTEXT_HZA_LEFT | PTEXT_VTA_TOP, 25.0f, LabelStyle::List),
454           AA_PICK_LVL, i);
455 
456         gui.addLabel(700.0f, 440.0f - (float)(i - firstraceindex) * 30.0f,
457             levels[i].targettimeshort, PTEXT_HZA_RIGHT | PTEXT_VTA_TOP, 25.0f, LabelStyle::List);
458       }
459     }
460     break;
461   case AM_TOP_LVL_PREP:
462   {
463       const int prevbutton = gui.addGraphic(20.0f, 275.0f, 50.0f, 50.0f, tex_button_prev, GraphicStyle::Button);
464       const int nextbutton = gui.addGraphic(730.0f, 275.0f, 50.0f, 50.0f, tex_button_next, GraphicStyle::Button);
465 
466       int idxnext = lss.currentlevel + 1;
467       int idxprev = lss.currentlevel - 1;
468 
469       CLAMP(idxnext, 0, static_cast<int> (levels.size() - 1));
470       CLAMP(idxprev, 0, static_cast<int> (levels.size() - 1));
471 
472       if (lss.currentlevel < static_cast<int> (levels.size() - 1))
473         gui.makeClickable(nextbutton, AA_PICK_LVL, idxnext);
474 
475       if (lss.currentlevel > 0)
476         gui.makeClickable(prevbutton, AA_PICK_LVL, idxprev);
477 
478     gui.makeClickable(
479       gui.addLabel(10.0f, 10.0f, "back", PTEXT_HZA_LEFT | PTEXT_VTA_BOTTOM, 40.0f),
480       AA_GO_LVL, (lss.currentlevel / MAX_RACES_ON_SCREEN) * MAX_RACES_ON_SCREEN);
481 
482     gui.makeClickable(
483         gui.addLabel(400.0f, 10.0f, "best times", PTEXT_HZA_CENTER | PTEXT_VTA_BOTTOM, 40.0f),
484         AA_BSHOWTIMES_LVL, 0);
485 
486     std::stringstream racenummsg;
487 
488     racenummsg << "single race " << lss.currentlevel+1 << '/' << levels.size();
489     gui.addLabel(790.0f,570.0f, racenummsg.str(), PTEXT_HZA_RIGHT | PTEXT_VTA_CENTER, 20.0f, LabelStyle::Weak);
490     gui.addLabel(100.0f,500.0f, levels[lss.currentlevel].name, PTEXT_HZA_LEFT | PTEXT_VTA_TOP, 35.0f, LabelStyle::Header);
491     gui.addLabel(100.0f,462.5f,
492         std::string("by ") + levels[lss.currentlevel].author,
493         PTEXT_HZA_LEFT | PTEXT_VTA_TOP, 20.0f, LabelStyle::Weak);
494     gui.addLabel(700.0f, 462.5f, levels[lss.currentlevel].targettimeshort, PTEXT_HZA_RIGHT | PTEXT_VTA_TOP, 20.0f);
495 
496     if (levels[lss.currentlevel].tex_screenshot != nullptr)
497         gui.addGraphic(100, 175, 250.0f * 4/3, 250, levels[lss.currentlevel].tex_screenshot);
498     else
499         gui.addGraphic(100, 175, 250.0f * 4/3, 250, tex_race_no_screenshot);
500 
501     if (levels[lss.currentlevel].tex_minimap != nullptr)
502         gui.addGraphic(450, 175, 250, 250, levels[lss.currentlevel].tex_minimap);
503     else
504         gui.addGraphic(450, 175, 250, 250, tex_race_no_minimap);
505 
506     gui.addLabel(100.0f,150.0f, levels[lss.currentlevel].description, PTEXT_HZA_LEFT | PTEXT_VTA_TOP, 20.0f);
507     gui.makeDefault(
508       gui.makeClickable(
509         gui.addLabel(790.0f,10.0f, "race", PTEXT_HZA_RIGHT | PTEXT_VTA_BOTTOM, 40.0f),
510         AA_START_LVL, 0));
511   }
512     break;
513   case AM_TOP_QUIT:
514     gui.addLabel(400.0f,350.0f, "Really quit?", PTEXT_HZA_CENTER | PTEXT_VTA_TOP, 40.0f, LabelStyle::Marked);
515     gui.makeClickable(
516       gui.addLabel(300.0f,250.0f, "Yes", PTEXT_HZA_CENTER | PTEXT_VTA_TOP, 40.0f),
517       AA_QUIT_CONFIRM, 0);
518     gui.makeClickable(
519       gui.addLabel(500.0f,250.0f, "No", PTEXT_HZA_CENTER | PTEXT_VTA_TOP, 40.0f),
520       AA_GO_TOP, 0);
521     break;
522 
523     case AM_TOP_LVL_TIMES:
524     {
525         gui.addLabel(100.0f,500.0f, levels[lss.currentlevel].name, PTEXT_HZA_LEFT | PTEXT_VTA_TOP, 35.0f, LabelStyle::Header);
526         gui.addLabel(100.0f,462.5f,
527             std::string("by ") + levels[lss.currentlevel].author,
528             PTEXT_HZA_LEFT | PTEXT_VTA_TOP, 20.0f, LabelStyle::Weak);
529         gui.addLabel(700.0f, 462.5f, levels[lss.currentlevel].targettime, PTEXT_HZA_RIGHT | PTEXT_VTA_TOP, 20.0f);
530         gui.makeClickable(
531             gui.addLabel(10.0f, 10.0f, "back", PTEXT_HZA_LEFT | PTEXT_VTA_BOTTOM, 40.0f),
532             AA_PICK_LVL, lss.currentlevel);
533 
534         current_times = best_times.getCurrentTimesHL(hs_sort_method);
535 
536         int first_time_index = index;
537 
538         const int prevbutton = gui.addGraphic(20.0f, 275.0f, 50.0f, 50.0f, tex_button_prev, GraphicStyle::Button);
539         const int nextbutton = gui.addGraphic(730.0f, 275.0f, 50.0f, 50.0f, tex_button_next, GraphicStyle::Button);
540 
541         if (first_time_index > 0)
542             gui.makeClickable(prevbutton, AA_SHOWTIMES_LVL, first_time_index - MAX_TIMES_ON_SCREEN);
543 
544         int times_on_screen_count = current_times.size() - first_time_index;
545 
546         if (times_on_screen_count > MAX_TIMES_ON_SCREEN)
547         {
548             times_on_screen_count = MAX_TIMES_ON_SCREEN;
549             gui.makeClickable(nextbutton, AA_SHOWTIMES_LVL, first_time_index + MAX_TIMES_ON_SCREEN);
550         }
551 
552         std::stringstream times_count_msg;
553 
554         times_count_msg << "best times " << first_time_index + 1 << '-'
555             << first_time_index + times_on_screen_count << '/' << current_times.size();
556         gui.addLabel(790.0f, 570.0f, times_count_msg.str(), PTEXT_HZA_RIGHT | PTEXT_VTA_CENTER, 20.0f, LabelStyle::Weak);
557 
558         // column buttons
559         gui.makeClickable(
560             gui.addLabel(XTIMES_PLAYERNAME, 420.0f, "player", PTEXT_HZA_LEFT | PTEXT_VTA_TOP, 20.0f),
561             AA_SORT_BY_PLAYERNAME, 0);
562         gui.makeClickable(
563             gui.addLabel(XTIMES_CARNAME, 420.0f, "car", PTEXT_HZA_LEFT | PTEXT_VTA_TOP, 20.0f),
564             AA_SORT_BY_CARNAME, 0);
565         gui.makeClickable(
566             gui.addLabel(XTIMES_CARCLASS, 420.0f, "class", PTEXT_HZA_LEFT | PTEXT_VTA_TOP, 20.0f),
567             AA_SORT_BY_CARCLASS, 0);
568         gui.makeClickable(
569             gui.addLabel(XTIMES_TOTALTIME, 420.0f, "time", PTEXT_HZA_RIGHT | PTEXT_VTA_TOP, 20.0f),
570             AA_SORT_BY_TOTALTIME, 0);
571 
572         for (int i = first_time_index; i < first_time_index + times_on_screen_count; ++i)
573         {
574             LabelStyle ls;
575 
576             if (current_times[i].highlighted)
577                 ls = LabelStyle::Marked;
578             else
579                 ls = LabelStyle::List;
580 
581             gui.addLabel(XTIMES_PLAYERNAME, 395.0f - (float)(i - first_time_index) * 25.0f,
582                 std::to_string(current_times[i].place) + ". ", PTEXT_HZA_RIGHT | PTEXT_VTA_TOP, 20.0f, ls);
583             gui.addLabel(XTIMES_PLAYERNAME, 395.0f - (float)(i - first_time_index) * 25.0f,
584                 current_times[i].rd.playername.substr(0, 14), PTEXT_HZA_LEFT | PTEXT_VTA_TOP, 20.0f, ls);
585             gui.addLabel(XTIMES_CARNAME, 395.0f - (float)(i - first_time_index) * 25.0f,
586                 current_times[i].rd.carname.substr(0, 9), PTEXT_HZA_LEFT | PTEXT_VTA_TOP, 20.0f, ls);
587             gui.addLabel(XTIMES_CARCLASS, 395.0f - (float)(i - first_time_index) * 25.0f,
588                 current_times[i].rd.carclass.substr(0, 8), PTEXT_HZA_LEFT | PTEXT_VTA_TOP, 20.0f, ls);
589             gui.addLabel(XTIMES_TOTALTIME, 395.0f - (float)(i - first_time_index) * 25.0f,
590                 PUtil::formatTime(current_times[i].rd.totaltime), PTEXT_HZA_RIGHT | PTEXT_VTA_TOP, 20.0f, ls);
591         }
592 
593         break;
594     }
595 
596     case AM_TOP_LVL_BTIMES:
597     {
598         gui.addLabel(100.0f,500.0f, levels[lss.currentlevel].name, PTEXT_HZA_LEFT | PTEXT_VTA_TOP, 35.0f, LabelStyle::Header);
599         gui.addLabel(100.0f,462.5f,
600             std::string("by ") + levels[lss.currentlevel].author,
601             PTEXT_HZA_LEFT | PTEXT_VTA_TOP, 20.0f, LabelStyle::Weak);
602         gui.addLabel(700.0f, 462.5f, levels[lss.currentlevel].targettime, PTEXT_HZA_RIGHT | PTEXT_VTA_TOP, 20.0f);
603         gui.makeClickable(
604             gui.addLabel(10.0f, 10.0f, "back", PTEXT_HZA_LEFT | PTEXT_VTA_BOTTOM, 40.0f),
605             AA_PICK_LVL, lss.currentlevel);
606 
607         current_times = best_times.getCurrentTimes("", hs_sort_method);
608 
609         int first_time_index = index;
610 
611         const int prevbutton = gui.addGraphic(20.0f, 275.0f, 50.0f, 50.0f, tex_button_prev, GraphicStyle::Button);
612         const int nextbutton = gui.addGraphic(730.0f, 275.0f, 50.0f, 50.0f, tex_button_next, GraphicStyle::Button);
613 
614         // NOTE: not using AA_BSHOWTIMES_LVL intentionally!
615         if (first_time_index > 0)
616             gui.makeClickable(prevbutton, AA_SHOWTIMES_LVL, first_time_index - MAX_TIMES_ON_SCREEN);
617 
618         int times_on_screen_count = current_times.size() - first_time_index;
619 
620         // NOTE: not using AA_BSHOWTIMES_LVL intentionally!
621         if (times_on_screen_count > MAX_TIMES_ON_SCREEN)
622         {
623             times_on_screen_count = MAX_TIMES_ON_SCREEN;
624             gui.makeClickable(nextbutton, AA_SHOWTIMES_LVL, first_time_index + MAX_TIMES_ON_SCREEN);
625         }
626 
627         std::stringstream times_count_msg;
628 
629         times_count_msg << "best times";
630 
631         if (!current_times.empty())
632             times_count_msg << ' ' << first_time_index + 1 << '-'
633             << first_time_index + times_on_screen_count << '/' << current_times.size();
634 
635         gui.addLabel(790.0f, 570.0f, times_count_msg.str(), PTEXT_HZA_RIGHT | PTEXT_VTA_CENTER, 20.0f, LabelStyle::Weak);
636 
637         // column buttons
638         gui.makeClickable(
639             gui.addLabel(XTIMES_PLAYERNAME, 420.0f, "player", PTEXT_HZA_LEFT | PTEXT_VTA_TOP, 20.0f),
640             AA_SORT_BY_PLAYERNAME, 0);
641         gui.makeClickable(
642             gui.addLabel(XTIMES_CARNAME, 420.0f, "car", PTEXT_HZA_LEFT | PTEXT_VTA_TOP, 20.0f),
643             AA_SORT_BY_CARNAME, 0);
644         gui.makeClickable(
645             gui.addLabel(XTIMES_CARCLASS, 420.0f, "class", PTEXT_HZA_LEFT | PTEXT_VTA_TOP, 20.0f),
646             AA_SORT_BY_CARCLASS, 0);
647         gui.makeClickable(
648             gui.addLabel(XTIMES_TOTALTIME, 420.0f, "time", PTEXT_HZA_RIGHT | PTEXT_VTA_TOP, 20.0f),
649             AA_SORT_BY_TOTALTIME, 0);
650 
651         for (int i = first_time_index; i < first_time_index + times_on_screen_count; ++i)
652         {
653             gui.addLabel(XTIMES_PLAYERNAME, 395.0f - (float)(i - first_time_index) * 25.0f,
654                 std::to_string(current_times[i].place) + ". ", PTEXT_HZA_RIGHT | PTEXT_VTA_TOP, 20.0f, LabelStyle::List);
655             gui.addLabel(XTIMES_PLAYERNAME, 395.0f - (float)(i - first_time_index) * 25.0f,
656                 current_times[i].rd.playername.substr(0, 14), PTEXT_HZA_LEFT | PTEXT_VTA_TOP, 20.0f, LabelStyle::List);
657             gui.addLabel(XTIMES_CARNAME, 395.0f - (float)(i - first_time_index) * 25.0f,
658                 current_times[i].rd.carname.substr(0, 9), PTEXT_HZA_LEFT | PTEXT_VTA_TOP, 20.0f, LabelStyle::List);
659             gui.addLabel(XTIMES_CARCLASS, 395.0f - (float)(i - first_time_index) * 25.0f,
660                 current_times[i].rd.carclass.substr(0, 8), PTEXT_HZA_LEFT | PTEXT_VTA_TOP, 20.0f, LabelStyle::List);
661             gui.addLabel(XTIMES_TOTALTIME, 395.0f - (float)(i - first_time_index) * 25.0f,
662                 PUtil::formatTime(current_times[i].rd.totaltime), PTEXT_HZA_RIGHT | PTEXT_VTA_TOP, 20.0f, LabelStyle::List);
663         }
664 
665         break;
666     }
667 
668     case AM_TOP_PRAC_TIMES:
669     {
670         gui.addLabel(100.0f,500.0f, events[lss.currentevent].levels[lss.currentlevel].name,
671             PTEXT_HZA_LEFT | PTEXT_VTA_TOP, 35.0f, LabelStyle::Header);
672         gui.addLabel(100.0f,462.5f,
673             std::string("by ") + events[lss.currentevent].levels[lss.currentlevel].author,
674             PTEXT_HZA_LEFT | PTEXT_VTA_TOP, 20.0f, LabelStyle::Weak);
675         gui.addLabel(700.0f, 462.5f, events[lss.currentevent].levels[lss.currentlevel].targettime,
676             PTEXT_HZA_RIGHT | PTEXT_VTA_TOP, 20.0f);
677         gui.makeClickable(
678             gui.addLabel(10.0f, 10.0f, "back", PTEXT_HZA_LEFT | PTEXT_VTA_BOTTOM, 40.0f),
679             AA_PICK_PRAC_LVL, lss.currentlevel);
680 
681         current_times = best_times.getCurrentTimesHL(hs_sort_method);
682 
683         int first_time_index = index;
684 
685         const int prevbutton = gui.addGraphic(20.0f, 275.0f, 50.0f, 50.0f, tex_button_prev, GraphicStyle::Button);
686         const int nextbutton = gui.addGraphic(730.0f, 275.0f, 50.0f, 50.0f, tex_button_next, GraphicStyle::Button);
687 
688         if (first_time_index > 0)
689             gui.makeClickable(prevbutton, AA_SHOWTIMES_PRAC, first_time_index - MAX_TIMES_ON_SCREEN);
690 
691         int times_on_screen_count = current_times.size() - first_time_index;
692 
693         if (times_on_screen_count > MAX_TIMES_ON_SCREEN)
694         {
695             times_on_screen_count = MAX_TIMES_ON_SCREEN;
696             gui.makeClickable(nextbutton, AA_SHOWTIMES_PRAC, first_time_index + MAX_TIMES_ON_SCREEN);
697         }
698 
699         std::stringstream times_count_msg;
700 
701         times_count_msg << "best times " << first_time_index + 1 << '-'
702             << first_time_index + times_on_screen_count << '/' << current_times.size();
703         gui.addLabel(790.0f, 570.0f, times_count_msg.str(), PTEXT_HZA_RIGHT | PTEXT_VTA_CENTER, 20.0f, LabelStyle::Weak);
704 
705         // column buttons
706         gui.makeClickable(
707             gui.addLabel(XTIMES_PLAYERNAME, 420.0f, "player", PTEXT_HZA_LEFT | PTEXT_VTA_TOP, 20.0f),
708             AA_SORT_BY_PLAYERNAME, 0);
709         gui.makeClickable(
710             gui.addLabel(XTIMES_CARNAME, 420.0f, "car", PTEXT_HZA_LEFT | PTEXT_VTA_TOP, 20.0f),
711             AA_SORT_BY_CARNAME, 0);
712         gui.makeClickable(
713             gui.addLabel(XTIMES_CARCLASS, 420.0f, "class", PTEXT_HZA_LEFT | PTEXT_VTA_TOP, 20.0f),
714             AA_SORT_BY_CARCLASS, 0);
715         gui.makeClickable(
716             gui.addLabel(XTIMES_TOTALTIME, 420.0f, "time", PTEXT_HZA_RIGHT | PTEXT_VTA_TOP, 20.0f),
717             AA_SORT_BY_TOTALTIME, 0);
718 
719         for (int i = first_time_index; i < first_time_index + times_on_screen_count; ++i)
720         {
721             LabelStyle ls;
722 
723             if (current_times[i].highlighted)
724                 ls = LabelStyle::Marked;
725             else
726                 ls = LabelStyle::List;
727 
728             gui.addLabel(XTIMES_PLAYERNAME, 395.0f - (float)(i - first_time_index) * 25.0f,
729                 std::to_string(current_times[i].place) + ". ", PTEXT_HZA_RIGHT | PTEXT_VTA_TOP, 20.0f, ls);
730             gui.addLabel(XTIMES_PLAYERNAME, 395.0f - (float)(i - first_time_index) * 25.0f,
731                 current_times[i].rd.playername.substr(0, 14), PTEXT_HZA_LEFT | PTEXT_VTA_TOP, 20.0f, ls);
732             gui.addLabel(XTIMES_CARNAME, 395.0f - (float)(i - first_time_index) * 25.0f,
733                 current_times[i].rd.carname.substr(0, 9), PTEXT_HZA_LEFT | PTEXT_VTA_TOP, 20.0f, ls);
734             gui.addLabel(XTIMES_CARCLASS, 395.0f - (float)(i - first_time_index) * 25.0f,
735                 current_times[i].rd.carclass.substr(0, 8), PTEXT_HZA_LEFT | PTEXT_VTA_TOP, 20.0f, ls);
736             gui.addLabel(XTIMES_TOTALTIME, 395.0f - (float)(i - first_time_index) * 25.0f,
737                 PUtil::formatTime(current_times[i].rd.totaltime), PTEXT_HZA_RIGHT | PTEXT_VTA_TOP, 20.0f, ls);
738         }
739 
740         break;
741     }
742 
743     case AM_TOP_PRAC_BTIMES:
744     {
745         gui.addLabel(100.0f,500.0f, events[lss.currentevent].levels[lss.currentlevel].name,
746             PTEXT_HZA_LEFT | PTEXT_VTA_TOP, 35.0f, LabelStyle::Header);
747         gui.addLabel(100.0f,462.5f,
748             std::string("by ") + events[lss.currentevent].levels[lss.currentlevel].author,
749             PTEXT_HZA_LEFT | PTEXT_VTA_TOP, 20.0f, LabelStyle::Weak);
750         gui.addLabel(700.0f, 462.5f, events[lss.currentevent].levels[lss.currentlevel].targettime,
751             PTEXT_HZA_RIGHT | PTEXT_VTA_TOP, 20.0f);
752         gui.makeClickable(
753             gui.addLabel(10.0f, 10.0f, "back", PTEXT_HZA_LEFT | PTEXT_VTA_BOTTOM, 40.0f),
754             AA_PICK_PRAC_LVL, lss.currentlevel);
755 
756         current_times = best_times.getCurrentTimes("", hs_sort_method);
757 
758         int first_time_index = index;
759 
760         const int prevbutton = gui.addGraphic(20.0f, 275.0f, 50.0f, 50.0f, tex_button_prev, GraphicStyle::Button);
761         const int nextbutton = gui.addGraphic(730.0f, 275.0f, 50.0f, 50.0f, tex_button_next, GraphicStyle::Button);
762 
763         // NOTE: not using AA_BSHOWTIMES_PRAC intentionally!
764         if (first_time_index > 0)
765             gui.makeClickable(prevbutton, AA_SHOWTIMES_PRAC, first_time_index - MAX_TIMES_ON_SCREEN);
766 
767         int times_on_screen_count = current_times.size() - first_time_index;
768 
769         // NOTE: not using AA_BSHOWTIMES_PRAC intentionally!
770         if (times_on_screen_count > MAX_TIMES_ON_SCREEN)
771         {
772             times_on_screen_count = MAX_TIMES_ON_SCREEN;
773             gui.makeClickable(nextbutton, AA_SHOWTIMES_PRAC, first_time_index + MAX_TIMES_ON_SCREEN);
774         }
775 
776         std::stringstream times_count_msg;
777 
778         times_count_msg << "best times";
779 
780         if (!current_times.empty())
781             times_count_msg << ' ' << first_time_index + 1 << '-'
782             << first_time_index + times_on_screen_count << '/' << current_times.size();
783 
784         gui.addLabel(790.0f, 570.0f, times_count_msg.str(), PTEXT_HZA_RIGHT | PTEXT_VTA_CENTER, 20.0f, LabelStyle::Weak);
785 
786         // column buttons
787         gui.makeClickable(
788             gui.addLabel(XTIMES_PLAYERNAME, 420.0f, "player", PTEXT_HZA_LEFT | PTEXT_VTA_TOP, 20.0f),
789             AA_SORT_BY_PLAYERNAME, 0);
790         gui.makeClickable(
791             gui.addLabel(XTIMES_CARNAME, 420.0f, "car", PTEXT_HZA_LEFT | PTEXT_VTA_TOP, 20.0f),
792             AA_SORT_BY_CARNAME, 0);
793         gui.makeClickable(
794             gui.addLabel(XTIMES_CARCLASS, 420.0f, "class", PTEXT_HZA_LEFT | PTEXT_VTA_TOP, 20.0f),
795             AA_SORT_BY_CARCLASS, 0);
796         gui.makeClickable(
797             gui.addLabel(XTIMES_TOTALTIME, 420.0f, "time", PTEXT_HZA_RIGHT | PTEXT_VTA_TOP, 20.0f),
798             AA_SORT_BY_TOTALTIME, 0);
799 
800         for (int i = first_time_index; i < first_time_index + times_on_screen_count; ++i)
801         {
802             gui.addLabel(XTIMES_PLAYERNAME, 395.0f - (float)(i - first_time_index) * 25.0f,
803                 std::to_string(current_times[i].place) + ". ", PTEXT_HZA_RIGHT | PTEXT_VTA_TOP, 20.0f, LabelStyle::List);
804             gui.addLabel(XTIMES_PLAYERNAME, 395.0f - (float)(i - first_time_index) * 25.0f,
805                 current_times[i].rd.playername.substr(0, 14), PTEXT_HZA_LEFT | PTEXT_VTA_TOP, 20.0f, LabelStyle::List);
806             gui.addLabel(XTIMES_CARNAME, 395.0f - (float)(i - first_time_index) * 25.0f,
807                 current_times[i].rd.carname.substr(0, 9), PTEXT_HZA_LEFT | PTEXT_VTA_TOP, 20.0f, LabelStyle::List);
808             gui.addLabel(XTIMES_CARCLASS, 395.0f - (float)(i - first_time_index) * 25.0f,
809                 current_times[i].rd.carclass.substr(0, 8), PTEXT_HZA_LEFT | PTEXT_VTA_TOP, 20.0f, LabelStyle::List);
810             gui.addLabel(XTIMES_TOTALTIME, 395.0f - (float)(i - first_time_index) * 25.0f,
811                 PUtil::formatTime(current_times[i].rd.totaltime), PTEXT_HZA_RIGHT | PTEXT_VTA_TOP, 20.0f, LabelStyle::List);
812         }
813 
814         break;
815     }
816 
817   default:
818     gui.addLabel(400.0f,300.0f, "Error in menu system, sorry", PTEXT_HZA_CENTER | PTEXT_VTA_TOP, 30.0f, LabelStyle::Marked);
819     gui.makeClickable(
820       gui.addLabel(400.0f,150.0f, "Go to top menu", PTEXT_HZA_CENTER | PTEXT_VTA_TOP, 30.0f),
821       AA_GO_TOP, 0);
822     break;
823   }
824 
825   //gui.doLayout();
826 }
827 
finishRace(Gamefinish state,float coursetime)828 void MainApp::finishRace(Gamefinish state, float coursetime)
829 {
830 	switch (lss.state)
831 	{
832 		case AM_TOP_EVT_PREP:
833 			switch (state)
834 			{
835 				case Gamefinish::pass:
836 					lss.leveltimes.resize(events[lss.currentevent].levels.size(), 0.0f);
837 					lss.leveltimes[lss.currentlevel] += coursetime;
838 					lss.totaltime += coursetime;
839 					lss.currentlevel++;
840 
841 					// event was completed so save unlock data
842 					if (lss.currentlevel >= (int)events[lss.currentevent].levels.size())
843 					{
844 						for (const std::string &s: events[lss.currentevent].unlocks)
845 							best_times.addNewUnlock(s);
846 
847 						player_unlocks = best_times.getUnlockData();
848 						best_times.skipSavePlayer();
849 					}
850 					break;
851 
852 				case Gamefinish::fail:
853 					lss.totaltime += coursetime;
854 					lss.livesleft--;
855 					break;
856 
857 				default:
858 					break;
859 			}
860 			levelScreenAction(AA_RESUME, 0);
861 			break;
862 
863 		case AM_TOP_PRAC_SEL_PREP:
864 			levelScreenAction(AA_PICK_PRAC_LVL, lss.currentlevel);
865 			break;
866 
867 		case AM_TOP_LVL_PREP:
868 			// Calculate the index of first level in the page by truncating the current level index to the nearest 10
869 			//levelScreenAction(AA_GO_LVL, (lss.currentlevel / MAX_RACES_ON_SCREEN) * MAX_RACES_ON_SCREEN );
870 			levelScreenAction(AA_PICK_LVL, lss.currentlevel);
871 			break;
872 
873 		case AM_TOP_LVL_TIMES:
874 			levelScreenAction(AA_SHOWTIMES_LVL, 0);
875 			break;
876 
877 		case AM_TOP_PRAC_TIMES:
878 			levelScreenAction(AA_SHOWTIMES_PRAC, 0);
879 			break;
880 
881 		default:
882 			PUtil::outLog() << "Race finished in invalid state " << lss.state << std::endl;
883 			break;
884 	}
885 }
886 
tickStateLevel(float delta)887 void MainApp::tickStateLevel(float delta)
888 {
889   gui.tick(delta);
890 }
891 
892 // TODO: fix this code
cursorMoveEvent(int posx,int posy)893 void MainApp::cursorMoveEvent(int posx, int posy)
894 {
895   if (appstate != AS_LEVEL_SCREEN) return;
896 
897   const GLdouble margin = (800.0 - 600.0 * cx / cy) / 2.0;
898 
899   gui.setCursorPos(
900     (float)posx / (float)getWidth() * (600.0 * cx / cy) + margin,
901     (1.0f - (float)posy / (float)getHeight()) * 600.0f);
902 }
903 
mouseButtonEvent(const SDL_MouseButtonEvent & mbe)904 void MainApp::mouseButtonEvent(const SDL_MouseButtonEvent &mbe)
905 {
906   if (mbe.type != SDL_MOUSEBUTTONDOWN) return;
907 
908   switch (appstate) {
909   case AS_LEVEL_SCREEN:
910     break;
911   case AS_LOAD_3:
912     levelScreenAction(AA_INIT, 0);
913     break;
914   default:
915     return;
916   }
917 
918   // TODO: fix this code
919 
920   const GLdouble margin = (800.0 - 600.0 * cx / cy) / 2.0;
921 
922   int action, index;
923 
924   if (!gui.getClickAction(action, index)) return;
925 
926   levelScreenAction(action, index);
927 
928   gui.setCursorPos(
929     (float)mbe.x / (float)getWidth() * (600.0 * cx / cy) + margin,
930     (1.0f - (float)mbe.y / (float)getHeight()) * 600.0f);
931 }
932 
933 //
934 // TODO: use ActionLeft and ActionRight instead of hardcoded right/left arrow
935 //
handleLevelScreenKey(const SDL_KeyboardEvent & ke)936 void MainApp::handleLevelScreenKey(const SDL_KeyboardEvent &ke)
937 {
938   switch (ke.keysym.sym) {
939   case SDLK_ESCAPE:
940     switch(lss.state) {
941     case AM_TOP:
942       levelScreenAction(AA_GO_QUIT, 0);
943       break;
944     case AM_TOP_EVT_PREP:
945     case AM_TOP_EVT_ABANDON:
946       levelScreenAction(AA_GO_EVT, 0);
947       break;
948     case AM_TOP_PRAC_SEL:
949       levelScreenAction(AA_GO_PRAC, 0);
950       break;
951     case AM_TOP_PRAC_SEL_PREP:
952       levelScreenAction(AA_PICK_PRAC, lss.currentevent);
953       break;
954     case AM_TOP_LVL_PREP:
955       levelScreenAction(AA_GO_LVL, (lss.currentlevel / MAX_RACES_ON_SCREEN) * MAX_RACES_ON_SCREEN);
956       break;
957     case AM_TOP_QUIT:
958       quitGame();
959       break;
960     case AM_TOP_LVL_TIMES:
961     case AM_TOP_LVL_BTIMES:
962         levelScreenAction(AA_PICK_LVL, lss.currentlevel);
963         break;
964     case AM_TOP_PRAC_TIMES:
965     case AM_TOP_PRAC_BTIMES:
966         levelScreenAction(AA_PICK_PRAC_LVL, lss.currentlevel);
967         break;
968     default:
969       levelScreenAction(AA_GO_TOP, 0);
970       break;
971     }
972     break;
973   case SDLK_RETURN:
974   case SDLK_KP_ENTER: {
975       int data1, data2;
976 
977       if (gui.getDefaultAction(data1, data2))
978         levelScreenAction(data1, data2);
979     } break;
980 
981     case SDLK_LEFT:
982     {
983         int pidx; // previous index
984 
985         switch (lss.state)
986         {
987             case AM_TOP_LVL_PREP:
988             {
989                 pidx = lss.currentlevel - 1;
990                 CLAMP_LOWER(pidx, 0);
991                 levelScreenAction(AA_PICK_LVL, pidx);
992                 break;
993             }
994 
995             case AM_TOP_LVL:
996             {
997                 pidx = (lss.currentlevel / MAX_RACES_ON_SCREEN - 1) * MAX_RACES_ON_SCREEN;
998                 CLAMP_LOWER(pidx, 0);
999                 levelScreenAction(AA_GO_LVL, pidx);
1000                 break;
1001             }
1002 
1003             case AM_TOP_EVT:
1004             {
1005                 pidx = (lss.currentevent / MAX_RACES_ON_SCREEN - 1) * MAX_RACES_ON_SCREEN;
1006                 CLAMP_LOWER(pidx, 0);
1007                 levelScreenAction(AA_GO_EVT, pidx);
1008                 break;
1009             }
1010 
1011             case AM_TOP_PRAC:
1012             {
1013                 pidx = (lss.currentevent / MAX_RACES_ON_SCREEN - 1) * MAX_RACES_ON_SCREEN;
1014                 CLAMP_LOWER(pidx, 0);
1015                 levelScreenAction(AA_GO_PRAC, pidx);
1016                 break;
1017             }
1018 
1019             case AM_TOP_PRAC_SEL_PREP:
1020             {
1021                 pidx = lss.currentlevel - 1;
1022                 CLAMP_LOWER(pidx, 0);
1023                 levelScreenAction(AA_PICK_PRAC_LVL, pidx);
1024                 break;
1025             }
1026 
1027             case AM_TOP_LVL_TIMES:
1028             {
1029                 pidx = (lss.currentplayer / MAX_TIMES_ON_SCREEN - 1) * MAX_TIMES_ON_SCREEN;
1030                 CLAMP_LOWER(pidx, 0);
1031                 levelScreenAction(AA_SHOWTIMES_LVL, pidx);
1032                 break;
1033             }
1034 
1035             case AM_TOP_PRAC_TIMES:
1036             {
1037                 pidx = (lss.currentplayer / MAX_TIMES_ON_SCREEN - 1) * MAX_TIMES_ON_SCREEN;
1038                 CLAMP_LOWER(pidx, 0);
1039                 levelScreenAction(AA_SHOWTIMES_PRAC, pidx);
1040                 break;
1041             }
1042 
1043             case AM_TOP_LVL_BTIMES:
1044             {
1045                 // NOTE: not using AA_BSHOWTIMES_LVL intentionally!
1046                 pidx = (lss.currentplayer / MAX_TIMES_ON_SCREEN - 1) * MAX_TIMES_ON_SCREEN;
1047                 CLAMP_LOWER(pidx, 0);
1048                 levelScreenAction(AA_SHOWTIMES_LVL, pidx);
1049                 break;
1050             }
1051 
1052             case AM_TOP_PRAC_BTIMES:
1053             {
1054                 // NOTE: not using AA_BSHOWTIMES_PRAC intentionally!
1055                 pidx = (lss.currentplayer / MAX_TIMES_ON_SCREEN - 1) * MAX_TIMES_ON_SCREEN;
1056                 CLAMP_LOWER(pidx, 0);
1057                 levelScreenAction(AA_SHOWTIMES_PRAC, pidx);
1058                 break;
1059             }
1060         }
1061 
1062         break;
1063     }
1064 
1065     case SDLK_RIGHT:
1066     {
1067         int nidx; // next index
1068 
1069         switch (lss.state)
1070         {
1071             case AM_TOP_LVL_PREP:
1072             {
1073                 nidx = lss.currentlevel + 1;
1074                 CLAMP_UPPER(nidx, static_cast<int> (levels.size() - 1));
1075                 levelScreenAction(AA_PICK_LVL, nidx);
1076                 break;
1077             }
1078 
1079             case AM_TOP_LVL:
1080             {
1081                 if (levels.size() - lss.currentlevel <= MAX_RACES_ON_SCREEN)
1082                     break;
1083 
1084                 nidx = (lss.currentlevel / MAX_RACES_ON_SCREEN + 1) * MAX_RACES_ON_SCREEN;
1085                 CLAMP_UPPER(nidx, static_cast<int> (levels.size() - 1));
1086                 levelScreenAction(AA_GO_LVL, nidx);
1087                 break;
1088             }
1089 
1090             case AM_TOP_EVT:
1091             {
1092                 if (events.size() - lss.currentevent <= MAX_RACES_ON_SCREEN)
1093                     break;
1094 
1095                 nidx = (lss.currentevent / MAX_RACES_ON_SCREEN + 1) * MAX_RACES_ON_SCREEN;
1096                 CLAMP_UPPER(nidx, static_cast<int> (events.size() - 1));
1097                 levelScreenAction(AA_GO_EVT, nidx);
1098                 break;
1099             }
1100 
1101             case AM_TOP_PRAC:
1102             {
1103                 if (events.size() - lss.currentevent <= MAX_RACES_ON_SCREEN)
1104                     break;
1105 
1106                 nidx = (lss.currentevent / MAX_RACES_ON_SCREEN + 1) * MAX_RACES_ON_SCREEN;
1107                 CLAMP_UPPER(nidx, static_cast<int> (events.size() - 1));
1108                 levelScreenAction(AA_GO_PRAC, nidx);
1109                 break;
1110             }
1111 
1112             case AM_TOP_PRAC_SEL_PREP:
1113             {
1114                 nidx = lss.currentlevel + 1;
1115                 CLAMP_UPPER(nidx, static_cast<int> (events[lss.currentevent].levels.size() - 1));
1116                 levelScreenAction(AA_PICK_PRAC_LVL, nidx);
1117                 break;
1118             }
1119 
1120             case AM_TOP_LVL_TIMES:
1121             {
1122                 if (current_times.size() - lss.currentplayer <= MAX_TIMES_ON_SCREEN)
1123                     break;
1124 
1125                 nidx = (lss.currentplayer / MAX_TIMES_ON_SCREEN + 1) * MAX_TIMES_ON_SCREEN;
1126                 CLAMP_UPPER(nidx, static_cast<int> (current_times.size() - 1));
1127                 levelScreenAction(AA_SHOWTIMES_LVL, nidx);
1128                 break;
1129             }
1130 
1131             case AM_TOP_PRAC_TIMES:
1132             {
1133                 if (current_times.size() - lss.currentplayer <= MAX_TIMES_ON_SCREEN)
1134                     break;
1135 
1136                 nidx = (lss.currentplayer / MAX_TIMES_ON_SCREEN + 1) * MAX_TIMES_ON_SCREEN;
1137                 CLAMP_UPPER(nidx, static_cast<int> (current_times.size() - 1));
1138                 levelScreenAction(AA_SHOWTIMES_PRAC, nidx);
1139                 break;
1140             }
1141 
1142             case AM_TOP_LVL_BTIMES:
1143             {
1144                 if (current_times.size() - lss.currentplayer <= MAX_TIMES_ON_SCREEN)
1145                     break;
1146 
1147                 // NOTE: not using AA_BSHOWTIMES_LVL intentionally!
1148                 nidx = (lss.currentplayer / MAX_TIMES_ON_SCREEN + 1) * MAX_TIMES_ON_SCREEN;
1149                 CLAMP_UPPER(nidx, static_cast<int> (current_times.size() - 1));
1150                 levelScreenAction(AA_SHOWTIMES_LVL, nidx);
1151                 break;
1152             }
1153 
1154             case AM_TOP_PRAC_BTIMES:
1155             {
1156                 if (current_times.size() - lss.currentplayer <= MAX_TIMES_ON_SCREEN)
1157                     break;
1158 
1159                 // NOTE: not using AA_BSHOWTIMES_PRAC intentionally!
1160                 nidx = (lss.currentplayer / MAX_TIMES_ON_SCREEN + 1) * MAX_TIMES_ON_SCREEN;
1161                 CLAMP_UPPER(nidx, static_cast<int> (current_times.size() - 1));
1162                 levelScreenAction(AA_SHOWTIMES_PRAC, nidx);
1163                 break;
1164             }
1165         }
1166 
1167         break;
1168     }
1169 
1170   default:
1171     break;
1172   }
1173 }
1174 
1175 
renderStateLevel(float eyetranslation)1176 void MainApp::renderStateLevel(float eyetranslation)
1177 {
1178   eyetranslation = eyetranslation;
1179 
1180   glMatrixMode(GL_PROJECTION);
1181   glPushMatrix();
1182   glLoadIdentity();
1183 
1184   const GLdouble margin = (800.0 - 600.0 * cx / cy) / 2.0;
1185 
1186   glOrtho(margin, 600.0 * cx / cy + margin, 0.0, 600.0, -1.0, 1.0);
1187 
1188   glPushMatrix();
1189   glLoadIdentity();
1190   glOrtho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0);
1191 
1192   glMatrixMode(GL_MODELVIEW);
1193 
1194   // draw background image
1195 
1196   glBlendFunc(GL_ONE, GL_ZERO);
1197   glDisable(GL_DEPTH_TEST);
1198   glDisable(GL_FOG);
1199   glDisable(GL_LIGHTING);
1200 
1201   tex_splash_screen->bind();
1202 
1203   //glColor4f(0.0f, 0.0f, 0.2f, 1.0f); // make image dark blue
1204   glColor4f(1.0f, 1.0f, 1.0f, 1.0f); // use image's normal colors
1205   //glColor4f(0.5f, 0.5f, 0.5f, 1.0f); // make image darker
1206 
1207     glBegin(GL_QUADS);
1208     // the background image is square and cut out a piece based on aspect ratio
1209     // -------- if aspect ratio is larger than 4:3
1210     // if aspect ratio is larger than 1:1
1211     if ((float)getWidth()/(float)getHeight() > 1.0f)
1212     {
1213 
1214       // lower and upper offset based on aspect ratio
1215       float off_l = (1 - ((float)getHeight() / (float)getWidth())) / 2.f;
1216       float off_u = 1 - off_l;
1217       glTexCoord2f(1.0f,off_u); glVertex2f(1.0f, 1.0f);
1218       glTexCoord2f(0.0f,off_u); glVertex2f(-1.0f, 1.0f);
1219       glTexCoord2f(0.0f,off_l); glVertex2f(-1.0f, -1.0f);
1220       glTexCoord2f(1.0f,off_l); glVertex2f(1.0f, -1.0f);
1221     }
1222     // other cases (including 4:3, in which case off_l and off_u are = 1)
1223     else
1224     {
1225 
1226       float off_l = (1 - ((float)getWidth() / (float)getHeight())) / 2.f;
1227       float off_u = 1 - off_l;
1228       glTexCoord2f(off_u,1.0f); glVertex2f(1.0f, 1.0f);
1229       glTexCoord2f(off_l,1.0f); glVertex2f(-1.0f, 1.0f);
1230       glTexCoord2f(off_l,0.0f); glVertex2f(-1.0f, -1.0f);
1231       glTexCoord2f(off_u,0.0f); glVertex2f(1.0f, -1.0f);
1232     }
1233     glEnd();
1234 
1235 
1236   glMatrixMode(GL_PROJECTION);
1237   glPopMatrix();
1238 
1239   glMatrixMode(GL_MODELVIEW);
1240   // draw GUI
1241 
1242   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1243 
1244   glColor4f(1.0f, 1.0f, 1.0f, 0.2f);
1245 
1246   tex_fontSourceCodeOutlined->bind();
1247 
1248   glPushMatrix(); // 0
1249 
1250   gui.render();
1251 
1252   glPopMatrix(); // 0
1253 
1254   glBlendFunc(GL_ONE, GL_ZERO);
1255   glEnable(GL_DEPTH_TEST);
1256   glEnable(GL_FOG);
1257   glEnable(GL_LIGHTING);
1258 
1259   glMatrixMode(GL_PROJECTION);
1260   glPopMatrix();
1261   glMatrixMode(GL_MODELVIEW);
1262 }
1263 
1264 /// @see GuiWidgetColors
1265 #define LIST_OF_GUIWIDGETCOLORS_FIELDS  \
1266     X(normal)                           \
1267     X(click)                            \
1268     X(hover)                            \
1269     X(listnormal)                       \
1270     X(listclick)                        \
1271     X(listhover)                        \
1272     X(weak)                             \
1273     X(strong)                           \
1274     X(marked)                           \
1275     X(header)                           \
1276     X(bnormal)                          \
1277     X(bclick)                           \
1278     X(bhover)
1279 
1280 ///
1281 /// @brief Loads the widget colors from the specified filename.
1282 /// @todo Should check `sscanf()` calls for success.
1283 /// @param [in] filename    Name of the XML file to be read.
1284 /// @returns Whether or not the operation was successful.
1285 /// @retval true            The colors were read successfully.
1286 /// @retval false           Some (or none) colors could not be read.
1287 ///
loadColors(const std::string & filename)1288 bool Gui::loadColors(const std::string &filename)
1289 {
1290     if (PUtil::isDebugLevel(DEBUGLEVEL_TEST))
1291         PUtil::outLog() << "Loading GUI colors from \"" << filename << "\"\n";
1292 
1293     XMLDocument xmlfile;
1294     XMLElement *rootelem = PUtil::loadRootElement(xmlfile, filename, "menucolors");
1295 
1296     if (rootelem == nullptr)
1297         return false;
1298 
1299     bool r = true;
1300     const char *val;
1301 
1302 #define X(ColorField) \
1303     val = rootelem->Attribute(#ColorField); \
1304     if (val != nullptr) \
1305         sscanf(val, "%f, %f, %f, %f", &colors.ColorField.x, &colors.ColorField.y, &colors.ColorField.z, &colors.ColorField.w); \
1306     else \
1307         r = false;
1308 
1309     LIST_OF_GUIWIDGETCOLORS_FIELDS
1310 
1311 #undef X
1312     return r;
1313 }
1314 
1315 ///
1316 /// @brief GUI tick
1317 ///
tick(float delta)1318 void Gui::tick(float delta)
1319 {
1320   float decay = delta * 3.0f;
1321 
1322   // gradually unglow all widgets
1323   for (unsigned int i = 0; i < widget.size(); i++)
1324   {
1325     widget[i].glow -= decay;
1326     CLAMP_LOWER(widget[i].glow, 0.0f);
1327   }
1328 
1329   // keep the highlighted widget fully glowing
1330   if (highlight != -1) {
1331     widget[highlight].glow = 1.0f;
1332   }
1333 
1334   defflash = fmodf(defflash + delta * 50.0f, PI*2.0f);
1335 }
1336 
setCursorPos(float x,float y)1337 void Gui::setCursorPos(float x, float y)
1338 {
1339   highlight = -1;
1340 
1341   for (unsigned int i = 0; i < widget.size(); i++) {
1342 
1343     if (!widget[i].clickable) continue;
1344 
1345     if (x >= widget[i].pos.x &&
1346       y >= widget[i].pos.y &&
1347       x < widget[i].pos.x + widget[i].dims_min.x &&
1348       y < widget[i].pos.y + widget[i].dims_min.y)
1349       highlight = i;
1350   }
1351 }
1352 
getClickAction(int & data1,int & data2)1353 bool Gui::getClickAction(int &data1, int &data2)
1354 {
1355   if (highlight == -1) return false;
1356 
1357   data1 = widget[highlight].d1;
1358   data2 = widget[highlight].d2;
1359 
1360   return true;
1361 }
1362 
getDefaultAction(int & data1,int & data2)1363 bool Gui::getDefaultAction(int &data1, int &data2)
1364 {
1365   if (defwidget == -1) return false;
1366 
1367   data1 = widget[defwidget].d1;
1368   data2 = widget[defwidget].d2;
1369 
1370   return true;
1371 }
1372 
render()1373 void Gui::render()
1374 {
1375   for (unsigned int i = 0; i < widget.size(); i++) {
1376 
1377     switch(widget[i].type) {
1378     case GWT_LABEL: {
1379       vec4f colc;
1380       if (widget[i].clickable) {
1381         colc = INTERP(widget[i].colclick, widget[i].colhover, widget[i].glow);
1382       } else {
1383         colc = widget[i].colnormal;
1384       }
1385 
1386       if ((int)i == defwidget)
1387         colc += vec4f(0.1f, -0.1f, -0.1f, 0.0f) * sinf(defflash);
1388 
1389       glPushMatrix();
1390 
1391       vec2f ctr = widget[i].pos;
1392       glTranslatef(ctr.x, ctr.y, 0.0f);
1393 
1394       glScalef(widget[i].fontsize, widget[i].fontsize, 1.0f);
1395 
1396       fonttex->bind();
1397 
1398       glColor4fv(colc);
1399       ssRender->drawText(widget[i].text, PTEXT_HZA_LEFT | PTEXT_VTA_BOTTOM);
1400       glPopMatrix();
1401       } break;
1402 
1403     case GWT_GRAPHIC: {
1404       vec4f colc = vec4f(1.0f, 1.0f, 1.0f, 1.0f);
1405 
1406       if (widget[i].clickable) {
1407         colc = INTERP(widget[i].colclick, widget[i].colhover, widget[i].glow);
1408       } else {
1409         colc = widget[i].colnormal;
1410       }
1411 
1412       vec2f min = widget[i].pos;
1413       vec2f max = widget[i].pos + widget[i].dims_min;
1414 
1415       widget[i].tex->bind();
1416 
1417       glColor4fv(colc);
1418 
1419       glBegin(GL_QUADS);
1420       glTexCoord2f(0.0f, 0.0f); glVertex2f(min.x, min.y);
1421       glTexCoord2f(1.0f, 0.0f); glVertex2f(max.x, min.y);
1422       glTexCoord2f(1.0f, 1.0f); glVertex2f(max.x, max.y);
1423       glTexCoord2f(0.0f, 1.0f); glVertex2f(min.x, max.y);
1424       glEnd();
1425       } break;
1426     }
1427   }
1428 }
1429 
1430 // Widget tree stuff wasn't working properly, so I removed it for
1431 // now. If I need ultra-snazzy menus, I may finish this code
1432 
1433 #if 0
1434 
1435 void Gui::doLayout()
1436 {
1437   // Calculate sizes
1438   for (unsigned int i = 0; i < widget.size(); i++) {
1439     if (widget[i].parent == GWPARENT_NONE) {
1440       measureWidgetTree(i);
1441       placeWidgetTree(i);
1442     }
1443   }
1444 }
1445 
1446 void Gui::measureWidgetTree(int w)
1447 {
1448   widget[w].childcount = 0;
1449   widget[w].fillercount = 0;
1450 
1451   switch (widget[w].type) {
1452   default:
1453 
1454     widget[w].dims_measure = widget[w].dims_min;
1455 
1456     break;
1457 
1458   case GWT_CONTAINER: {
1459 
1460     vec2f measure = vec2f(0.0f, 0.0f);
1461 
1462     for (unsigned int i = 0; i < widget.size(); i++) {
1463       if (widget[i].parent == w) {
1464         measureWidgetTree(i);
1465 
1466         widget[w].childcount++;
1467 
1468         if (widget[i].type == GWT_FILLER)
1469           widget[w].fillercount++;
1470 
1471         if (widget[w].vert) {
1472           CLAMP_LOWER(measure.x, widget[i].dims_measure.x);
1473           measure.y += widget[i].dims_measure.y;
1474         } else {
1475           measure.x += widget[i].dims_measure.x;
1476           CLAMP_LOWER(measure.y, widget[i].dims_measure.y);
1477         }
1478       }
1479     }
1480 
1481     widget[w].dims_measure = measure;
1482 
1483     } break;
1484   }
1485 }
1486 
1487 void Gui::placeWidgetTree(int w)
1488 {
1489   if (widget[w].childcount <= 0) return;
1490 
1491   float extraspace = widget[w].vert ?
1492     - widget[w].dims_measure.x :
1493     - widget[w].dims_measure.y;
1494   if (widget[w].parent == GWPARENT_NONE) {
1495     extraspace += widget[w].vert ?
1496       widget[w].dims_min.x :
1497       widget[w].dims_min.y;
1498   }
1499 
1500   CLAMP_LOWER(extraspace, 0.0f);
1501 
1502   //CLAMP_LOWER(widget[w].dims_measure.x, widget[w].dims_min.x);
1503   //CLAMP_LOWER(widget[w].dims_measure.y, widget[w].dims_min.y);
1504 
1505   float
1506     addtofillers = 0.0f,
1507     addtochildren = 0.0f;
1508   /*
1509   if (widget[w].fillercount > 0)
1510     addtofillers = extraspace / (float)widget[w].fillercount;
1511   else
1512     addtochildren = extraspace / (float)widget[w].childcount;*/
1513 
1514   if (widget[w].vert) {
1515     float distrib = widget[w].pos.y;
1516 
1517     for (unsigned int i = 0; i < widget.size(); i++) {
1518       if (widget[i].parent == w) {
1519 
1520         widget[i].pos.x = widget[w].pos.x;
1521         widget[i].pos.y = distrib;
1522 
1523         widget[i].dims_measure.x = widget[w].dims_measure.x;
1524 
1525         switch (widget[i].type) {
1526         case GWT_FILLER:
1527           widget[i].dims_measure.y += addtofillers;
1528           break;
1529         case GWT_CONTAINER:
1530           widget[i].dims_measure.y += addtochildren;
1531           placeWidgetTree(i);
1532           break;
1533         default:
1534           widget[i].dims_measure.y += addtochildren;
1535           break;
1536         }
1537 
1538         distrib += widget[i].dims_measure.y;
1539       }
1540     }
1541   } else {
1542     float distrib = widget[w].pos.x;
1543 
1544     for (unsigned int i = 0; i < widget.size(); i++) {
1545       if (widget[i].parent == w) {
1546 
1547         widget[i].pos.x = distrib;
1548         widget[i].pos.y = widget[w].pos.y;
1549 
1550         widget[i].dims_measure.y = widget[w].dims_measure.y;
1551 
1552         switch (widget[i].type) {
1553         case GWT_FILLER:
1554           widget[i].dims_measure.x += addtofillers;
1555           break;
1556         case GWT_CONTAINER:
1557           widget[i].dims_measure.x += addtochildren;
1558           placeWidgetTree(i);
1559           break;
1560         default:
1561           widget[i].dims_measure.x += addtochildren;
1562           break;
1563         }
1564 
1565         distrib += widget[i].dims_measure.x;
1566       }
1567     }
1568   }
1569 }
1570 
1571 void Gui::render()
1572 {
1573   // Render trees of all root containers
1574 
1575   for (unsigned int i = 0; i < widget.size(); i++) {
1576     if (widget[i].parent == GWPARENT_NONE)
1577       renderWidgetTree(i);
1578   }
1579 }
1580 
1581 void Gui::renderWidgetTree(int w)
1582 {
1583   vec2f min, max;
1584 
1585   switch (widget[w].type) {
1586   case GWT_CONTAINER:
1587     glColor4f(1.0f,0.0f,0.0f,0.2f);
1588     break;
1589   case GWT_FILLER:
1590     glColor4f(0.0f,1.0f,0.0f,0.2f);
1591     break;
1592   case GWT_LABEL:
1593     glColor4f(0.0f,0.0f,1.0f,0.2f);
1594     break;
1595   }
1596 
1597   min = widget[w].pos;
1598   max = widget[w].pos + widget[w].dims_measure;
1599 
1600   glDisable(GL_TEXTURE_2D);
1601   glBegin(GL_QUADS);
1602   glVertex2f(min.x, min.y);
1603   glVertex2f(max.x, min.y);
1604   glVertex2f(max.x, max.y);
1605   glVertex2f(min.x, max.y);
1606   glEnd();
1607   glEnable(GL_TEXTURE_2D);
1608 
1609   // Render this widget
1610   switch (widget[w].type) {
1611   default:
1612     break;
1613 
1614   case GWT_LABEL: {
1615     glPushMatrix();
1616     vec2f ctr = widget[w].pos + widget[w].dims_measure * 0.5f;
1617     glTranslatef(ctr.x, ctr.y, 0.0f);
1618     glScalef(widget[w].fontsize, widget[w].fontsize, 1.0f);
1619     glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
1620     ssRender->drawText(widget[w].text, PTEXT_HZA_CENTER | PTEXT_VTA_CENTER);
1621     glPopMatrix();
1622     } break;
1623   }
1624 
1625   // Render children
1626   switch (widget[w].type) {
1627   case GWT_CONTAINER:
1628     for (unsigned int i = 0; i < widget.size(); i++) {
1629       if (widget[i].parent == w)
1630         renderWidgetTree(i);
1631     }
1632     break;
1633   }
1634 }
1635 
1636 #endif
1637 
getFreeWidget()1638 int Gui::getFreeWidget()
1639 {
1640   for (unsigned int i = 0; i < widget.size(); i++) {
1641     if (widget[i].type == GWT_FREE)
1642       return i;
1643   }
1644   widget.push_back(GuiWidget(GWT_FREE));
1645   return widget.size() - 1;
1646 }
1647 
1648 #if 0
1649 int Gui::addRootContainer(float x, float y, float width, float height, bool vert)
1650 {
1651   int w = getFreeWidget();
1652   widget[w].type = GWT_CONTAINER;
1653   widget[w].vert = vert;
1654   widget[w].parent = GWPARENT_NONE;
1655   widget[w].dims_min = vec2f(width, height);
1656   widget[w].pos = vec2f(x, y);
1657 
1658   return w;
1659 }
1660 
1661 int Gui::addFiller(int parent, float minwidth, float minheight)
1662 {
1663   int w = getFreeWidget();
1664   widget[w].type = GWT_FILLER;
1665   widget[w].parent = parent;
1666   widget[w].dims_min = vec2f(minwidth, minheight);
1667 
1668   return w;
1669 }
1670 
1671 int Gui::addContainer(float x, float y, float width, float height, bool vert)
1672 {
1673   int w = getFreeWidget();
1674   widget[w].type = GWT_CONTAINER;
1675   widget[w].vert = vert;
1676   widget[w].parent = parent;
1677   widget[w].dims_min = vec2f(minwidth, minheight);
1678 
1679   return w;
1680 }
1681 #endif
1682 
addLabel(float x,float y,const std::string & text,uint32 flags,float fontsize,LabelStyle ls)1683 int Gui::addLabel(float x, float y, const std::string &text, uint32 flags, float fontsize, LabelStyle ls)
1684 {
1685   int w = getFreeWidget();
1686   widget[w].type = GWT_LABEL;
1687   widget[w].text = text;
1688   widget[w].fontsize = fontsize;
1689   widget[w].dims_min = ssRender->getTextDims(text) * fontsize;
1690   widget[w].pos = vec2f(x, y);
1691 
1692   if (ls == LabelStyle::Regular)
1693   {
1694       widget[w].colnormal   = colors.normal;
1695       widget[w].colclick    = colors.click;
1696       widget[w].colhover    = colors.hover;
1697   }
1698   else
1699   if (ls == LabelStyle::Weak)
1700   {
1701       widget[w].colnormal   = colors.weak;
1702       widget[w].colclick    = colors.click;
1703       widget[w].colhover    = colors.hover;
1704   }
1705   else
1706   if (ls == LabelStyle::Strong)
1707   {
1708       widget[w].colnormal   = colors.strong;
1709       widget[w].colclick    = colors.click;
1710       widget[w].colhover    = colors.hover;
1711   }
1712   else
1713   if (ls == LabelStyle::Marked)
1714   {
1715       widget[w].colnormal   = colors.marked;
1716       widget[w].colclick    = colors.click;
1717       widget[w].colhover    = colors.hover;
1718   }
1719   else
1720   if (ls == LabelStyle::Header)
1721   {
1722       widget[w].colnormal   = colors.header;
1723       widget[w].colclick    = colors.click;
1724       widget[w].colhover    = colors.hover;
1725   }
1726   else
1727   if (ls == LabelStyle::List)
1728   {
1729       widget[w].colnormal   = colors.listnormal;
1730       widget[w].colclick    = colors.listclick;
1731       widget[w].colhover    = colors.listhover;
1732   }
1733 
1734   if (flags & PTEXT_HZA_CENTER)
1735     widget[w].pos.x -= widget[w].dims_min.x * 0.5f;
1736   else if (flags & PTEXT_HZA_RIGHT)
1737     widget[w].pos.x -= widget[w].dims_min.x;
1738 
1739   if (flags & PTEXT_VTA_CENTER)
1740     widget[w].pos.y -= widget[w].dims_min.y * 0.5f;
1741   else if (flags & PTEXT_VTA_TOP)
1742     widget[w].pos.y -= widget[w].dims_min.y;
1743 
1744   return w;
1745 }
1746 
addGraphic(float x,float y,float width,float height,PTexture * tex,GraphicStyle gs)1747 int Gui::addGraphic(float x, float y, float width, float height, PTexture *tex, GraphicStyle gs)
1748 {
1749   int w = getFreeWidget();
1750   widget[w].type = GWT_GRAPHIC;
1751   widget[w].dims_min = vec2f(width, height);
1752   widget[w].pos = vec2f(x, y);
1753   widget[w].tex = tex;
1754 
1755     if (gs == GraphicStyle::Button)
1756     {
1757         widget[w].colnormal = colors.bnormal;
1758         widget[w].colclick  = colors.bclick;
1759         widget[w].colhover  = colors.bhover;
1760     }
1761     else
1762     if (gs == GraphicStyle::Image)
1763     {
1764         widget[w].colnormal = {1.00f, 1.00f, 1.00f, 1.00f};
1765         widget[w].colclick  = {1.00f, 1.00f, 1.00f, 1.00f};
1766         widget[w].colhover  = {1.00f, 1.00f, 1.00f, 1.00f};
1767     }
1768 
1769   return w;
1770 }
1771