+ enum menu_input_mode{
+ k_menu_input_mode_keys,
+ k_menu_input_mode_mouse
+ }
+ input_mode;
+ f32 mouse_track, mouse_dist; /* used for waking up mouse */
+ f32 slider_offset;
+
+ struct page_stack_frame {
+ u32 page;
+ ent_menuitem *loc;
+ ent_camera *cam;
+ }
+ page_stack[ MENU_STACK_SIZE ];
+
+ ent_menuitem *loc;
+ ent_camera *cam;
+ camera view;
+
+ mdl_context model;
+ GLuint texture;
+ glmesh mesh;
+
+ mdl_array_ptr items, markers, cameras;
+}
+static menu;
+
+/*
+ * Attaches memory locations to the various items in the menu
+ */
+static void menu_link(void){
+ /* link data locations */
+ for( u32 i=0; i<mdl_arrcount(&menu.items); i++ ){
+ ent_menuitem *item = mdl_arritm( &menu.items, i );
+
+ if( item->type == k_ent_menuitem_type_toggle ||
+ item->type == k_ent_menuitem_type_slider ){
+
+ const char *name;
+
+ if( item->type == k_ent_menuitem_type_slider )
+ name = mdl_pstr( &menu.model, item->slider.pstr_data );
+ else
+ name = mdl_pstr( &menu.model, item->checkmark.pstr_data );
+ vg_var *var = vg_console_match_var( name );
+
+ if( var ){
+ if( ( item->type == k_ent_menuitem_type_slider &&
+ var->data_type != k_var_dtype_f32
+ ) ||
+ ( item->type == k_ent_menuitem_type_toggle &&!
+ ( var->data_type == k_var_dtype_i32 ||
+ var->data_type == k_var_dtype_u32
+ )
+ )
+ ){
+ vg_error( "Cannot hook to data %s(%p), because it is type %d.\n",
+ name, var, var->data_type );
+ item->pvoid = NULL;
+ }
+ else{
+ item->pvoid = var->data;
+ }
+ }
+ else{
+ vg_error( "No data named %s\n", name );
+ item->pvoid = NULL;
+ }
+ }
+ else{
+ item->pvoid = NULL;
+ }
+ }
+
+ /* link controllers */
+ menu.ctr_deck = NULL;
+ menu.ctr_kbm = NULL;
+ menu.ctr_ps = NULL;
+ menu.ctr_steam = NULL;
+ menu.ctr_xbox = NULL;
+
+ for( u32 i=0; i<mdl_arrcount(&menu.items); i++ ){
+ ent_menuitem *item = mdl_arritm( &menu.items, i );
+
+ if( MDL_CONST_PSTREQ( &menu.model, item->visual.pstr_name, "deck" ) )
+ menu.ctr_deck = item;
+ if( MDL_CONST_PSTREQ( &menu.model, item->visual.pstr_name, "kbm" ) )
+ menu.ctr_kbm = item;
+ if( MDL_CONST_PSTREQ( &menu.model, item->visual.pstr_name, "ps" ) )
+ menu.ctr_ps = item;
+ if( MDL_CONST_PSTREQ( &menu.model, item->visual.pstr_name, "steam" ) )
+ menu.ctr_steam = item;
+ if( MDL_CONST_PSTREQ( &menu.model, item->visual.pstr_name, "xbox" ) )
+ menu.ctr_xbox = item;
+ }
+}
+
+static void menu_close(void){
+ skaterift.activity = k_skaterift_default;
+ menu.page_depth = 0;
+ menu.page = 0xffffffff;
+ srinput.state = k_input_state_resume;
+}
+
+static void menu_init(void){
+ void *alloc = vg_mem.rtmemory;
+
+ mdl_open( &menu.model, "models/rs_menu.mdl", alloc );
+ mdl_load_metadata_block( &menu.model, alloc );
+
+ vg_linear_clear( vg_mem.scratch );
+
+ MDL_LOAD_ARRAY( &menu.model, &menu.items, ent_menuitem, alloc );
+ MDL_LOAD_ARRAY( &menu.model, &menu.markers, ent_marker, alloc );
+ MDL_LOAD_ARRAY( &menu.model, &menu.cameras, ent_camera, alloc );
+
+ vg_linear_clear( vg_mem.scratch );
+
+ if( !mdl_arrcount( &menu.model.textures ) )
+ vg_fatal_error( "No texture in menu file" );
+
+ mdl_texture *tex0 = mdl_arritm( &menu.model.textures, 0 );
+ void *data = vg_linear_alloc( vg_mem.scratch, tex0->file.pack_size );
+ mdl_fread_pack_file( &menu.model, &tex0->file, data );
+
+ mdl_async_load_glmesh( &menu.model, &menu.mesh, NULL );
+ vg_tex2d_load_qoi_async( data, tex0->file.pack_size,
+ VG_TEX2D_LINEAR|VG_TEX2D_CLAMP,
+ &menu.texture );
+
+ mdl_close( &menu.model );
+ shader_model_menu_register();
+}
+
+/*
+ * Drop back a page until we're at the bottom which then we jus quit
+ */
+static void menu_back_page(void){
+ menu.page_depth --;
+ if( menu.page_depth == 0 ){
+ menu_close();
+ }
+ else{
+ menu.page = menu.page_stack[ menu.page_depth ].page;
+ menu.cam = menu.page_stack[ menu.page_depth ].cam;
+
+ if( menu.input_mode == k_menu_input_mode_keys )
+ menu.loc = menu.page_stack[ menu.page_depth ].loc;
+ else menu.loc = NULL;
+ }
+}
+
+/*
+ * Open page to the string identifier
+ */
+static void menu_open_page( const char *name,
+ enum ent_menuitem_stack_behaviour stackmode ){
+ if( stackmode == k_ent_menuitem_stack_append ){
+ if( menu.page_depth >= MENU_STACK_SIZE )
+ vg_fatal_error( "Stack overflow\n" );
+ }
+ else{
+ if( menu.page_depth == 0 )
+ vg_fatal_error( "Stack underflow\n" );
+ }
+
+ u32 hash = vg_strdjb2( name );
+ for( u32 i=0; i<mdl_arrcount(&menu.items); i++ ){
+ ent_menuitem *item = mdl_arritm( &menu.items, i );
+
+ if( item->type == k_ent_menuitem_type_page ){
+ if( mdl_pstreq( &menu.model, item->page.pstr_name, name, hash ) ){
+ u32 new_page = __builtin_ctz( item->groups );
+
+ if( new_page == menu.page ){
+ if( stackmode != k_ent_menuitem_stack_replace )
+ menu_back_page();
+ }
+ else{
+ menu.page_stack[ menu.page_depth ].page = menu.page;
+ menu.page_stack[ menu.page_depth ].cam = menu.cam;
+ menu.page_stack[ menu.page_depth ].loc = menu.loc;
+
+ if( stackmode == k_ent_menuitem_stack_append )
+ menu.page_depth ++;
+
+ menu.page = __builtin_ctz( item->groups );