From ae6718ec0fb21077b767e189aca26b0fed488775 Mon Sep 17 00:00:00 2001 From: kasull Date: Wed, 11 Mar 2026 00:24:47 -0400 Subject: 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 --- src/editor/editor_contextmenu.cpp | 235 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 src/editor/editor_contextmenu.cpp (limited to 'src/editor/editor_contextmenu.cpp') 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* 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* 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* 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; +} -- cgit v1.2.3