summaryrefslogtreecommitdiff
path: root/src/editor/editor_contextmenu.cpp
diff options
context:
space:
mode:
authorkasull <qsullian@gmail.com>2026-03-11 00:24:47 -0400
committerkasull <qsullian@gmail.com>2026-03-11 00:24:47 -0400
commitae6718ec0fb21077b767e189aca26b0fed488775 (patch)
treea4216103d8a9a77edbc470dc4ab094e77ac30261 /src/editor/editor_contextmenu.cpp
parentbc1ea16c5be92e3bc810b0a30e01fbc9a7f191a9 (diff)
editor object placement and context menus
replace the editor menubar dropdown flow with a reusable context menu component merge sprite/entity placement into a single object tool persist entities, and add undo support for created sprites and entities
Diffstat (limited to 'src/editor/editor_contextmenu.cpp')
-rw-r--r--src/editor/editor_contextmenu.cpp235
1 files changed, 235 insertions, 0 deletions
diff --git a/src/editor/editor_contextmenu.cpp b/src/editor/editor_contextmenu.cpp
new file mode 100644
index 0000000..2b0bb1f
--- /dev/null
+++ b/src/editor/editor_contextmenu.cpp
@@ -0,0 +1,235 @@
+#include "editor_gui_internal.h"
+
+static U8 editor_contextmenu_item_enabled( const EDITOR_CONTEXTMENU_ITEM* item ) {
+ if( !item )
+ return 0;
+
+ if( item->enabled_cb )
+ return item->enabled_cb( item );
+
+ return item->enabled;
+}
+
+static I32 editor_contextmenu_title_h( const GUI_EDITOR_CONTEXTMENU* menu ) {
+ return ( menu && menu->title[0] ) ? 18 : 0;
+}
+
+static I32 editor_contextmenu_row_h() {
+ return 20;
+}
+
+static I32 editor_contextmenu_width( LIST<EDITOR_CONTEXTMENU_ITEM>* items, const char* title ) {
+ I32 width = EDITOR_TOOLBAR_DROPDOWN_W;
+ if( title && title[0] ) {
+ I32 text_w = 0;
+ gui_draw_get_str_bounds( &text_w, 0, FNT_JPN12, "%s", title );
+ width = max( width, text_w + 18 );
+ }
+
+ if( !items )
+ return width;
+
+ for( I32 i = 0; i < (I32)items->size; ++i ) {
+ I32 text_w = 0;
+ gui_draw_get_str_bounds( &text_w, 0, FNT_JPN12, "%s", ( *items )[i].text );
+ width = max( width, text_w + 20 );
+ }
+
+ return width;
+}
+
+static I32 editor_contextmenu_height( LIST<EDITOR_CONTEXTMENU_ITEM>* items, const char* title ) {
+ I32 height = 4 + editor_contextmenu_row_h() * ( items ? (I32)items->size : 0 );
+ if( title && title[0] )
+ height += 18 + 1;
+ return max( height, editor_contextmenu_row_h() + 4 );
+}
+
+static I32 editor_contextmenu_items_y( GUI_EDITOR_CONTEXTMENU* menu ) {
+ I32 y = gui_rely( menu ) + 2;
+ if( menu->title[0] )
+ y += editor_contextmenu_title_h( menu ) + 1;
+ return y;
+}
+
+static I32 editor_contextmenu_hit_test( GUI_EDITOR_CONTEXTMENU* menu, I32 mx, I32 my ) {
+ if( !menu || !menu->items )
+ return -1;
+
+ I32 x = gui_relx( menu );
+ I32 y = gui_rely( menu );
+ if( mx < x || mx >= x + menu->w || my < y || my >= y + menu->h )
+ return -1;
+
+ I32 items_y = editor_contextmenu_items_y( menu );
+ if( my < items_y )
+ return -1;
+
+ I32 idx = ( my - items_y ) / editor_contextmenu_row_h();
+ if( idx < 0 || idx >= (I32)menu->items->size )
+ return -1;
+
+ return idx;
+}
+
+static void gui_editor_contextmenu_draw_fn( void* ptr ) {
+ GUI_EDITOR_CONTEXTMENU* menu = (GUI_EDITOR_CONTEXTMENU*)ptr;
+ if( !menu || !menu->items || !menu->items->size )
+ return;
+
+ I32 x = gui_relx( menu );
+ I32 y = gui_rely( menu );
+ 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 );
+ }
+ I32 hit = editor_contextmenu_hit_test( menu, mx, my );
+ U8 m1 = gui_mbutton_down( GUI_MBTNLEFT );
+ 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( menu ) ? ui_clr.border : ui_clr.border_inactive;
+ gui_draw_frect( x, y, menu->w, menu->h, border );
+ gui_draw_frect( x + 1, y + 1, max( 1, menu->w - 2 ), max( 1, menu->h - 2 ), ui_clr.bg_sec );
+
+ I32 cy = y + 2;
+ if( menu->title[0] ) {
+ gui_draw_str( x + 8, cy + 1, ALIGN_L, FNT_JPN12, ui_clr.txt, "%s", menu->title );
+ cy += editor_contextmenu_title_h( menu );
+ gui_draw_line( x + 1, cy, x + menu->w - 2, cy, border );
+ cy += 1;
+ }
+
+ for( I32 i = 0; i < (I32)menu->items->size; ++i ) {
+ EDITOR_CONTEXTMENU_ITEM* item = &( *menu->items )[i];
+ U8 enabled = editor_contextmenu_item_enabled( item );
+ U8 hovered = hit == i;
+ U8 active = menu->held && menu->held_idx == i && m1 && enabled;
+
+ CLR fill = ui_clr.bg_sec;
+ if( active )
+ fill = active_fill;
+ else if( hovered )
+ fill = hover_fill;
+
+ gui_draw_frect( x + 1, cy + i * editor_contextmenu_row_h(), max( 1, menu->w - 2 ), editor_contextmenu_row_h(), fill );
+ gui_draw_str(
+ x + 8,
+ cy + i * editor_contextmenu_row_h() + 2,
+ ALIGN_L,
+ FNT_JPN12,
+ enabled ? ui_clr.txt : ui_clr.txt_inactive,
+ "%s",
+ item->text
+ );
+ }
+}
+
+static void gui_editor_contextmenu_input_fn( void* ptr ) {
+ GUI_EDITOR_CONTEXTMENU* menu = (GUI_EDITOR_CONTEXTMENU*)ptr;
+ if( !menu || !menu->items || !menu->items->size )
+ return;
+
+ U8 m1 = gui_mbutton_down( GUI_MBTNLEFT );
+ U8 m2 = gui_mbutton_down( GUI_MBTNRIGHT );
+ if( menu->initheld ) {
+ if( m1 || m2 )
+ return;
+ menu->initheld = 0;
+ return;
+ }
+
+ I32 mx = 0, my = 0;
+ gui_cursor_pos( &mx, &my );
+ I32 hit = editor_contextmenu_hit_test( menu, mx, my );
+
+ if( m1 || m2 ) {
+ if( !menu->held ) {
+ menu->held = 1;
+ menu->held_idx = hit;
+ }
+ return;
+ }
+
+ if( !menu->held )
+ return;
+
+ if( hit == -1 ) {
+ editor_hide_contextmenu();
+ return;
+ }
+
+ if( hit == menu->held_idx ) {
+ EDITOR_CONTEXTMENU_ITEM* item = &( *menu->items )[hit];
+ if( editor_contextmenu_item_enabled( item ) && item->cb ) {
+ item->cb( item->data );
+ editor_hide_contextmenu();
+ return;
+ }
+ }
+
+ menu->held = 0;
+ menu->held_idx = -1;
+}
+
+void editor_hide_contextmenu() {
+ if( !editor || !editor->gui.contextmenu )
+ return;
+
+ GUI_EDITOR_CONTEXTMENU* menu = editor->gui.contextmenu;
+ editor->gui.contextmenu = 0;
+
+ if( menu->parent ) {
+ I32 idx = menu->parent->children.idx_of( (GUI_BASE*)menu );
+ if( idx != -1 )
+ menu->parent->children.erase( idx );
+ }
+
+ GUI_EDITOR_TOOLBAR* bar = (GUI_EDITOR_TOOLBAR*)editor->gui.header_toolbar;
+ if( bar )
+ bar->active_root = -1;
+
+ gui_free( menu );
+}
+
+U8 editor_contextmenu_open() {
+ return editor && editor->gui.contextmenu && editor->gui.contextmenu->enabled;
+}
+
+void editor_show_contextmenu( I32 x, I32 y, LIST<EDITOR_CONTEXTMENU_ITEM>* items, const char* title ) {
+ editor_hide_contextmenu();
+ if( !editor || !editor->wnd || !items || !items->size )
+ return;
+
+ GUI_EDITOR_CONTEXTMENU* menu = new GUI_EDITOR_CONTEXTMENU;
+ menu->items = items;
+ menu->draw_fn = gui_editor_contextmenu_draw_fn;
+ menu->input_fn = gui_editor_contextmenu_input_fn;
+ menu->enabled = 1;
+ menu->initheld = 1;
+ menu->held = 0;
+ menu->held_idx = -1;
+ menu->parent = editor->wnd;
+ snprintf( menu->name, sizeof( menu->name ), "EDITOR_CONTEXTMENU" );
+ snprintf( menu->title, sizeof( menu->title ), "%s", title ? title : "" );
+
+ menu->w = editor_contextmenu_width( items, title );
+ menu->h = editor_contextmenu_height( items, title );
+ menu->xbound = menu->w;
+ menu->ybound = menu->h;
+
+ if( x + menu->w > editor->wnd->w - 2 )
+ x = max( 1, editor->wnd->w - menu->w - 2 );
+ if( y + menu->h > editor->wnd->h - 2 )
+ y = max( 1, editor->wnd->h - menu->h - 2 );
+ if( x < 1 ) x = 1;
+ if( y < 1 ) y = 1;
+
+ menu->x = x;
+ menu->y = y;
+ editor->wnd->children.push( menu );
+ editor->gui.contextmenu = menu;
+}