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