update for ui api
[carveJwlIkooP6JGAAIwe30JlM.git] / menu.c
1 #pragma once
2 #include "skaterift.h"
3 #include "menu.h"
4 #include "model.h"
5 #include "entity.h"
6 #include "input.h"
7 #include "world_map.h"
8 #include "ent_miniworld.h"
9 #include "audio.h"
10 #include "workshop.h"
11 #include "gui.h"
12 #include "control_overlay.h"
13 #include "network.h"
14 #include "shaders/model_menu.h"
15
16 struct global_menu menu = { .skip_starter = 0 };
17
18 void menu_at_begin(void)
19 {
20 if( menu.skip_starter ) return;
21
22 skaterift.activity = k_skaterift_menu;
23 menu.page = k_menu_page_starter;
24 }
25
26 void menu_init(void)
27 {
28 vg_console_reg_var( "skip_starter_menu", &menu.skip_starter,
29 k_var_dtype_i32, VG_VAR_PERSISTENT );
30 vg_tex2d_load_qoi_async_file( "textures/prem.qoi",
31 VG_TEX2D_CLAMP|VG_TEX2D_NOMIP|VG_TEX2D_NEAREST,
32 &menu.prem_tex );
33 }
34
35 void menu_open( enum menu_page page )
36 {
37 skaterift.activity = k_skaterift_menu;
38
39 if( page != k_menu_page_any )
40 {
41 menu.page = page;
42 }
43 }
44
45 bool menu_viewing_map(void)
46 {
47 return (skaterift.activity == k_skaterift_menu) &&
48 (menu.page == k_menu_page_main) &&
49 (menu.main_index == k_menu_main_map);
50 }
51
52 static void menu_decor_select( ui_context *ctx, ui_rect rect )
53 {
54 ui_px b = ctx->font->sx, hb = b/2;
55 ui_rect a0 = { rect[0] - 20 - hb, rect[1] + rect[3]/2 - hb, b,b },
56 a1 = { rect[0] + rect[2] + 20 + hb, rect[1] + rect[3]/2 - hb, b,b };
57
58 ui_text( ctx, a0, "\x95", 1, k_ui_align_middle_center, 0 );
59 ui_text( ctx, a1, "\x93", 1, k_ui_align_middle_center, 0 );
60 }
61
62 static void menu_standard_widget( ui_context *ctx,
63 ui_rect inout_panel, ui_rect rect, ui_px s )
64 {
65 ui_split( inout_panel, k_ui_axis_h, ctx->font->sy*s*2,
66 8, rect, inout_panel );
67 }
68
69 static bool menu_slider( ui_context *ctx,
70 ui_rect inout_panel, bool select, const char *label,
71 const f32 disp_min, const f32 disp_max, f32 *value,
72 const char *format )
73 {
74 ui_rect rect, box;
75 menu_standard_widget( ctx, inout_panel, rect, 1 );
76 ui_label( ctx, rect, label, 1, 8, box );
77
78 f32 t;
79 enum ui_button_state state = ui_slider_base( ctx, box, 0, 1, value, &t ),
80 mask_using =
81 k_ui_button_holding_inside |
82 k_ui_button_holding_outside |
83 k_ui_button_click,
84 mask_brighter = mask_using | k_ui_button_hover;
85
86 if( vg_input.display_input_method == k_input_method_controller )
87 {
88 if( select )
89 {
90 f32 m = axis_state( k_sraxis_mbrowse_h );
91 if( fabsf(m) > 0.5f )
92 {
93 *value += m * vg.time_frame_delta * (1.0f/2.0f);
94 *value = vg_clampf( *value, 0, 1 );
95 }
96
97 menu_decor_select( ctx, rect );
98 state |= k_ui_button_hover;
99 }
100 else
101 state = k_ui_button_none;
102 }
103
104 ui_rect line = { box[0], box[1], t * (f32)box[2], box[3] };
105 ui_fill( ctx, line, state&mask_brighter? GUI_COL_ACTIVE: GUI_COL_NORM );
106 ui_fill( ctx, (ui_rect){ box[0]+line[2], box[1], box[2]-line[2], box[3] },
107 GUI_COL_DARK );
108
109 ui_outline( ctx, box, 1, state? GUI_COL_HI: GUI_COL_ACTIVE, 0 );
110 ui_slider_text( ctx, box,
111 format, disp_min + (*value)*( disp_max-disp_min ) );
112
113 return (state & mask_using) && 1;
114 }
115
116 static bool menu_button( ui_context *ctx,
117 ui_rect inout_panel, bool select, const char *text )
118 {
119 ui_rect rect;
120 menu_standard_widget( ctx, inout_panel, rect, 1 );
121
122 enum ui_button_state state = k_ui_button_none;
123
124 if( vg_input.display_input_method == k_input_method_controller )
125 {
126 if( select )
127 {
128 menu_decor_select( ctx, rect );
129
130 if( button_down( k_srbind_maccept ) )
131 state = k_ui_button_click;
132 }
133 }
134 else
135 {
136 state = ui_button_base( ctx, rect );
137 select = 0;
138 }
139
140 if( state == k_ui_button_click )
141 {
142 ui_fill( ctx, rect, GUI_COL_DARK );
143 }
144 else if( state == k_ui_button_holding_inside )
145 {
146 ui_fill( ctx, rect, GUI_COL_DARK );
147 }
148 else if( state == k_ui_button_holding_outside )
149 {
150 ui_fill( ctx, rect, GUI_COL_DARK );
151 ui_outline( ctx, rect, 1, GUI_COL_CLICK, 0 );
152 }
153 else if( state == k_ui_button_hover )
154 {
155 ui_fill( ctx, rect, GUI_COL_ACTIVE );
156 ui_outline( ctx, rect, 1, GUI_COL_CLICK, 0 );
157 }
158 else
159 {
160 ui_fill( ctx, rect, select? GUI_COL_ACTIVE: GUI_COL_NORM );
161 if( select )
162 ui_outline( ctx, rect, 1, GUI_COL_HI, 0 );
163 }
164
165 ui_text( ctx, rect, text, 1, k_ui_align_middle_center, 0 );
166
167 if( state == k_ui_button_click )
168 {
169 audio_lock();
170 audio_oneshot( &audio_ui[0], 1.0f, 0.0f );
171 audio_unlock();
172 return 1;
173 }
174 else return 0;
175 }
176
177 static bool menu_checkbox( ui_context *ctx, ui_rect inout_panel, bool select,
178 const char *str_label, i32 *data )
179 {
180 ui_rect rect, label, box;
181 menu_standard_widget( ctx, inout_panel, rect, 1 );
182
183 ui_split( rect, k_ui_axis_v, -rect[3], 0, label, box );
184 ui_text( ctx, label, str_label, ctx->scale, k_ui_align_middle_left, 0 );
185
186 enum ui_button_state state = k_ui_button_none;
187
188 if( vg_input.display_input_method == k_input_method_controller )
189 {
190 if( select )
191 {
192 menu_decor_select( ctx, rect );
193
194 if( button_down( k_srbind_maccept ) )
195 {
196 *data = (*data) ^ 0x1;
197 state = k_ui_button_click;
198 }
199 }
200 }
201 else
202 {
203 state = ui_checkbox_base( ctx, box, data );
204 select = 0;
205 }
206
207 if( state == k_ui_button_holding_inside )
208 {
209 ui_fill( ctx, box, GUI_COL_ACTIVE );
210 ui_outline( ctx, box, 1, GUI_COL_CLICK, 0 );
211 }
212 else if( state == k_ui_button_holding_outside )
213 {
214 ui_fill( ctx, box, GUI_COL_DARK );
215 ui_outline( ctx, box, 1, GUI_COL_CLICK, 0 );
216 }
217 else if( state == k_ui_button_hover )
218 {
219 ui_fill( ctx, box, GUI_COL_ACTIVE );
220 ui_outline( ctx, box, 1, GUI_COL_HI, 0 );
221 }
222 else
223 {
224 ui_fill( ctx, box, select? GUI_COL_ACTIVE: GUI_COL_DARK );
225 ui_outline( ctx, box, 1, select? GUI_COL_HI: GUI_COL_NORM, 0 );
226 }
227
228 if( *data )
229 {
230 ui_rect_pad( box, (ui_px[2]){8,8} );
231 ui_fill( ctx, box, GUI_COL_HI );
232 }
233
234 if( state == k_ui_button_click )
235 {
236 audio_lock();
237 audio_oneshot( &audio_ui[0], 1.0f, 0.0f );
238 audio_unlock();
239 return 1;
240 }
241 else return 0;
242 }
243
244 static void menu_heading( ui_context *ctx,
245 ui_rect inout_panel, const char *label, u32 colour )
246 {
247 ui_rect rect;
248 menu_standard_widget( ctx, inout_panel, rect, 1 );
249
250 rect[0] -= 8;
251 rect[2] += 16;
252
253 u32 c0 = ui_opacity( GUI_COL_DARK, 0.36f ),
254 c1 = ui_opacity( GUI_COL_DARK, 0.5f );
255
256 struct ui_vert *vs = ui_fill( ctx, rect, c0 );
257
258 vs[0].colour = c1;
259 vs[1].colour = c1;
260
261 rect[1] += 4;
262 ui_text( ctx, rect, label, 1, k_ui_align_middle_center, 1 );
263 rect[0] += 1;
264 rect[1] -= 1;
265 ui_text( ctx, rect, label, 1, k_ui_align_middle_center, colour? colour:
266 ui_colour(ctx, k_ui_blue+k_ui_brighter) );
267 }
268
269 static u32 medal_colour( ui_context *ctx, u32 flags )
270 {
271 if( flags & k_ent_route_flag_achieve_gold )
272 return ui_colour( ctx, k_ui_yellow );
273 else if( flags & k_ent_route_flag_achieve_silver )
274 return ui_colour( ctx, k_ui_fg );
275 else return 0;
276 }
277
278 static i32 menu_nav( i32 *p_row, int mv, i32 max )
279 {
280 i32 row_prev = *p_row;
281
282 if( mv < 0 ) *p_row = vg_min( max, (*p_row) +1 );
283 if( mv > 0 ) *p_row = vg_max( 0, (*p_row) -1 );
284
285 if( *p_row != row_prev )
286 {
287 audio_lock();
288 audio_oneshot( &audio_ui[3], 1.0f, 0.0f );
289 audio_unlock();
290 }
291
292 return *p_row;
293 }
294
295 static void menu_try_find_cam( i32 id )
296 {
297 world_instance *world = &world_static.instances[0];
298 for( u32 i=0; i<mdl_arrcount(&world->ent_npc); i ++ )
299 {
300 ent_npc *fnpc = mdl_arritm( &world->ent_npc, i );
301 if( (fnpc->id == 50) && (fnpc->context == id) )
302 {
303 if( mdl_entity_id_type(fnpc->camera) == k_ent_camera )
304 {
305 u32 index = mdl_entity_id_id( fnpc->camera );
306 menu.bg_cam = mdl_arritm( &world->ent_camera, index );
307 menu.bg_blur = 0;
308 }
309 }
310 }
311 }
312
313 static void menu_link_modal( const char *url )
314 {
315 menu.web_link = url;
316
317 /* Only reset the choice of browser if 'No' was selected. */
318 if( menu.web_choice == 2 )
319 menu.web_choice = 0;
320 }
321
322 void menu_gui( ui_context *ctx )
323 {
324 if( button_down( k_srbind_mopen ) )
325 {
326 if( skaterift.activity == k_skaterift_default )
327 {
328 menu_open( k_menu_page_main );
329 return;
330 }
331 }
332
333 if( skaterift.activity != k_skaterift_menu )
334 return;
335
336 menu.bg_blur = 1;
337 menu.bg_cam = NULL;
338
339 /* get buttons inputs
340 * -------------------------------------------------------------------*/
341 int ml = button_press( k_srbind_mleft ),
342 mr = button_press( k_srbind_mright ),
343 mu = button_press( k_srbind_mup ),
344 md = button_press( k_srbind_mdown ),
345 mh = ml-mr,
346 mv = mu-md,
347 enter = button_down( k_srbind_maccept );
348
349 /* TAB CONTROL */
350 u8 lb_down = 0, rb_down = 0;
351 vg_exec_input_program( k_vg_input_type_button_u8,
352 (vg_input_op[]){
353 vg_joy_button, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, vg_end
354 }, &rb_down );
355 vg_exec_input_program( k_vg_input_type_button_u8,
356 (vg_input_op[]){
357 vg_joy_button, SDL_CONTROLLER_BUTTON_LEFTSHOULDER, vg_end
358 }, &lb_down );
359
360 int mtab = (i32)rb_down - (i32)lb_down;
361
362 if( menu.repeater > 0.0f )
363 {
364 menu.repeater -= vg_minf( vg.time_frame_delta, 0.5f );
365 mv = 0;
366 mh = 0;
367 mtab = 0;
368 }
369 else
370 {
371 if( mv || mh || mtab )
372 menu.repeater += 0.2f;
373 }
374
375 if( vg_input.display_input_method == k_input_method_kbm )
376 {
377 ui_capture_mouse(ctx, 1);
378 }
379
380 if( skaterift.activity != k_skaterift_menu ) return;
381
382
383 if( menu.web_link )
384 {
385 menu_try_find_cam( 3 );
386
387 ui_rect panel = { 0,0, 800, 200 },
388 screen = { 0,0, vg.window_x,vg.window_y };
389 ui_rect_center( screen, panel );
390 ui_fill( ctx, panel, GUI_COL_DARK );
391 ui_outline( ctx, panel, 1, GUI_COL_NORM, 0 );
392 ui_rect_pad( panel, (ui_px[]){8,8} );
393
394 ui_rect title;
395 ui_split( panel, k_ui_axis_h, 28*2, 0, title, panel );
396 ctx->font = &vgf_default_title;
397 ui_text( ctx, title, "Open Link?", 1, k_ui_align_middle_center, 0 );
398
399 ui_split( panel, k_ui_axis_h, 28, 0, title, panel );
400 ctx->font = &vgf_default_large;
401 ui_text( ctx, title, menu.web_link, 1, k_ui_align_middle_center, 0 );
402
403 ui_rect end = { panel[0], panel[1] + panel[3] - 48, panel[2], 48 };
404
405 ui_rect a,b,c;
406 ui_split_ratio( end, k_ui_axis_v, 2.0/3.0, 2, a, c );
407 ui_split_ratio( a, k_ui_axis_v, 1.0/2.0, 2, a, b );
408
409 i32 R = menu_nav( &menu.web_choice, mh, 2 );
410
411 if( menu_button( ctx, a, R==0, "Steam Overlay" ) )
412 {
413 if( steam_ready )
414 {
415 ISteamFriends *hSteamFriends = SteamAPI_SteamFriends();
416 SteamAPI_ISteamFriends_ActivateGameOverlayToWebPage( hSteamFriends,
417 menu.web_link,
418 k_EActivateGameOverlayToWebPageMode_Default );
419 menu.web_link = NULL;
420 }
421 }
422
423 if( menu_button( ctx, b, R==1, "Web Browser" ) )
424 {
425 char buf[512];
426 vg_str str;
427 vg_strnull( &str, buf, sizeof(buf) );
428 #ifdef _WIN32
429 vg_strcat( &str, "start " );
430 #else
431 vg_strcat( &str, "xdg-open " );
432 #endif
433 vg_strcat( &str, menu.web_link );
434
435 if( vg_strgood(&str) )
436 system( buf );
437
438 menu.web_link = NULL;
439 }
440
441 if( menu_button( ctx, c, R==2, "No" ) || button_down( k_srbind_mback ) )
442 {
443 audio_lock();
444 audio_oneshot( &audio_ui[3], 1.0f, 0.0f );
445 audio_unlock();
446 menu.web_link = NULL;
447 }
448
449 goto menu_draw;
450 }
451
452
453 if( vg.settings_open )
454 {
455 if( button_down( k_srbind_mback ) )
456 {
457 audio_lock();
458 audio_oneshot( &audio_ui[3], 1.0f, 0.0f );
459 audio_unlock();
460 vg_settings_close();
461 srinput.state = k_input_state_resume;
462 }
463
464 return;
465 }
466
467 if( menu.page == k_menu_page_credits )
468 {
469 ui_rect panel = { 0,0, 600, 400 },
470 screen = { 0,0, vg.window_x,vg.window_y };
471 ui_rect_center( screen, panel );
472 ui_fill( ctx, panel, GUI_COL_DARK );
473 ui_outline( ctx, panel, 1, GUI_COL_NORM, 0 );
474 ui_rect_pad( panel, (ui_px[]){8,8} );
475
476 ui_rect title;
477 ui_split( panel, k_ui_axis_h, 28*2, 0, title, panel );
478 ctx->font = &vgf_default_title;
479 ui_text( ctx, title, "Skate Rift - Credits",
480 1, k_ui_align_middle_center, 0 );
481
482 ui_split( panel, k_ui_axis_h, 28, 0, title, panel );
483 ctx->font = &vgf_default_large;
484 ui_text( ctx, title,
485 "Mt.Zero Software", 1, k_ui_align_middle_center, 0 );
486
487 ui_split( panel, k_ui_axis_h, 8, 0, title, panel );
488 ui_split( panel, k_ui_axis_h, 28*2, 0, title, panel );
489 ctx->font = &vgf_default_title;
490 ui_text( ctx, title, "Free Software", 1, k_ui_align_middle_center, 0 );
491
492 ui_split( panel, k_ui_axis_h, 8, 0, title, panel );
493 ctx->font = &vgf_default_large;
494 ui_text( ctx, panel,
495 "Sam Lantinga - SDL2 - libsdl.org\n"
496 "Hunter WB - Anyascii\n"
497 "David Herberth - GLAD\n"
498 "Dominic Szablewski - QOI - qoiformat.org\n"
499 "Sean Barrett - stb_image, stb_vorbis,\n"
500 " stb_include\n"
501 "Khronos Group - OpenGL\n"
502 , 1, k_ui_align_left, 0 );
503
504 ui_rect end = { panel[0], panel[1] + panel[3] - 64, panel[2], 64 };
505
506 if( menu_button( ctx, end, 1, "Back" ) || button_down( k_srbind_mback ) )
507 {
508 menu.page = k_menu_page_main;
509 }
510
511 goto menu_draw;
512 }
513 else if( menu.page == k_menu_page_starter )
514 {
515 i32 R = menu_nav( &menu.intro_row, mv, 3 );
516 ui_rect panel = { 0,0, 600, 400 },
517 screen = { 0,0, vg.window_x,vg.window_y };
518 ui_rect_center( screen, panel );
519 ui_fill( ctx, panel, ui_opacity( GUI_COL_DARK, 0.35f ) );
520 ui_outline( ctx, panel, 1, GUI_COL_NORM, 0 );
521 ui_rect_pad( panel, (ui_px[]){8,8} );
522
523 ui_rect title;
524 ui_split( panel, k_ui_axis_h, 28*2, 0, title, panel );
525 ctx->font = &vgf_default_title;
526 ui_text( ctx, title,
527 "Welcome to Skate Rift", 1, k_ui_align_middle_center, 0 );
528
529 ui_split( panel, k_ui_axis_h, 28, 0, title, panel );
530 ctx->font = &vgf_default_large;
531
532 menu_checkbox( ctx, panel, R == 0,
533 "Show controls overlay (good for new players)",
534 &control_overlay.enabled );
535 menu_checkbox( ctx, panel, R == 1, "Auto connect to global server",
536 &network_client.auto_connect );
537
538 ui_rect end = { panel[0], panel[1] + panel[3] - 100, panel[2], 100 };
539 menu_checkbox( ctx, end, R == 2,
540 "Don't show this again", &menu.skip_starter );
541 if( menu_button( ctx, end, R == 3, "OK" ) )
542 {
543 menu.page = k_menu_page_main;
544 skaterift.activity = k_skaterift_default;
545 }
546
547 menu_try_find_cam( 3 );
548 goto menu_draw;
549 }
550 else if( menu.page == k_menu_page_premium )
551 {
552 i32 R = menu_nav( &menu.prem_row, mh, 1 );
553 ui_rect panel = { 0,0, 600, 400+240 },
554 screen = { 0,0, vg.window_x,vg.window_y };
555 ui_rect_center( screen, panel );
556 ui_fill( ctx, panel, ui_opacity( GUI_COL_DARK, 0.35f ) );
557 ui_outline( ctx, panel, 1, GUI_COL_NORM, 0 );
558 ui_rect_pad( panel, (ui_px[]){8,8} );
559
560 ui_rect title;
561 ui_split( panel, k_ui_axis_h, 28*2, 0, title, panel );
562 ctx->font = &vgf_default_title;
563 ui_text( ctx, title, "Content is in the full game.",
564 1, k_ui_align_middle_center, 0 );
565
566 ui_split( panel, k_ui_axis_h, 28, 0, title, panel );
567 ctx->font = &vgf_default_large;
568
569 ui_rect img;
570 ui_split( panel, k_ui_axis_h, 456, 0, img, panel );
571 ui_image( ctx, img, &menu.prem_tex );
572
573 ui_rect end = { panel[0], panel[1] + panel[3] - 48, panel[2], 48 }, a,b;
574 ui_split_ratio( end, k_ui_axis_v, 0.5f, 2, a, b );
575
576 if( menu_button( ctx, a, R == 0, "Store Page" ) )
577 {
578 if( steam_ready )
579 SteamAPI_ISteamFriends_ActivateGameOverlayToStore(
580 SteamAPI_SteamFriends(), 2103940, k_EOverlayToStoreFlag_None);
581 }
582
583 if( menu_button( ctx, b, R == 1, "Nah" ) || button_down( k_srbind_mback ) )
584 {
585 audio_lock();
586 audio_oneshot( &audio_ui[3], 1.0f, 0.0f );
587 audio_unlock();
588 skaterift.activity = k_skaterift_default;
589 return;
590 }
591
592 goto menu_draw;
593 }
594
595 /* TOP BAR
596 * -------------------------------------------------------------------*/
597
598 ctx->font = &vgf_default_title;
599 ui_px height = ctx->font->ch + 16;
600 ui_rect topbar = { 0, 0, vg.window_x, height };
601
602 const char *opts[] = {
603 [k_menu_main_main] = "Menu",
604 [k_menu_main_map] = "Map",
605 [k_menu_main_settings ] = "Settings",
606 [k_menu_main_guide ] = "Guides"
607 };
608
609 if( mtab )
610 {
611 menu.main_index += mtab;
612
613 if( menu.main_index == -1 )
614 menu.main_index ++;
615
616 if( menu.main_index == vg_list_size(opts) )
617 menu.main_index --;
618
619 audio_lock();
620 audio_oneshot( &audio_ui[3], 1.0f, 0.0f );
621 audio_unlock();
622 }
623
624 ui_px x = 0, spacer = 8;
625 for( u32 draw=0; draw<2; draw ++ )
626 {
627 if( vg_input.display_input_method == k_input_method_controller )
628 {
629 if( draw )
630 {
631 ui_rect inf_lb = { x, 0, 32, height };
632 ui_fill( ctx, inf_lb, lb_down? GUI_COL_NORM: GUI_COL_NORM );
633 ui_text( ctx, inf_lb, "\x91", 1, k_ui_align_middle_center, 0 );
634 }
635 x += 32 + spacer;
636 }
637
638 for( i32 i=0; i<vg_list_size(opts); i ++ )
639 {
640 ui_rect box = { x, 0, ui_text_line_width(ctx, opts[i]) + 32, height };
641
642 if( draw )
643 {
644 enum ui_button_state state = ui_button_base( ctx, box );
645 if( state == k_ui_button_click )
646 {
647 ui_fill( ctx, box, GUI_COL_DARK );
648 menu.main_index = i;
649 }
650 else if( state == k_ui_button_holding_inside )
651 {
652 ui_fill( ctx, box, GUI_COL_DARK );
653 }
654 else if( state == k_ui_button_holding_outside )
655 {
656 ui_fill( ctx, box, GUI_COL_DARK );
657 ui_outline( ctx, box, 1, GUI_COL_CLICK, 0 );
658 }
659 else if( state == k_ui_button_hover )
660 {
661 ui_fill( ctx, box, GUI_COL_NORM );
662 ui_outline( ctx, box, 1, GUI_COL_ACTIVE, 0 );
663 }
664 else
665 ui_fill( ctx, box,
666 i==menu.main_index? GUI_COL_ACTIVE: GUI_COL_NORM );
667
668 ui_text( ctx, box, opts[i], 1, k_ui_align_middle_center, 0 );
669 }
670
671 x += box[2] + spacer;
672 }
673
674 if( vg_input.display_input_method == k_input_method_controller )
675 {
676 if( draw )
677 {
678 ui_rect inf_rb = { x, 0, 32, height };
679 ui_fill( ctx, inf_rb, rb_down? GUI_COL_NORM: GUI_COL_NORM );
680 ui_text( ctx, inf_rb, "\x92", 1, k_ui_align_middle_center, 0 );
681 }
682 x += 32;
683 }
684
685 if( draw )
686 ui_fill( ctx,
687 (ui_rect){ x+8,0, vg.window_x-(x+8),height }, GUI_COL_NORM );
688 else
689 {
690 x = vg.window_x/2 - x/2;
691 ui_fill( ctx,
692 (ui_rect){ 0, 0, x-8, height }, GUI_COL_NORM );
693 }
694 }
695
696 if( menu.main_index == k_menu_main_map )
697 {
698 menu.bg_blur = 0;
699 ctx->font = &vgf_default_large;
700 ui_rect title = { vg.window_x/2- 512/2, height+8, 512, 64 };
701
702 ui_px x = 8,
703 y = height+8;
704
705 struct ui_vert *vs =
706 ui_fill( ctx, (ui_rect){ x,y, 0,height },
707 world_static.active_instance? GUI_COL_DARK: GUI_COL_ACTIVE );
708
709 char buf[64];
710 vg_str str;
711 vg_strnull( &str, buf, sizeof(buf) );
712 vg_strcat( &str, "Hub World" );
713
714 if( world_static.active_instance )
715 {
716 vg_strcat( &str, " (" );
717 vg_input_string( &str, input_button_list[k_srbind_mhub], 1 );
718 vg_strcatch( &str, '\x06' );
719 vg_strcatch( &str, '\x00' );
720 vg_strcat( &str, "\x1B[0m)" );
721 }
722
723 ui_px w = ui_text( ctx, (ui_rect){ x+8, y, 1000, height }, buf, 1,
724 k_ui_align_middle_left, 0 );
725 w *= ctx->font->sx;
726 x += w + 16;
727
728 vs[1].co[0] = x + 8;
729 vs[2].co[0] = x;
730
731 x += 2;
732
733 world_instance *world = &world_static.instances[1];
734 if( world->status == k_world_status_loaded )
735 {
736 const char *world_name =
737 mdl_pstr( &world->meta, world->info.pstr_name );
738
739 vg_strnull( &str, buf, sizeof(buf) );
740 vg_strcat( &str, world_name );
741
742 if( !world_static.active_instance &&
743 (vg_input.display_input_method == k_input_method_controller) )
744 {
745 vg_strcat( &str, " (" );
746 vg_input_string( &str, input_button_list[k_srbind_mhub], 1 );
747 vg_strcatch( &str, '\x06' );
748 vg_strcatch( &str, '\x00' );
749 vg_strcat( &str, "\x1B[0m)" );
750 }
751
752 vs = ui_fill( ctx, (ui_rect){ x,y, 1000,height },
753 world_static.active_instance? GUI_COL_ACTIVE: GUI_COL_DARK );
754 w = ui_text( ctx, (ui_rect){ x+16,y, 1000,height }, buf,
755 1, k_ui_align_middle_left, 0 );
756
757 w = w*ctx->font->sx + 8*3;
758 x += w;
759
760 if( button_down( k_srbind_mhub ) ||
761 ui_button_base( ctx, (ui_rect){0,y,x,height} ) == k_ui_button_click )
762 {
763 world_switch_instance( world_static.active_instance ^ 0x1 );
764 skaterift.activity = k_skaterift_default;
765 world_map.view_ready = 0;
766 }
767
768 vs[0].co[0] += 8;
769 vs[1].co[0] = x + 8;
770 vs[2].co[0] = x;
771 }
772
773 x = 8;
774 y += 8 + height;
775
776 if( world_static.active_instance )
777 {
778 ui_rect stat_panel = { x,y, 256,vg.window_y-y };
779 u32 c0 = ui_opacity( GUI_COL_DARK, 0.36f );
780 struct ui_vert *vs = ui_fill( ctx, stat_panel, c0 );
781
782 ui_rect_pad( stat_panel, (ui_px[2]){8,0} );
783
784 for( u32 i=0; i<mdl_arrcount( &world->ent_region ); i ++ )
785 {
786 ent_region *region = mdl_arritm( &world->ent_region, i );
787
788 if( !region->zone_volume )
789 continue;
790
791 const char *title = mdl_pstr( &world->meta, region->pstr_title );
792 ctx->font = &vgf_default_large;
793
794 ui_rect title_box;
795 menu_standard_widget( ctx, stat_panel, title_box, 1 );
796
797 stat_panel[0] += 16;
798 stat_panel[2] -= 16;
799 ctx->font = &vgf_default_small;
800
801 ent_volume *volume = mdl_arritm(&world->ent_volume,
802 mdl_entity_id_id(region->zone_volume));
803
804 u32 combined = k_ent_route_flag_achieve_gold |
805 k_ent_route_flag_achieve_silver;
806
807 char buf[128];
808 vg_str str;
809
810 for( u32 j=0; j<mdl_arrcount(&world->ent_route); j ++ )
811 {
812 ent_route *route = mdl_arritm(&world->ent_route, j );
813
814 v3f local;
815 m4x3_mulv( volume->to_local, route->board_transform[3], local );
816 if( !((fabsf(local[0]) <= 1.0f) &&
817 (fabsf(local[1]) <= 1.0f) &&
818 (fabsf(local[2]) <= 1.0f)) )
819 {
820 continue;
821 }
822
823 combined &= route->flags;
824
825 vg_strnull( &str, buf, sizeof(buf) );
826 vg_strcat( &str, "(Race) " );
827 vg_strcat( &str, mdl_pstr(&world->meta, route->pstr_name));
828
829 if( route->flags & k_ent_route_flag_achieve_silver )
830 vg_strcat( &str, " \xb3");
831 if( route->flags & k_ent_route_flag_achieve_gold )
832 vg_strcat( &str, "\xb3");
833
834 ui_rect r;
835 ui_standard_widget( ctx, stat_panel, r, 1 );
836 ui_text( ctx, r, buf, 1, k_ui_align_middle_left,
837 medal_colour( ctx, route->flags ) );
838 }
839
840 for( u32 j=0; j<mdl_arrcount(&world->ent_challenge); j ++ )
841 {
842 ent_challenge *challenge = mdl_arritm( &world->ent_challenge, j );
843
844 v3f local;
845 m4x3_mulv( volume->to_local, challenge->transform.co, local );
846 if( !((fabsf(local[0]) <= 1.0f) &&
847 (fabsf(local[1]) <= 1.0f) &&
848 (fabsf(local[2]) <= 1.0f)) )
849 {
850 continue;
851 }
852
853 vg_strnull( &str, buf, sizeof(buf) );
854 vg_strcat( &str, mdl_pstr(&world->meta, challenge->pstr_alias));
855
856 u32 flags = 0x00;
857 if( challenge->status )
858 {
859 flags |= k_ent_route_flag_achieve_gold;
860 flags |= k_ent_route_flag_achieve_silver;
861 vg_strcat( &str, " \xb3\xb3" );
862 }
863
864 combined &= flags;
865
866 ui_rect r;
867 ui_standard_widget( ctx, stat_panel, r, 1 );
868 ui_text( ctx, r, buf, 1,
869 k_ui_align_middle_left, medal_colour( ctx, flags ) );
870 }
871
872 stat_panel[0] -= 16;
873 stat_panel[2] += 16;
874
875 u32 title_col = 0;
876 if( combined & k_ent_route_flag_achieve_gold )
877 title_col = ui_colour( ctx, k_ui_yellow );
878 else if( combined & k_ent_route_flag_achieve_silver )
879 title_col = ui_colour( ctx, k_ui_fg );
880
881 menu_heading( ctx, title_box, title, title_col );
882 }
883
884 vs[2].co[1] = stat_panel[1];
885 vs[3].co[1] = stat_panel[1];
886 }
887 }
888 else
889 {
890 if( button_down( k_srbind_mback ) )
891 {
892 audio_lock();
893 audio_oneshot( &audio_ui[3], 1.0f, 0.0f );
894 audio_unlock();
895 skaterift.activity = k_skaterift_default;
896 return;
897 }
898
899 ui_rect list0 = { vg.window_x/2 - 512/2, height+32,
900 512, vg.window_y-height-64 }, list;
901 rect_copy( list0, list );
902 ui_rect_pad( list, (ui_px[2]){8,8} );
903
904 /* MAIN / MAIN
905 * -------------------------------------------------------------------*/
906
907 if( menu.main_index == k_menu_main_main )
908 {
909 i32 R = menu_nav( &menu.main_row, mv, 2 );
910
911 if( menu_button( ctx, list, R == 0, "Resume" ) )
912 {
913 skaterift.activity = k_skaterift_default;
914 return;
915 }
916
917 if( menu_button( ctx, list, R == 1, "Credits" ) )
918 {
919 menu.page = k_menu_page_credits;
920 }
921
922 ui_rect end = { list[0], list[1]+list[3]-64, list[2], 72 };
923 if( menu_button( ctx, end, R == 2, "Quit Game" ) )
924 {
925 vg.window_should_close = 1;
926 }
927 }
928 else if( menu.main_index == k_menu_main_settings )
929 {
930 ui_fill( ctx, list0, ui_opacity( GUI_COL_DARK, 0.36f ) );
931 ui_outline( ctx, list0, 1, GUI_COL_NORM, 0 );
932 i32 R = menu_nav( &menu.settings_row, mv, 8 );
933
934 ctx->font = &vgf_default_large;
935 list[1] -= 8;
936 menu_heading( ctx, list, "Game", 0 );
937 menu_checkbox( ctx, list, R == 0, "Show controls overlay",
938 &control_overlay.enabled );
939 menu_checkbox( ctx, list, R == 1, "Auto connect to global server",
940 &network_client.auto_connect );
941
942 menu_heading( ctx, list, "Audio/Video", 0 );
943 menu_slider( ctx, list, R == 2, "Volume", 0, 100,
944 &vg_audio.external_global_volume, "%.f%%" );
945 menu_slider( ctx, list, R == 3, "Resolution", 0, 100,
946 &k_render_scale, "%.f%%" );
947 menu_checkbox( ctx, list, R == 4, "Motion Blur", &k_blur_effect );
948
949 menu_heading( ctx, list, "Camera", 0 );
950 menu_slider( ctx, list, R == 5, "Fov", 97, 135,
951 &k_fov, "%.1f\xb0" );
952 menu_slider( ctx, list, R == 6, "Cam Height", -0.4f, +1.4f,
953 &k_cam_height, vg_lerpf(-0.4f,1.4f,k_cam_height)>=0.0f?
954 "+%.2fm": "%.2fm" );
955 menu_checkbox( ctx, list, R == 7, "Invert Y Axis", &k_invert_y );
956
957
958 ui_rect end = { list[0], list[1]+list[3]-64, list[2], 72 };
959 ctx->font = &vgf_default_small;
960 menu_heading( ctx, end, "Advanced", 0 );
961 if( menu_button( ctx, end, R == 8, "Open Engine Settings" ) )
962 {
963 vg_settings_open();
964 }
965 }
966 else if( menu.main_index == k_menu_main_guide )
967 {
968 list0[0] = 8;
969 list[0] = 16;
970
971 ui_px w = 700,
972 marg = list0[0]+list0[2],
973 pw = vg.window_x - marg,
974 infx = pw/2 - w/2 + marg;
975 ui_rect inf = { infx, height +32, w, 800 };
976
977 struct ui_vert *vs = NULL;
978
979 if( menu.guide_sel && (menu.guide_sel <= 3) )
980 {
981 vs = ui_fill( ctx, inf, ui_opacity( GUI_COL_DARK, 0.20f ) );
982 ui_rect_pad( inf, (ui_px[]){8,8} );
983 }
984
985 ui_fill( ctx, list0, ui_opacity( GUI_COL_DARK, 0.36f ) );
986 ui_outline( ctx, list0, 1, GUI_COL_NORM, 0 );
987 i32 R = menu_nav( &menu.guides_row, mv, 6 );
988
989 ctx->font = &vgf_default_large;
990 list[1] -= 8;
991 menu_heading( ctx, list, "Info", 0 );
992 if( menu.guide_sel == 1 )
993 {
994 menu_try_find_cam( 1 );
995
996 ui_rect title;
997 ui_split( inf, k_ui_axis_h, 28*2, 0, title, inf );
998 ctx->font = &vgf_default_title;
999 ui_text( ctx,
1000 title, "Where to go", 1, k_ui_align_middle_center, 0 );
1001
1002 ui_split( inf, k_ui_axis_h, 28, 0, title, inf );
1003 ctx->font = &vgf_default_large;
1004 ui_text( ctx, inf,
1005 "Visit the sandcastles built by John Cockroach to be\n"
1006 "transported into the real location. Use the map in the\n"
1007 "menu to return back this hub island.\n"
1008 "\n"
1009 "You can begin training at the Volcano Island, where you\n"
1010 "can learn movement skills. Then travel to Mt.Zero Island\n"
1011 "or Bort Downtown to test them out. Finally the most\n"
1012 "challenging course can be taken on at the Valley.\n"
1013 "\n"
1014 "Also visit the Steam Workshop for community made\n"
1015 "locations to skate!"
1016 , 1, k_ui_align_left, 0 );
1017
1018 vs[2].co[1] = vs[0].co[1] + 84 + vgf_default_large.sy*11 + 16;
1019 vs[3].co[1] = vs[2].co[1];
1020 }
1021 if( menu_button( ctx, list, R == 0, "Where to go" ) )
1022 menu.guide_sel = 1;
1023
1024 if( menu.guide_sel == 3 )
1025 {
1026 menu_try_find_cam( 2 );
1027
1028 ui_rect title;
1029 ui_split( inf, k_ui_axis_h, 28*2, 0, title, inf );
1030 ctx->font = &vgf_default_title;
1031 ui_text( ctx, title, "Online", 1, k_ui_align_middle_center, 0 );
1032
1033 ui_split( inf, k_ui_axis_h, 28, 0, title, inf );
1034 ctx->font = &vgf_default_large;
1035 ui_text( ctx, inf,
1036 "Connection to the global server is managed by this radar\n"
1037 "dish in the hub island. Come back here to turn the\n"
1038 "connection on or off.\n"
1039 "\n"
1040 "You'll then see other players or your friends if you go\n"
1041 "to the same location, and your highscores will be saved\n"
1042 "onto the scoreboards."
1043 , 1, k_ui_align_left, 0 );
1044
1045 vs[2].co[1] = vs[0].co[1] + 84 + vgf_default_large.sy*7 + 16;
1046 vs[3].co[1] = vs[2].co[1];
1047 }
1048 if( menu_button( ctx, list, R == 1, "Playing Online" ) )
1049 menu.guide_sel = 3;
1050
1051 menu_heading( ctx, list, "Controls", 0 );
1052 if( menu_button( ctx, list, R == 2, "Skating \xb2" ) )
1053 {
1054 menu.guide_sel = 0;
1055 menu_link_modal(
1056 "https://skaterift.com/index.php?page=movement" );
1057 }
1058 if( menu.guide_sel == 0 || menu.guide_sel > 3 ) menu_try_find_cam( 3 );
1059
1060 if( menu_button( ctx, list, R == 3, "Tricks \xb2" ) )
1061 {
1062 menu.guide_sel = 0;
1063 menu_link_modal(
1064 "https://skaterift.com/index.php?page=tricks" );
1065 }
1066
1067 menu_heading( ctx, list, "Workshop", 0 );
1068 if( menu_button( ctx, list, R == 4, "Create a Board \xb2" ) )
1069 {
1070 menu.guide_sel = 0;
1071 menu_link_modal(
1072 "https://skaterift.com/index.php?page=workshop_board" );
1073 }
1074 if( menu_button( ctx, list, R == 5, "Create a World \xb2" ) )
1075 {
1076 menu.guide_sel = 0;
1077 menu_link_modal(
1078 "https://skaterift.com/index.php?page=workshop_world" );
1079 }
1080 if( menu_button( ctx, list, R == 6, "Create a Playermodel \xb2" ) )
1081 {
1082 menu.guide_sel = 0;
1083 menu_link_modal(
1084 "https://skaterift.com/index.php?page=workshop_player" );
1085 }
1086 }
1087 }
1088
1089 menu_draw:
1090
1091 vg_ui.frosting = 0.015f;
1092 ui_flush( ctx, k_ui_shader_colour, NULL );
1093 vg_ui.frosting = 0.0f;
1094 ctx->font = &vgf_default_small;
1095 }