summaryrefslogtreecommitdiff
path: root/src/editor/editor_menubar.cpp
diff options
context:
space:
mode:
authorkasull <qsullian@gmail.com>2026-03-05 11:38:03 -0500
committerkasull <qsullian@gmail.com>2026-03-05 11:38:03 -0500
commit7c8b5072d8441aa6a7da9bd7560c47ba8c41270b (patch)
tree7f3a076dd4c651047dfcea82dbf05aa90ea83ed0 /src/editor/editor_menubar.cpp
parent54bcabc374b438ee288964d6f6314f5da2121a0e (diff)
split UI components and make menubar data-driven
replace monolithic gui.cpp with focused editor UI component files shared editor_gui_internal.h for layout constants and cross-component helpers modular menubar rendering/input
Diffstat (limited to 'src/editor/editor_menubar.cpp')
-rw-r--r--src/editor/editor_menubar.cpp430
1 files changed, 430 insertions, 0 deletions
diff --git a/src/editor/editor_menubar.cpp b/src/editor/editor_menubar.cpp
new file mode 100644
index 0000000..5491c05
--- /dev/null
+++ b/src/editor/editor_menubar.cpp
@@ -0,0 +1,430 @@
+#include "editor_gui_internal.h"
+
+U8 editor_menu_hover_mask_active = 0;
+I32 editor_menu_hover_real_x = 0;
+I32 editor_menu_hover_real_y = 0;
+
+static void editor_header_back_cb( void* ) {
+ editor_toolbar_close_menu();
+ editor_close( editor );
+}
+
+static void editor_header_mode_2d_cb( void* ) {
+ editor_set_view_mode( EDITOR_VIEWMODE_2D );
+}
+
+static void editor_header_mode_3d_cb( void* ) {
+ editor_set_view_mode( EDITOR_VIEWMODE_3D );
+}
+
+static void editor_header_mode_sim_cb( void* ) {
+ editor_set_view_mode( EDITOR_VIEWMODE_SIM );
+}
+
+void editor_apply_view_mode( GAME_EDITOR* e ) {
+ if( !e || !e->gui.v2d || !e->gui.v3d )
+ return;
+
+ if( e->gui.view_mode == EDITOR_VIEWMODE_2D ) {
+ e->gui.v2d->enabled = 1;
+ e->gui.v3d->enabled = 0;
+ return;
+ }
+
+ e->gui.v2d->enabled = 0;
+ e->gui.v3d->enabled = 1;
+}
+
+U8 editor_toolbar_menu_open( const GUI_EDITOR_TOOLBAR* bar ) {
+ return bar && bar->open_root >= 0;
+}
+
+static I32 editor_toolbar_find_root( GUI_EDITOR_TOOLBAR* bar, const char* text ) {
+ if( !bar || !text )
+ return -1;
+
+ for( I32 i = 0; i < (I32)bar->entries.size; ++i ) {
+ if( !strcmp( bar->entries[i].text, text ) )
+ return i;
+ }
+
+ return -1;
+}
+
+void editor_toolbar_set_open( GUI_EDITOR_TOOLBAR* bar, U8 file_open, U8 edit_open ) {
+ if( !bar )
+ return;
+
+ bar->open_root = -1;
+ if( file_open )
+ bar->open_root = editor_toolbar_find_root( bar, "file" );
+ else if( edit_open )
+ bar->open_root = editor_toolbar_find_root( bar, "edit" );
+}
+
+void editor_toolbar_close_menu() {
+ if( !editor )
+ return;
+
+ GUI_EDITOR_TOOLBAR* bar = (GUI_EDITOR_TOOLBAR*)editor->gui.header_toolbar;
+ editor_toolbar_set_open( bar, 0, 0 );
+}
+
+void editor_set_view_mode( I32 mode ) {
+ if( !editor )
+ return;
+
+ editor_toolbar_close_menu();
+ editor->gui.view_mode = mode;
+ editor_apply_view_mode( editor );
+ editor_layout_map_view( editor );
+}
+
+void editor_raise_header_toolbar( GAME_EDITOR* e ) {
+ if( !e || !e->map || !e->wnd || !e->gui.header_toolbar )
+ return;
+
+ GUI_BASE* bar = e->gui.header_toolbar;
+ GUI_BASE* parent = bar->parent;
+ if( !parent )
+ return;
+
+ I32 idx = parent->children.idx_of( bar );
+ if( idx == -1 || idx == (I32)parent->children.size - 1 )
+ return;
+
+ parent->children.erase( idx );
+ parent->children.push( bar );
+}
+
+static void editor_menubar_set_entry( EDITOR_MENUBAR_ENTRY* entry, const char* text, U8 type ) {
+ if( !entry )
+ return;
+
+ entry->entries.clear();
+ snprintf( entry->text, sizeof( entry->text ), "%s", text );
+ entry->type = type;
+}
+
+static void editor_toolbar_init_entries( GUI_EDITOR_TOOLBAR* bar ) {
+ if( !bar )
+ return;
+
+ bar->entries.clear();
+ bar->entries.resize( 4 );
+
+ editor_menubar_set_entry( &bar->entries[0], "file", EDITOR_MENUBAR_ENTRY_SUBMENU );
+ bar->entries[0].entries.resize( 1 );
+ editor_menubar_set_entry( &bar->entries[0].entries[0], "save", EDITOR_MENUBAR_ENTRY_FUNCTION );
+
+ editor_menubar_set_entry( &bar->entries[1], "edit", EDITOR_MENUBAR_ENTRY_SUBMENU );
+ bar->entries[1].entries.resize( 2 );
+ editor_menubar_set_entry( &bar->entries[1].entries[0], "undo", EDITOR_MENUBAR_ENTRY_FUNCTION );
+ editor_menubar_set_entry( &bar->entries[1].entries[1], "redo", EDITOR_MENUBAR_ENTRY_FUNCTION );
+
+ editor_menubar_set_entry( &bar->entries[2], "view", EDITOR_MENUBAR_ENTRY_SUBMENU );
+ bar->entries[2].entries.resize( 3 );
+ editor_menubar_set_entry( &bar->entries[2].entries[0], "2d view", EDITOR_MENUBAR_ENTRY_FUNCTION );
+ editor_menubar_set_entry( &bar->entries[2].entries[1], "3d view", EDITOR_MENUBAR_ENTRY_FUNCTION );
+ editor_menubar_set_entry( &bar->entries[2].entries[2], "simulation", EDITOR_MENUBAR_ENTRY_FUNCTION );
+
+ editor_menubar_set_entry( &bar->entries[3], "tools", EDITOR_MENUBAR_ENTRY_SUBMENU );
+ bar->entries[3].entries.resize( 6 );
+ editor_menubar_set_entry( &bar->entries[3].entries[0], "none", EDITOR_MENUBAR_ENTRY_FUNCTION );
+ editor_menubar_set_entry( &bar->entries[3].entries[1], "select", EDITOR_MENUBAR_ENTRY_FUNCTION );
+ editor_menubar_set_entry( &bar->entries[3].entries[2], "wall", EDITOR_MENUBAR_ENTRY_FUNCTION );
+ editor_menubar_set_entry( &bar->entries[3].entries[3], "poly", EDITOR_MENUBAR_ENTRY_FUNCTION );
+ editor_menubar_set_entry( &bar->entries[3].entries[4], "sprite", EDITOR_MENUBAR_ENTRY_FUNCTION );
+ editor_menubar_set_entry( &bar->entries[3].entries[5], "ent", EDITOR_MENUBAR_ENTRY_FUNCTION );
+}
+
+static I32 editor_toolbar_root_width( EDITOR_MENUBAR_ENTRY* entry ) {
+ if( !entry )
+ return 44;
+
+ I32 text_w = 0;
+ gui_draw_get_str_bounds( &text_w, 0, FNT_JPN12, "%s", entry->text );
+ return max( 44, text_w + 16 );
+}
+
+static I32 editor_toolbar_dropdown_width( EDITOR_MENUBAR_ENTRY* entry ) {
+ if( !entry || entry->type != EDITOR_MENUBAR_ENTRY_SUBMENU )
+ return EDITOR_TOOLBAR_DROPDOWN_W;
+
+ I32 width = EDITOR_TOOLBAR_DROPDOWN_W;
+ for( I32 i = 0; i < (I32)entry->entries.size; ++i ) {
+ I32 text_w = 0;
+ gui_draw_get_str_bounds( &text_w, 0, FNT_JPN12, "%s", entry->entries[i].text );
+ width = max( width, text_w + 20 );
+ }
+
+ return width;
+}
+
+static void editor_toolbar_root_rect( GUI_EDITOR_TOOLBAR* bar, I32 idx, I32* rx, I32* ry, I32* rw, I32* rh ) {
+ I32 x = gui_relx( bar );
+ I32 y = gui_rely( bar );
+ I32 cx = x + 8;
+ for( I32 i = 0; i < idx; ++i )
+ cx += editor_toolbar_root_width( &bar->entries[i] ) + 4;
+
+ I32 w = editor_toolbar_root_width( &bar->entries[idx] );
+ *rx = cx;
+ *ry = y + 2;
+ *rw = w;
+ *rh = max( 1, bar->h - 4 );
+}
+
+static void editor_toolbar_sub_rect( GUI_EDITOR_TOOLBAR* bar, I32 root_idx, I32 row, I32* rx, I32* ry, I32* rw, I32* rh ) {
+ I32 root_x = 0, root_y = 0, root_w = 0, root_h = 0;
+ editor_toolbar_root_rect( bar, root_idx, &root_x, &root_y, &root_w, &root_h );
+ I32 row_h = max( 18, root_h );
+
+ *rx = root_x;
+ *ry = gui_rely( bar ) + bar->h + row * row_h;
+ *rw = editor_toolbar_dropdown_width( &bar->entries[root_idx] );
+ *rh = row_h;
+}
+
+static U8 editor_toolbar_pt_in_rect( I32 mx, I32 my, I32 x, I32 y, I32 w, I32 h ) {
+ return mx >= x && mx < x + w && my >= y && my < y + h;
+}
+
+struct EDITOR_MENUBAR_HIT {
+ I32 root{-1};
+ I32 sub{-1};
+};
+
+static EDITOR_MENUBAR_HIT editor_toolbar_hit_test( GUI_EDITOR_TOOLBAR* bar, I32 mx, I32 my ) {
+ EDITOR_MENUBAR_HIT hit{};
+ for( I32 i = 0; i < (I32)bar->entries.size; ++i ) {
+ I32 x = 0, y = 0, w = 0, h = 0;
+ editor_toolbar_root_rect( bar, i, &x, &y, &w, &h );
+ if( editor_toolbar_pt_in_rect( mx, my, x, y, w, h ) ) {
+ hit.root = i;
+ return hit;
+ }
+ }
+
+ if( bar->open_root >= 0 && bar->open_root < (I32)bar->entries.size ) {
+ EDITOR_MENUBAR_ENTRY* root = &bar->entries[bar->open_root];
+ if( root->type == EDITOR_MENUBAR_ENTRY_SUBMENU ) {
+ for( I32 i = 0; i < (I32)root->entries.size; ++i ) {
+ I32 x = 0, y = 0, w = 0, h = 0;
+ editor_toolbar_sub_rect( bar, bar->open_root, i, &x, &y, &w, &h );
+ if( editor_toolbar_pt_in_rect( mx, my, x, y, w, h ) ) {
+ hit.root = bar->open_root;
+ hit.sub = i;
+ return hit;
+ }
+ }
+ }
+ }
+
+ return hit;
+}
+
+static U8 editor_toolbar_entry_enabled( const EDITOR_MENUBAR_ENTRY* root, const EDITOR_MENUBAR_ENTRY* entry ) {
+ if( !root || !entry )
+ return 0;
+
+ if( !strcmp( root->text, "edit" ) && !strcmp( entry->text, "undo" ) )
+ return editor && editor->undo_actions.size > 0;
+ if( !strcmp( root->text, "edit" ) && !strcmp( entry->text, "redo" ) )
+ return editor && editor->redo_actions.size > 0;
+
+ return 1;
+}
+
+static void editor_toolbar_invoke( const EDITOR_MENUBAR_ENTRY* root, const EDITOR_MENUBAR_ENTRY* entry ) {
+ if( !root || !entry || !editor )
+ return;
+ if( !editor_toolbar_entry_enabled( root, entry ) )
+ return;
+
+ if( !strcmp( root->text, "file" ) && !strcmp( entry->text, "save" ) ) {
+ editor_save_map( editor );
+ return;
+ }
+ if( !strcmp( root->text, "edit" ) && !strcmp( entry->text, "undo" ) ) {
+ editor_undo( editor );
+ return;
+ }
+ if( !strcmp( root->text, "edit" ) && !strcmp( entry->text, "redo" ) ) {
+ editor_redo( editor );
+ return;
+ }
+ if( !strcmp( root->text, "view" ) && !strcmp( entry->text, "2d view" ) ) {
+ editor_set_view_mode( EDITOR_VIEWMODE_2D );
+ return;
+ }
+ if( !strcmp( root->text, "view" ) && !strcmp( entry->text, "3d view" ) ) {
+ editor_set_view_mode( EDITOR_VIEWMODE_3D );
+ return;
+ }
+ if( !strcmp( root->text, "view" ) && !strcmp( entry->text, "simulation" ) ) {
+ editor_set_view_mode( EDITOR_VIEWMODE_SIM );
+ return;
+ }
+ if( !strcmp( root->text, "tools" ) ) {
+ if( !strcmp( entry->text, "none" ) ) settool( EDITOR_TOOL_NONE );
+ else if( !strcmp( entry->text, "select" ) ) settool( EDITOR_TOOL_SELECT );
+ else if( !strcmp( entry->text, "wall" ) ) settool( EDITOR_TOOL_WALL );
+ else if( !strcmp( entry->text, "poly" ) ) settool( EDITOR_TOOL_POLY );
+ else if( !strcmp( entry->text, "sprite" ) ) settool( EDITOR_TOOL_SPRITE );
+ else if( !strcmp( entry->text, "ent" ) ) settool( EDITOR_TOOL_ENT );
+ }
+}
+
+static void gui_editor_toolbar_draw_fn( void* ptr ) {
+ GUI_EDITOR_TOOLBAR* bar = (GUI_EDITOR_TOOLBAR*)ptr;
+ I32 x = gui_relx( bar );
+ I32 y = gui_rely( bar );
+ I32 mx = 0, my = 0;
+ if( editor_menu_hover_mask_active ) {
+ mx = editor_menu_hover_real_x;
+ my = editor_menu_hover_real_y;
+ } else {
+ gui_cursor_pos( &mx, &my );
+ }
+
+ U8 m1 = gui_mbutton_down( GUI_MBTNLEFT );
+ EDITOR_MENUBAR_HIT hit = editor_toolbar_hit_test( bar, mx, my );
+ CLR hover_fill = CLR::blend( ui_clr.border, ui_clr.bg, 0.70f );
+ CLR active_fill = CLR::blend( ui_clr.bg_sec, ui_clr.border, 0.22f );
+
+ CLR border = gui_is_fg_window( bar ) ? ui_clr.border : ui_clr.border_inactive;
+ gui_draw_frect( x, y, bar->w, bar->h, ui_clr.bg_sec );
+
+ for( I32 i = 0; i < (I32)bar->entries.size; ++i ) {
+ I32 rx = 0, ry = 0, rw = 0, rh = 0;
+ editor_toolbar_root_rect( bar, i, &rx, &ry, &rw, &rh );
+ U8 is_hover = hit.root == i && hit.sub == -1;
+ U8 is_active = bar->held && bar->held_root == i && bar->held_sub == -1 && m1;
+ U8 is_open = bar->open_root == i;
+
+ CLR fill = ui_clr.bg_sec;
+ if( is_open || is_hover )
+ fill = hover_fill;
+ if( is_active )
+ fill = active_fill;
+
+ if( is_open || is_active || is_hover )
+ gui_draw_frect( rx, ry, rw, rh, fill );
+ gui_draw_str( rx + 6, y + bar->h / 2 - 8, ALIGN_L, FNT_JPN12, ui_clr.txt, "%s", bar->entries[i].text );
+ }
+
+ if( bar->open_root < 0 || bar->open_root >= (I32)bar->entries.size )
+ return;
+
+ EDITOR_MENUBAR_ENTRY* root = &bar->entries[bar->open_root];
+ if( root->type != EDITOR_MENUBAR_ENTRY_SUBMENU )
+ return;
+
+ if( !root->entries.size )
+ return;
+
+ I32 panel_x = 0, panel_y = 0, panel_w = 0, panel_h = 0;
+ editor_toolbar_sub_rect( bar, bar->open_root, 0, &panel_x, &panel_y, &panel_w, &panel_h );
+ I32 row_h = panel_h;
+ gui_draw_frect( panel_x, panel_y, panel_w, row_h * root->entries.size, border );
+ gui_draw_frect( panel_x + 1, panel_y + 1, max( 1, panel_w - 2 ), max( 1, row_h * (I32)root->entries.size - 2 ), ui_clr.bg_sec );
+
+ for( I32 i = 0; i < (I32)root->entries.size; ++i ) {
+ EDITOR_MENUBAR_ENTRY* entry = &root->entries[i];
+ I32 rx = 0, ry = 0, rw = 0, rh = 0;
+ editor_toolbar_sub_rect( bar, bar->open_root, i, &rx, &ry, &rw, &rh );
+
+ U8 enabled = editor_toolbar_entry_enabled( root, entry );
+ U8 is_hover = hit.root == bar->open_root && hit.sub == i;
+ U8 is_active = bar->held && bar->held_root == bar->open_root && bar->held_sub == i && m1;
+
+ CLR fill = ui_clr.bg_sec;
+ if( enabled && is_active )
+ fill = active_fill;
+ else if( is_hover )
+ fill = hover_fill;
+ gui_draw_frect( rx + 1, ry + 1, max( 1, rw - 2 ), max( 1, rh - 2 ), fill );
+
+ CLR txt_clr = enabled ? ui_clr.txt : ui_clr.txt_inactive;
+ gui_draw_str( rx + 8, ry + rh / 2 - 8, ALIGN_L, FNT_JPN12, txt_clr, "%s", entry->text );
+ }
+}
+
+static void gui_editor_toolbar_input_fn( void* ptr ) {
+ GUI_EDITOR_TOOLBAR* bar = (GUI_EDITOR_TOOLBAR*)ptr;
+ I32 mx, my;
+ gui_cursor_pos( &mx, &my );
+ U8 m1 = gui_mbutton_down( GUI_MBTNLEFT );
+ EDITOR_MENUBAR_HIT hit = editor_toolbar_hit_test( bar, mx, my );
+
+ if( m1 ) {
+ if( !bar->held ) {
+ bar->held = 1;
+ bar->held_root = hit.root;
+ bar->held_sub = hit.sub;
+ if( hit.root == -1 && editor_toolbar_menu_open( bar ) )
+ bar->open_root = -1;
+ }
+ return;
+ }
+
+ if( bar->held && bar->held_root == hit.root && bar->held_sub == hit.sub ) {
+ if( hit.sub >= 0 ) {
+ EDITOR_MENUBAR_ENTRY* root = &bar->entries[hit.root];
+ EDITOR_MENUBAR_ENTRY* sub = &root->entries[hit.sub];
+ editor_toolbar_invoke( root, sub );
+ bar->open_root = -1;
+ } else if( hit.root >= 0 ) {
+ EDITOR_MENUBAR_ENTRY* root = &bar->entries[hit.root];
+ if( root->type == EDITOR_MENUBAR_ENTRY_SUBMENU ) {
+ if( bar->open_root == hit.root ) bar->open_root = -1;
+ else bar->open_root = hit.root;
+ } else {
+ editor_toolbar_invoke( root, root );
+ bar->open_root = -1;
+ }
+ }
+ }
+
+ bar->held = 0;
+ bar->held_root = -1;
+ bar->held_sub = -1;
+}
+
+GUI_EDITOR_TOOLBAR* gui_editor_toolbar( I32 x, I32 y, I32 w, I32 h, const char* name ) {
+ if( !gui_check_target() )
+ return 0;
+
+ GUI_EDITOR_TOOLBAR* bar = new GUI_EDITOR_TOOLBAR;
+ bar->x = x;
+ bar->y = y;
+ bar->w = w;
+ bar->h = h;
+ bar->xbound = w;
+ bar->ybound = h + 120;
+ bar->draw_fn = gui_editor_toolbar_draw_fn;
+ bar->input_fn = gui_editor_toolbar_input_fn;
+ bar->held = 0;
+ bar->held_root = -1;
+ bar->held_sub = -1;
+ bar->open_root = -1;
+ strcpy( bar->name, name );
+ editor_toolbar_init_entries( bar );
+
+ GUI_VIEW* parent = gui_get_view();
+ bar->parent = parent;
+ parent->children.push( bar );
+ return bar;
+}
+
+void editor_create_header_controls( GAME_EDITOR* e ) {
+ GAME_EDITOR::EDITOR_GUI* egui = &e->gui;
+ egui->header_toolbar = gui_editor_toolbar( 1, EDITOR_LAYOUT_MENU_Y, e->wnd->w - 3, EDITOR_LAYOUT_MENU_H, "editor toolbar" );
+ egui->header_back = gui_button( 10, EDITOR_LAYOUT_NAV_Y, 92, 20, "[ back ]", editor_header_back_cb );
+ egui->header_viewtype_label = gui_label( 250, EDITOR_LAYOUT_NAV_Y + 4, "view type:" );
+ egui->header_viewtype = gui_editor_viewtype_segment( 320, EDITOR_LAYOUT_NAV_Y, 186, 20, "viewtype segment" );
+ egui->header_mode_2d = gui_button( 500, EDITOR_LAYOUT_NAV_Y, 92, 20, "[ 2d view ]", editor_header_mode_2d_cb );
+ egui->header_mode_3d = gui_button( 600, EDITOR_LAYOUT_NAV_Y, 92, 20, "[ 3d view ]", editor_header_mode_3d_cb );
+ egui->header_mode_sim = gui_button( 700, EDITOR_LAYOUT_NAV_Y, 92, 20, "[ simulation ]", editor_header_mode_sim_cb );
+}