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.cpp | 138 ++++++++++++++++ src/editor/editor.h | 55 +++++-- src/editor/editor_contextmenu.cpp | 235 ++++++++++++++++++++++++++ src/editor/editor_layout.cpp | 2 +- src/editor/editor_menubar.cpp | 337 ++++++++++++-------------------------- src/editor/editor_window.cpp | 28 ++-- src/editor/properties.cpp | 16 ++ src/editor/toolview.cpp | 169 ++++++++++++++++++- src/editor/view2d.cpp | 149 ++++++++++++++--- src/game/world/map.cpp | 56 +++++++ src/game/world/map.h | 2 + src/gui/list.cpp | 3 +- 12 files changed, 903 insertions(+), 287 deletions(-) create mode 100644 src/editor/editor_contextmenu.cpp (limited to 'src') diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index 3873913..dee5080 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -1,6 +1,7 @@ #include "editor.h" #include "../game.h" +#include "../game/object.h" GAME_EDITOR* editor = 0; @@ -20,6 +21,7 @@ void editor_clear_gui_state_refs( GAME_EDITOR* e ) { e->gui.status = 0; e->gui.assets_scroll = 0; + e->gui.contextmenu = 0; e->gui.header_toolbar = 0; e->gui.header_viewtype_label = 0; e->gui.header_back = 0; @@ -82,6 +84,18 @@ static U8 editor_poly_eq( const MAP_POLYGON& a, const MAP_POLYGON& b ) { return 1; } +static U8 editor_sprite_eq( const MAP_SPRITE& a, const MAP_SPRITE& b ) { + return a.pos == b.pos + && a.size == b.size + && editor_clr_eq( a.clr, b.clr ) + && a.tex == b.tex; +} + +static U8 editor_entity_eq( const MAP_ENTITY& a, const MAP_ENTITY& b ) { + return a.pos == b.pos + && a.classid == b.classid; +} + static I32 editor_clamp_valid_idx( I32 idx, I32 size ) { if( size <= 0 ) return -1; @@ -139,6 +153,24 @@ static I32 editor_find_poly_idx( WORLD_MAP* map, const MAP_POLYGON& want, I32 ex } ); } +static I32 editor_find_sprite_idx( WORLD_MAP* map, const MAP_SPRITE& want, I32 expected_idx ) { + if( !map ) + return -1; + + return editor_find_nearest_idx( (I32)map->sprites.size, expected_idx, fn( I32 i ) { + return editor_sprite_eq( map->sprites[i], want ); + } ); +} + +static I32 editor_find_entity_idx( WORLD_MAP* map, const MAP_ENTITY& want, I32 expected_idx ) { + if( !map ) + return -1; + + return editor_find_nearest_idx( (I32)map->entities.size, expected_idx, fn( I32 i ) { + return editor_entity_eq( map->entities[i], want ); + } ); +} + static void editor_mark_clear_wall_refs( GAME_EDITOR* e, MAP_WALL* w, U8* clear_props_select, U8* clear_view_select, U8* clear_view_drag ) { if( e->gui.props ) { if( e->gui.props->seltype == EDITOR_SELECT_WALL && e->gui.props->curselect == w ) @@ -196,6 +228,30 @@ static void editor_mark_clear_poly_refs( GAME_EDITOR* e, MAP_POLYGON* p, U8* cle } } +static void editor_mark_clear_sprite_refs( GAME_EDITOR* e, MAP_SPRITE* s, U8* clear_props_select, U8* clear_view_select, U8* clear_view_drag ) { + if( e->gui.props && e->gui.props->seltype == EDITOR_SELECT_SPRITE && e->gui.props->curselect == s ) + *clear_props_select = 1; + + if( e->gui.v2d ) { + if( e->gui.v2d->seltype == EDITOR_SELECT_SPRITE && e->gui.v2d->curselect == s ) + *clear_view_select = 1; + if( e->gui.v2d->dragtype == EDITOR_SELECT_SPRITE && e->gui.v2d->curdrag == s ) + *clear_view_drag = 1; + } +} + +static void editor_mark_clear_entity_refs( GAME_EDITOR* e, MAP_ENTITY* ent, U8* clear_props_select, U8* clear_view_select, U8* clear_view_drag ) { + if( e->gui.props && e->gui.props->seltype == EDITOR_SELECT_ENT && e->gui.props->curselect == ent ) + *clear_props_select = 1; + + if( e->gui.v2d ) { + if( e->gui.v2d->seltype == EDITOR_SELECT_ENT && e->gui.v2d->curselect == ent ) + *clear_view_select = 1; + if( e->gui.v2d->dragtype == EDITOR_SELECT_ENT && e->gui.v2d->curdrag == ent ) + *clear_view_drag = 1; + } +} + void editor_undo_clear( GAME_EDITOR* e ) { if( !e ) return; @@ -243,6 +299,38 @@ void editor_undo_record_create_poly( GAME_EDITOR* e, I32 start_idx ) { editor_push_undo_action( e, action ); } +void editor_undo_record_create_sprite( GAME_EDITOR* e, I32 start_idx ) { + if( !e || !e->map ) + return; + + WORLD_MAP* map = e->map; + if( start_idx < 0 || start_idx >= (I32)map->sprites.size ) + return; + + GAME_EDITOR::EDITOR_UNDO_ACTION action{}; + action.type = EDITOR_UNDO_CREATE_SPRITES; + action.start_idx = start_idx; + action.sprites.push( map->sprites[start_idx] ); + + editor_push_undo_action( e, action ); +} + +void editor_undo_record_create_entity( GAME_EDITOR* e, I32 start_idx ) { + if( !e || !e->map ) + return; + + WORLD_MAP* map = e->map; + if( start_idx < 0 || start_idx >= (I32)map->entities.size ) + return; + + GAME_EDITOR::EDITOR_UNDO_ACTION action{}; + action.type = EDITOR_UNDO_CREATE_ENTITIES; + action.start_idx = start_idx; + action.entities.push( map->entities[start_idx] ); + + editor_push_undo_action( e, action ); +} + static U8 editor_apply_undo_action_reverse( GAME_EDITOR* e, GAME_EDITOR::EDITOR_UNDO_ACTION& action ) { if( !e || !e->map ) return 0; @@ -289,6 +377,38 @@ static U8 editor_apply_undo_action_reverse( GAME_EDITOR* e, GAME_EDITOR::EDITOR_ map->polygons.erase( idx ); } } break; + case EDITOR_UNDO_CREATE_SPRITES: { + I32 count = (I32)action.sprites.size; + for( I32 i = count - 1; i >= 0; --i ) { + if( !map->sprites.size ) + break; + + I32 expected = action.start_idx + i; + I32 idx = editor_find_sprite_idx( map, action.sprites[i], expected ); + if( idx < 0 ) + idx = editor_clamp_valid_idx( expected, (I32)map->sprites.size ); + + MAP_SPRITE* s = &map->sprites[idx]; + editor_mark_clear_sprite_refs( e, s, &clear_props_select, &clear_view_select, &clear_view_drag ); + map->sprites.erase( idx ); + } + } break; + case EDITOR_UNDO_CREATE_ENTITIES: { + I32 count = (I32)action.entities.size; + for( I32 i = count - 1; i >= 0; --i ) { + if( !map->entities.size ) + break; + + I32 expected = action.start_idx + i; + I32 idx = editor_find_entity_idx( map, action.entities[i], expected ); + if( idx < 0 ) + idx = editor_clamp_valid_idx( expected, (I32)map->entities.size ); + + MAP_ENTITY* ent = &map->entities[idx]; + editor_mark_clear_entity_refs( e, ent, &clear_props_select, &clear_view_select, &clear_view_drag ); + map->entities.erase( idx ); + } + } break; default: return 0; } @@ -333,6 +453,22 @@ static U8 editor_apply_undo_action_forward( GAME_EDITOR* e, GAME_EDITOR::EDITOR_ map->polygons.push( *p ); } ); } break; + case EDITOR_UNDO_CREATE_SPRITES: { + if( !action.sprites.size ) + return 0; + + action.sprites.each( fn( MAP_SPRITE* s ) { + map->sprites.push( *s ); + } ); + } break; + case EDITOR_UNDO_CREATE_ENTITIES: { + if( !action.entities.size ) + return 0; + + action.entities.each( fn( MAP_ENTITY* ent ) { + map->entities.push( *ent ); + } ); + } break; default: return 0; } @@ -415,6 +551,8 @@ GAME_EDITOR* editor_create( GAME_DATA* game ) { e->tool.polysides = EDITOR_DEFAULT_POLY_SIDES; e->tool.wallheight = EDITOR_DEFAULT_WALL_HEIGHT; e->tool.placementheight = EDITOR_DEFAULT_PLACEMENT_HEIGHT; + e->tool.entclass = OBJCLASS_TRIGGER; + e->tool.objecttype = EDITOR_OBJECT_SPRITE; e->game = game; gui_init( game ); diff --git a/src/editor/editor.h b/src/editor/editor.h index 6735d47..828f2e4 100644 --- a/src/editor/editor.h +++ b/src/editor/editor.h @@ -14,8 +14,7 @@ enum EditorTools_t { EDITOR_TOOL_SELECT, EDITOR_TOOL_WALL, EDITOR_TOOL_POLY, - EDITOR_TOOL_SPRITE, - EDITOR_TOOL_ENT + EDITOR_TOOL_OBJECT }; enum EditorWallShape_t { @@ -38,7 +37,9 @@ enum EditorSelectType_t { enum EditorUndoType_t { EDITOR_UNDO_NONE = 0, EDITOR_UNDO_CREATE_WALLS = 1, - EDITOR_UNDO_CREATE_POLY = 2 + EDITOR_UNDO_CREATE_POLY = 2, + EDITOR_UNDO_CREATE_SPRITES = 3, + EDITOR_UNDO_CREATE_ENTITIES = 4 }; enum EditorInfoBoxType_t { @@ -58,15 +59,22 @@ enum Editor2DViewType_t { EDITOR_2DVIEW_FRONT_ELEVATION = 2 }; -enum EditorMenubarEntryType_t { - EDITOR_MENUBAR_ENTRY_FUNCTION = 0, - EDITOR_MENUBAR_ENTRY_SUBMENU = 1 +enum EditorObjectType_t { + EDITOR_OBJECT_SPRITE = 0, + EDITOR_OBJECT_ENTITY = 1 }; -struct EDITOR_MENUBAR_ENTRY { +typedef void( *EDITOR_CONTEXTMENU_CALLBACK )( void* data ); +struct EDITOR_CONTEXTMENU_ITEM; +typedef U8( *EDITOR_CONTEXTMENU_ENABLED_CALLBACK )( const EDITOR_CONTEXTMENU_ITEM* item ); + +struct EDITOR_CONTEXTMENU_ITEM { char text[64]{}; - U8 type{}; - LIST entries{}; + void* data{}; + EDITOR_CONTEXTMENU_CALLBACK cb{}; + EDITOR_CONTEXTMENU_ENABLED_CALLBACK enabled_cb{}; + U8 enabled{1}; + LIST items{}; }; struct GAME_EDITOR; @@ -85,6 +93,7 @@ struct GAME_EDITOR_TOOL { /* entity */ U32 entclass; void* entprops; + I32 objecttype; }; struct GAME_EDITOR { @@ -115,6 +124,7 @@ struct GAME_EDITOR { I32 view_mode{}; I32 view2d_type{}; + struct GUI_EDITOR_CONTEXTMENU* contextmenu{}; GUI_BASE* header_toolbar{}; GUI_LABEL* header_viewtype_label{}; GUI_BUTTON* header_back{}; @@ -145,6 +155,8 @@ struct GAME_EDITOR { I32 start_idx{}; LIST walls{}; LIST polys{}; + LIST sprites{}; + LIST entities{}; }; LIST undo_actions{}; LIST redo_actions{}; @@ -197,11 +209,14 @@ extern void editor_create_map_view( GAME_EDITOR* e ); extern void editor_update_properties_column( GAME_EDITOR* e ); extern const char* editor_tool_name(); extern void editor_set_view_mode( I32 mode ); -extern void editor_toolbar_set_open( struct GUI_EDITOR_TOOLBAR* bar, U8 file_open, U8 edit_open ); -extern U8 editor_toolbar_menu_open( const struct GUI_EDITOR_TOOLBAR* bar ); +extern void editor_show_contextmenu( I32 x, I32 y, LIST* items, const char* title = 0 ); +extern void editor_hide_contextmenu(); +extern U8 editor_contextmenu_open(); extern void editor_undo_clear( GAME_EDITOR* e ); extern void editor_undo_record_create_walls( GAME_EDITOR* e, I32 start_idx, I32 count ); extern void editor_undo_record_create_poly( GAME_EDITOR* e, I32 start_idx ); +extern void editor_undo_record_create_sprite( GAME_EDITOR* e, I32 start_idx ); +extern void editor_undo_record_create_entity( GAME_EDITOR* e, I32 start_idx ); extern U8 editor_undo( GAME_EDITOR* e ); extern U8 editor_redo( GAME_EDITOR* e ); @@ -233,8 +248,11 @@ struct GUI_EDITOR_2DVIEW : GUI_VIEW { U8 poly_drag; VEC2 poly_start; VEC2 poly_end; + VEC3 pending_object_pos; I32 pending_wall_undo_idx; + I32 pending_object_undo_idx; + U8 pending_object_undo_type; }; struct GUI_EDITOR_PROPVIEW : GUI_VIEW { @@ -247,6 +265,8 @@ struct GUI_EDITOR_PROPVIEW : GUI_VIEW { struct GUI_EDITOR_TOOLVIEW : GUI_VIEW { GUI_VIEW* itemview; U8 wallshape_dropdown_open; + U8 objecttype_dropdown_open; + U8 entclass_dropdown_open; I32 scroll; I32 content_h; }; @@ -283,12 +303,19 @@ struct GUI_EDITOR_VIEWTYPE_SEGMENT : GUI_BASE { I32 held_seg{-1}; }; +struct GUI_EDITOR_CONTEXTMENU : GUI_BASE { + LIST* items{}; + U8 initheld{1}; + U8 held{}; + I32 held_idx{-1}; + char title[64]{}; +}; + struct GUI_EDITOR_TOOLBAR : GUI_BASE { U8 held{}; I32 held_root{-1}; - I32 held_sub{-1}; - I32 open_root{-1}; - LIST entries{}; + I32 active_root{-1}; + LIST entries{}; }; extern GUI_EDITORWINDOW* gui_editorwindow( I32 w, I32 h ); 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; +} diff --git a/src/editor/editor_layout.cpp b/src/editor/editor_layout.cpp index f98ef00..de3ffb3 100644 --- a/src/editor/editor_layout.cpp +++ b/src/editor/editor_layout.cpp @@ -151,7 +151,7 @@ void editor_layout_start_menu( GAME_EDITOR* e ) { } static void editor_layout_tool_buttons( GUI_EDITORWINDOW* wnd, const EDITOR_LAYOUT& l ) { - const char* names[] = { "none", "select", "wall", "poly", "sprite", "ent" }; + const char* names[] = { "none", "select", "wall", "poly", "object" }; I32 y = l.tool_btn_y; for( U32 i = 0; i < sizeof( names ) / sizeof( names[0] ); ++i ) { GUI_BUTTON* btn = (GUI_BUTTON*)gui_find_node( wnd, names[i] ); diff --git a/src/editor/editor_menubar.cpp b/src/editor/editor_menubar.cpp index c15053f..b89d1c6 100644 --- a/src/editor/editor_menubar.cpp +++ b/src/editor/editor_menubar.cpp @@ -35,39 +35,8 @@ void editor_apply_view_mode( GAME_EDITOR* e ) { 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 ); + editor_hide_contextmenu(); } void editor_set_view_mode( I32 mode ) { @@ -97,13 +66,53 @@ void editor_raise_header_toolbar( GAME_EDITOR* e ) { parent->children.push( bar ); } -static void editor_menubar_set_entry( EDITOR_MENUBAR_ENTRY* entry, const char* text, U8 type ) { - if( !entry ) +static void editor_contextmenu_set_item( + EDITOR_CONTEXTMENU_ITEM* item, + const char* text, + EDITOR_CONTEXTMENU_CALLBACK cb = 0, + void* data = 0, + EDITOR_CONTEXTMENU_ENABLED_CALLBACK enabled_cb = 0 +) { + if( !item ) return; - entry->entries.clear(); - snprintf( entry->text, sizeof( entry->text ), "%s", text ); - entry->type = type; + item->items.clear(); + snprintf( item->text, sizeof( item->text ), "%s", text ? text : "" ); + item->cb = cb; + item->data = data; + item->enabled = 1; + item->enabled_cb = enabled_cb; +} + +static void editor_contextmenu_save_cb( void* ) { + if( editor ) + editor_save_map( editor ); +} + +static void editor_contextmenu_undo_cb( void* ) { + if( editor ) + editor_undo( editor ); +} + +static void editor_contextmenu_redo_cb( void* ) { + if( editor ) + editor_redo( editor ); +} + +static void editor_contextmenu_set_view_mode_cb( void* data ) { + editor_set_view_mode( (I32)(I64)data ); +} + +static void editor_contextmenu_set_tool_cb( void* data ) { + editor_settool( (U8)(I64)data ); +} + +static U8 editor_contextmenu_undo_enabled( const EDITOR_CONTEXTMENU_ITEM* ) { + return editor && editor->undo_actions.size > 0; +} + +static U8 editor_contextmenu_redo_enabled( const EDITOR_CONTEXTMENU_ITEM* ) { + return editor && editor->redo_actions.size > 0; } static void editor_toolbar_init_entries( GUI_EDITOR_TOOLBAR* bar ) { @@ -113,32 +122,31 @@ static void editor_toolbar_init_entries( GUI_EDITOR_TOOLBAR* bar ) { 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 ); + editor_contextmenu_set_item( &bar->entries[0], "file" ); + bar->entries[0].items.resize( 1 ); + editor_contextmenu_set_item( &bar->entries[0].items[0], "save", editor_contextmenu_save_cb ); + + editor_contextmenu_set_item( &bar->entries[1], "edit" ); + bar->entries[1].items.resize( 2 ); + editor_contextmenu_set_item( &bar->entries[1].items[0], "undo", editor_contextmenu_undo_cb, 0, editor_contextmenu_undo_enabled ); + editor_contextmenu_set_item( &bar->entries[1].items[1], "redo", editor_contextmenu_redo_cb, 0, editor_contextmenu_redo_enabled ); + + editor_contextmenu_set_item( &bar->entries[2], "view" ); + bar->entries[2].items.resize( 3 ); + editor_contextmenu_set_item( &bar->entries[2].items[0], "2d view", editor_contextmenu_set_view_mode_cb, (void*)(I64)EDITOR_VIEWMODE_2D ); + editor_contextmenu_set_item( &bar->entries[2].items[1], "3d view", editor_contextmenu_set_view_mode_cb, (void*)(I64)EDITOR_VIEWMODE_3D ); + editor_contextmenu_set_item( &bar->entries[2].items[2], "simulation", editor_contextmenu_set_view_mode_cb, (void*)(I64)EDITOR_VIEWMODE_SIM ); + + editor_contextmenu_set_item( &bar->entries[3], "tools" ); + bar->entries[3].items.resize( 5 ); + editor_contextmenu_set_item( &bar->entries[3].items[0], "none", editor_contextmenu_set_tool_cb, (void*)(I64)EDITOR_TOOL_NONE ); + editor_contextmenu_set_item( &bar->entries[3].items[1], "select", editor_contextmenu_set_tool_cb, (void*)(I64)EDITOR_TOOL_SELECT ); + editor_contextmenu_set_item( &bar->entries[3].items[2], "wall", editor_contextmenu_set_tool_cb, (void*)(I64)EDITOR_TOOL_WALL ); + editor_contextmenu_set_item( &bar->entries[3].items[3], "poly", editor_contextmenu_set_tool_cb, (void*)(I64)EDITOR_TOOL_POLY ); + editor_contextmenu_set_item( &bar->entries[3].items[4], "object", editor_contextmenu_set_tool_cb, (void*)(I64)EDITOR_TOOL_OBJECT ); } -static I32 editor_toolbar_root_width( EDITOR_MENUBAR_ENTRY* entry ) { +static I32 editor_toolbar_root_width( const EDITOR_CONTEXTMENU_ITEM* entry ) { if( !entry ) return 44; @@ -147,20 +155,6 @@ static I32 editor_toolbar_root_width( EDITOR_MENUBAR_ENTRY* entry ) { 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 ); @@ -168,35 +162,22 @@ static void editor_toolbar_root_rect( GUI_EDITOR_TOOLBAR* bar, I32 idx, I32* rx, 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 ); + if( rx ) *rx = cx; + if( ry ) *ry = y + 2; + if( rw ) *rw = editor_toolbar_root_width( &bar->entries[idx] ); + if( rh ) *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; -} +struct EDITOR_TOOLBAR_HIT { + I32 root{-1}; +}; 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{}; +static EDITOR_TOOLBAR_HIT editor_toolbar_hit_test( GUI_EDITOR_TOOLBAR* bar, I32 mx, I32 my ) { + EDITOR_TOOLBAR_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 ); @@ -206,76 +187,9 @@ static EDITOR_MENUBAR_HIT editor_toolbar_hit_test( GUI_EDITOR_TOOLBAR* bar, I32 } } - 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" ) ) editor_settool( EDITOR_TOOL_NONE ); - else if( !strcmp( entry->text, "select" ) ) editor_settool( EDITOR_TOOL_SELECT ); - else if( !strcmp( entry->text, "wall" ) ) editor_settool( EDITOR_TOOL_WALL ); - else if( !strcmp( entry->text, "poly" ) ) editor_settool( EDITOR_TOOL_POLY ); - else if( !strcmp( entry->text, "sprite" ) ) editor_settool( EDITOR_TOOL_SPRITE ); - else if( !strcmp( entry->text, "ent" ) ) editor_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 ); @@ -289,107 +203,61 @@ static void gui_editor_toolbar_draw_fn( void* ptr ) { } U8 m1 = gui_mbutton_down( GUI_MBTNLEFT ); - EDITOR_MENUBAR_HIT hit = editor_toolbar_hit_test( bar, mx, my ); + EDITOR_TOOLBAR_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; + U8 hovered = hit.root == i; + U8 active = bar->held && bar->held_root == i && m1; + U8 open = editor_contextmenu_open() && bar->active_root == i; CLR fill = ui_clr.bg_sec; - if( enabled && is_active ) + if( active ) fill = active_fill; - else if( is_hover ) + else if( open || hovered ) 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 ); + if( open || hovered || active ) + 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 ); } } static void gui_editor_toolbar_input_fn( void* ptr ) { GUI_EDITOR_TOOLBAR* bar = (GUI_EDITOR_TOOLBAR*)ptr; - I32 mx, my; + I32 mx = 0, my = 0; gui_cursor_pos( &mx, &my ); + U8 m1 = gui_mbutton_down( GUI_MBTNLEFT ); - EDITOR_MENUBAR_HIT hit = editor_toolbar_hit_test( bar, mx, my ); + EDITOR_TOOLBAR_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; - } + if( bar->held && bar->held_root == hit.root && hit.root >= 0 ) { + if( editor_contextmenu_open() && bar->active_root == hit.root ) { + editor_hide_contextmenu(); + } else { + I32 rx = 0; + editor_toolbar_root_rect( bar, hit.root, &rx, 0, 0, 0 ); + editor_show_contextmenu( rx, gui_rely( bar ) + bar->h, &bar->entries[hit.root].items ); + bar->active_root = hit.root; } } 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 ) { @@ -402,14 +270,13 @@ GUI_EDITOR_TOOLBAR* gui_editor_toolbar( I32 x, I32 y, I32 w, I32 h, const char* bar->w = w; bar->h = h; bar->xbound = w; - bar->ybound = h + 120; + bar->ybound = h; 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 ); + bar->active_root = -1; + snprintf( bar->name, sizeof( bar->name ), "%s", name ); editor_toolbar_init_entries( bar ); GUI_VIEW* parent = gui_get_view(); diff --git a/src/editor/editor_window.cpp b/src/editor/editor_window.cpp index bd70418..f81b1a8 100644 --- a/src/editor/editor_window.cpp +++ b/src/editor/editor_window.cpp @@ -6,9 +6,8 @@ void gui_editorwindow_draw_fn( void* ptr ) { GUI_EDITORWINDOW* wnd = (GUI_EDITORWINDOW*)ptr; editor_raise_header_toolbar( editor ); - GUI_BASE* toolbar = ( editor && editor->map ) ? editor->gui.header_toolbar : 0; - GUI_EDITOR_TOOLBAR* tbar = (GUI_EDITOR_TOOLBAR*)toolbar; - U8 menu_open = editor_toolbar_menu_open( tbar ); + GUI_BASE* contextmenu = ( editor && editor->map ) ? (GUI_BASE*)editor->gui.contextmenu : 0; + U8 menu_open = editor_contextmenu_open(); F32 saved_mx = input.mouse.pos.x; F32 saved_my = input.mouse.pos.y; @@ -28,11 +27,16 @@ void gui_editorwindow_draw_fn( void* ptr ) { wnd->children.each( fn( GUI_BASE** ptr ) { GUI_BASE* it = *ptr; + if( it == contextmenu ) + return; if( !it->enabled ) return; if( it->draw_fn ) it->draw_fn( it ); else dlog( "gui_editorwindow_draw_fn(): child %p has no draw_fn", it ); } ); + if( contextmenu && contextmenu->enabled && contextmenu->draw_fn ) + contextmenu->draw_fn( contextmenu ); + input.mouse.pos.x = saved_mx; input.mouse.pos.y = saved_my; editor_menu_hover_mask_active = 0; @@ -43,16 +47,20 @@ static void gui_editorwindow_input_fn( void* ptr ) { editor_raise_header_toolbar( editor ); GUI_BASE* toolbar = ( editor && editor->map ) ? editor->gui.header_toolbar : 0; + GUI_BASE* contextmenu = ( editor && editor->map ) ? (GUI_BASE*)editor->gui.contextmenu : 0; + U8 menu_was_open = editor_contextmenu_open(); if( toolbar && toolbar->enabled && toolbar->input_fn ) toolbar->input_fn( toolbar ); - GUI_EDITOR_TOOLBAR* tbar = (GUI_EDITOR_TOOLBAR*)toolbar; - if( editor_toolbar_menu_open( tbar ) ) + if( menu_was_open && editor && editor->gui.contextmenu && editor->gui.contextmenu->input_fn ) + editor->gui.contextmenu->input_fn( editor->gui.contextmenu ); + + if( menu_was_open || editor_contextmenu_open() ) return; wnd->children.each( fn( GUI_BASE** childptr ) { GUI_BASE* child = *childptr; - if( !child || child == toolbar || !child->enabled || !child->input_fn ) + if( !child || child == toolbar || child == contextmenu || !child->enabled || !child->input_fn ) return; child->input_fn( child ); } ); @@ -125,8 +133,7 @@ const char* editor_tool_name() { case EDITOR_TOOL_SELECT: return "select"; case EDITOR_TOOL_WALL: return "wall"; case EDITOR_TOOL_POLY: return "poly"; - case EDITOR_TOOL_SPRITE: return "sprite"; - case EDITOR_TOOL_ENT: return "ent"; + case EDITOR_TOOL_OBJECT: return "object"; default: return "none"; } } @@ -136,8 +143,7 @@ static U8 EDITOR_TOOL_BUTTON_TYPES[] = { EDITOR_TOOL_SELECT, EDITOR_TOOL_WALL, EDITOR_TOOL_POLY, - EDITOR_TOOL_SPRITE, - EDITOR_TOOL_ENT + EDITOR_TOOL_OBJECT }; static void settool_btn_cb( void* ptr ) { @@ -154,7 +160,7 @@ void editor_create_toolview_column( GAME_EDITOR* e ) { I32 x = 320; I32 y = EDITOR_LAYOUT_CONTENT_Y + EDITOR_LAYOUT_VIEW_DEFAULT_H + EDITOR_LAYOUT_VIEW_TOOL_GAP + EDITOR_LAYOUT_TOOL_BTN_TOP_GAP; I32 off = 23; - const char* labels[] = { "none", "select", "wall", "poly", "sprite", "ent" }; + const char* labels[] = { "none", "select", "wall", "poly", "object" }; for( U32 i = 0; i < sizeof( labels ) / sizeof( labels[0] ); ++i ) { GUI_BUTTON* btn = gui_button( x, y, 45, 20, labels[i], settool_btn_cb ); diff --git a/src/editor/properties.cpp b/src/editor/properties.cpp index fa3a117..ac21bb0 100644 --- a/src/editor/properties.cpp +++ b/src/editor/properties.cpp @@ -1,6 +1,7 @@ #include "editor.h" #include "../render/gl.h" #include "../game/assets.h" +#include "../game/object.h" const I32 PROPVIEW_TITLE_OFFSET = 15; const I32 PROPVIEW_PAD = 10; @@ -211,6 +212,7 @@ void gui_editor_propview_create_mapprops( GUI_EDITOR_PROPVIEW* view ) { y += space; } ); gui_label( x, y, "sprites: %d", m->sprites.size ); y += space; + gui_label( x, y, "entities: %d", m->entities.size ); y += space; gui_label( x, y, "loaded textures: %d", m->textures.size ); y += space; gui_vectorinput( x, y, propview_input_width( view ), "spawn position", (F32*)&m->startpos, 3, -INFINITY, INFINITY, step ); y += (space+18); GUI_FLOATINPUT* ang = gui_floatinput( x, y, propview_input_width( view ), "spawn angle", &m->startang, -180.f, 180.f, 1.f ); y += (space+18); @@ -318,7 +320,21 @@ void gui_editor_propview_create_surfprops( GUI_EDITOR_PROPVIEW* view ) { } void gui_editor_propview_create_entprops( GUI_EDITOR_PROPVIEW* view ) { + MAP_ENTITY* e = (MAP_ENTITY*)view->curselect; + WORLD_MAP* m = editor->map; + + I32 x = 10, y = 10; + I32 space = 20; + F32 step = editor->propgrid ? editor->grid : 0.25f; + + I32 ent_idx = m->entities.idx_where( fn( MAP_ENTITY* me ) { + return me == e; + } ); + gui_label( x, y, "idx: %d", ent_idx ); y += space; + gui_label( x, y, "class: %s", obj_classid_to_name( e->classid ) ); y += space; + gui_label( x, y, "class id: %u", e->classid ); y += space; + gui_vectorinput( x, y, propview_input_width( view ), "position", (F32*)&e->pos, 3, -INFINITY, INFINITY, step ); y += (space+18); } void gui_editor_propview_update( GUI_EDITOR_PROPVIEW* view ) { diff --git a/src/editor/toolview.cpp b/src/editor/toolview.cpp index 56e55f2..25c308d 100644 --- a/src/editor/toolview.cpp +++ b/src/editor/toolview.cpp @@ -1,5 +1,6 @@ #include "editor.h" #include "../util/string.h" +#include "../game/object.h" const U32 TOOLVIEW_TITLE_OFFSET = 15; const I32 TOOLVIEW_INNER_X = 10; @@ -7,6 +8,8 @@ const I32 TOOLVIEW_INNER_PAD = 20; const I32 TOOLVIEW_ROW_HEIGHT = 20; const I32 TOOLVIEW_ROW_GAP = 4; const I32 TOOLVIEW_WALL_SHAPE_LIST_HEIGHT = 54; +const I32 TOOLVIEW_OBJECT_TYPE_LIST_HEIGHT = 36; +const I32 TOOLVIEW_ENTITY_CLASS_LIST_HEIGHT = 54; const I32 TOOLVIEW_SCROLL_STEP = 20; const I32 TOOLVIEW_SCROLLBAR_W = 8; const I32 TOOLVIEW_SCROLLBAR_MIN_H = 18; @@ -116,6 +119,48 @@ void gui_editor_toolview_wallshape_select_cb( void* ptr ) { gui_editor_toolview_queue_refresh( view ); } +void gui_editor_toolview_objecttype_dropdown_toggle_cb( void* ptr ) { + GUI_BUTTON* btn = (GUI_BUTTON*)ptr; + GUI_EDITOR_TOOLVIEW* view = gui_editor_toolview_from_item_child( btn ); + if( !view ) + return; + + view->objecttype_dropdown_open = !view->objecttype_dropdown_open; + view->entclass_dropdown_open = 0; + gui_editor_toolview_queue_refresh( view ); +} + +void gui_editor_toolview_objecttype_select_cb( void* ptr ) { + GUI_LIST* list = (GUI_LIST*)ptr; + GUI_EDITOR_TOOLVIEW* view = gui_editor_toolview_from_item_child( list ); + if( !view ) + return; + + view->objecttype_dropdown_open = 0; + view->entclass_dropdown_open = 0; + gui_editor_toolview_queue_refresh( view ); +} + +void gui_editor_toolview_entclass_dropdown_toggle_cb( void* ptr ) { + GUI_BUTTON* btn = (GUI_BUTTON*)ptr; + GUI_EDITOR_TOOLVIEW* view = gui_editor_toolview_from_item_child( btn ); + if( !view ) + return; + + view->entclass_dropdown_open = !view->entclass_dropdown_open; + gui_editor_toolview_queue_refresh( view ); +} + +void gui_editor_toolview_entclass_select_cb( void* ptr ) { + GUI_LIST* list = (GUI_LIST*)ptr; + GUI_EDITOR_TOOLVIEW* view = gui_editor_toolview_from_item_child( list ); + if( !view ) + return; + + view->entclass_dropdown_open = 0; + gui_editor_toolview_queue_refresh( view ); +} + I32 gui_editor_toolview_create_wallshape_dropdown( GUI_EDITOR_TOOLVIEW* view, I32 y ) { static LIST shape_entries{}; if( !shape_entries.size ) { @@ -162,6 +207,102 @@ I32 gui_editor_toolview_create_wallshape_dropdown( GUI_EDITOR_TOOLVIEW* view, I3 return y; } +I32 gui_editor_toolview_create_objecttype_dropdown( GUI_EDITOR_TOOLVIEW* view, I32 y ) { + static LIST object_entries{}; + if( !object_entries.size ) { + GUI_LIST_ENTRY sprite{}; + sprite.val = EDITOR_OBJECT_SPRITE; + strcpy( sprite.title, "sprite" ); + object_entries.push( sprite ); + + GUI_LIST_ENTRY entity{}; + entity.val = EDITOR_OBJECT_ENTITY; + strcpy( entity.title, "entity" ); + object_entries.push( entity ); + } + + const char* object_name = editor->tool.objecttype == EDITOR_OBJECT_ENTITY ? "entity" : "sprite"; + char dropdown_title[64]; + sprintf( dropdown_title, "kind: %s v", object_name ); + + gui_button( + TOOLVIEW_INNER_X, + y, + view->w - TOOLVIEW_INNER_PAD, + TOOLVIEW_ROW_HEIGHT, + dropdown_title, + gui_editor_toolview_objecttype_dropdown_toggle_cb + ); + + y += TOOLVIEW_ROW_HEIGHT + TOOLVIEW_ROW_GAP; + if( !view->objecttype_dropdown_open ) + return y; + + GUI_LIST* object_type = gui_list( + TOOLVIEW_INNER_X, + y, + view->w - TOOLVIEW_INNER_PAD, + TOOLVIEW_OBJECT_TYPE_LIST_HEIGHT, + "object type", + &object_entries, + &editor->tool.objecttype + ); + + object_type->cb = gui_editor_toolview_objecttype_select_cb; + y += TOOLVIEW_OBJECT_TYPE_LIST_HEIGHT + TOOLVIEW_ROW_GAP; + return y; +} + +I32 gui_editor_toolview_create_entclass_dropdown( GUI_EDITOR_TOOLVIEW* view, I32 y ) { + static LIST entclass_entries{}; + if( !entclass_entries.size ) { + GUI_LIST_ENTRY trigger{}; + trigger.val = OBJCLASS_TRIGGER; + strcpy( trigger.title, "trigger" ); + entclass_entries.push( trigger ); + + GUI_LIST_ENTRY player{}; + player.val = OBJCLASS_PLAYER; + strcpy( player.title, "player" ); + entclass_entries.push( player ); + + GUI_LIST_ENTRY npc{}; + npc.val = OBJCLASS_BASENPC; + strcpy( npc.title, "base_npc" ); + entclass_entries.push( npc ); + } + + char dropdown_title[64]; + sprintf( dropdown_title, "class: %s v", obj_classid_to_name( editor->tool.entclass ) ); + + gui_button( + TOOLVIEW_INNER_X, + y, + view->w - TOOLVIEW_INNER_PAD, + TOOLVIEW_ROW_HEIGHT, + dropdown_title, + gui_editor_toolview_entclass_dropdown_toggle_cb + ); + + y += TOOLVIEW_ROW_HEIGHT + TOOLVIEW_ROW_GAP; + if( !view->entclass_dropdown_open ) + return y; + + GUI_LIST* entclass = gui_list( + TOOLVIEW_INNER_X, + y, + view->w - TOOLVIEW_INNER_PAD, + TOOLVIEW_ENTITY_CLASS_LIST_HEIGHT, + "entity class", + &entclass_entries, + (I32*)&editor->tool.entclass + ); + + entclass->cb = gui_editor_toolview_entclass_select_cb; + y += TOOLVIEW_ENTITY_CLASS_LIST_HEIGHT + TOOLVIEW_ROW_GAP; + return y; +} + I32 gui_editor_toolview_create_poly_sides_input( GUI_EDITOR_TOOLVIEW* view, I32 y ) { return gui_editor_toolview_create_float_input( view, @@ -204,11 +345,10 @@ I32 gui_editor_toolview_create_placement_height_input( GUI_EDITOR_TOOLVIEW* view void gui_editor_toolview_get_title( GUI_EDITOR_TOOLVIEW* view, char* out ) { switch( editor->tool.type ) { - case EDITOR_TOOL_ENT: memcpy( out, "tool: entity", 12 ); break; + case EDITOR_TOOL_OBJECT: memcpy( out, "tool: object", 12 ); break; case EDITOR_TOOL_WALL: memcpy( out, "tool: wall", 10 ); break; case EDITOR_TOOL_POLY: memcpy( out, "tool: polygon", 14 ); break; case EDITOR_TOOL_SELECT: memcpy( out, "tool: select", 12 ); break; - case EDITOR_TOOL_SPRITE: memcpy( out, "tool: sprite", 12 ); break; default: memcpy( out, "tool: none", 10 ); break; } } @@ -243,6 +383,27 @@ void gui_editor_toolview_update( GUI_EDITOR_TOOLVIEW* view ) { y = gui_editor_toolview_create_poly_sides_input( view, y ); y = gui_editor_toolview_create_placement_height_input( view, y ); } + else if( editor->tool.type == EDITOR_TOOL_OBJECT ) { + y = gui_editor_toolview_create_objecttype_dropdown( view, y ); + if( view->objecttype_dropdown_open ) { + view->content_h = y + 6; + gui_editor_toolview_clamp_scroll( view ); + gui_set_view( oldview ); + return; + } + + if( editor->tool.objecttype == EDITOR_OBJECT_ENTITY ) { + y = gui_editor_toolview_create_entclass_dropdown( view, y ); + if( view->entclass_dropdown_open ) { + view->content_h = y + 6; + gui_editor_toolview_clamp_scroll( view ); + gui_set_view( oldview ); + return; + } + } + + y = gui_editor_toolview_create_placement_height_input( view, y ); + } view->content_h = y + 6; gui_editor_toolview_clamp_scroll( view ); @@ -279,7 +440,7 @@ void gui_editor_toolview_draw_fn( void* ptr ) { I32 w = view->w; I32 h = view->h; - gui_draw_str( x, y, ALIGN_L, FNT_JPN12, ui_clr.txt, "contextual tool options" ); + gui_draw_str( x, y, ALIGN_L, FNT_JPN12, ui_clr.txt, "tool options" ); y += TOOLVIEW_TITLE_OFFSET; CLR col = gui_is_fg_window( view )? ui_clr.border : ui_clr.border_inactive; @@ -341,6 +502,8 @@ GUI_EDITOR_TOOLVIEW* gui_editor_toolview( I32 x, I32 y, I32 w, I32 h ) { view->draw_fn = gui_editor_toolview_draw_fn; view->input_fn = gui_editor_toolview_input_fn; view->wallshape_dropdown_open = 0; + view->objecttype_dropdown_open = 0; + view->entclass_dropdown_open = 0; view->scroll = 0; view->content_h = 0; diff --git a/src/editor/view2d.cpp b/src/editor/view2d.cpp index 8dbe42c..f1b5b17 100644 --- a/src/editor/view2d.cpp +++ b/src/editor/view2d.cpp @@ -1,6 +1,7 @@ #include #include "editor.h" #include "../render/gl_2d.h" +#include "../game/object.h" #include "../game/objlist.h" #include "../game/world/bsp.h" @@ -13,6 +14,7 @@ const I32 EDITOR_POLY_SIDES_RUNTIME_MAX = 256; VEC2 gui_editor_2dview_screen_to_world( GUI_EDITOR_2DVIEW* view, I32 x, I32 y ); VEC2 gui_editor_2dview_world_to_screen( GUI_EDITOR_2DVIEW* view, VEC2 world ); F32 gui_editor_2dview_placement_z(); +void gui_editor_2dview_draw_object_preview( GUI_EDITOR_2DVIEW* view ); VEC3 gui_editor_2dview_plane_to_world3( VEC2 plane, F32 depth ) { return { plane.x, plane.y, depth }; @@ -650,6 +652,25 @@ void gui_editor_2dview_draw_sprites( GUI_EDITOR_2DVIEW* view, I32 x, I32 y ) { } ); } +void gui_editor_2dview_draw_entities( GUI_EDITOR_2DVIEW* view, I32 x, I32 y ) { + WORLD_MAP* m = editor->map; + + m->entities.each( fn( MAP_ENTITY* e ) { + VEC2 pos = gui_editor_2dview_world3_to_screen( view, e->pos ); + I32 px = (I32)pos.x; + I32 py = (I32)pos.y; + U8 sel = gui_editor_2dview_is_gizmo_active( view, e, EDITOR_SELECT_ENT ); + + if( sel ) { + gui_draw_line( px - 8, py, px + 8, py, CLR::WHITE( 0.7f ) ); + gui_draw_line( px, py - 8, px, py + 8, CLR::WHITE( 0.7f ) ); + } + gui_draw_line( px - 6, py, px + 6, py, CLR::YELLOW() ); + gui_draw_line( px, py - 6, px, py + 6, CLR::YELLOW() ); + gui_draw_str( px + 8, py - 8, ALIGN_L, FNT_JPN12, ui_clr.txt, "%s", obj_classid_to_name( e->classid ) ); + } ); +} + void gui_editor_2dview_draw_player( GUI_EDITOR_2DVIEW* view, I32 x, I32 y ) { if( !objl->pl ) @@ -712,10 +733,12 @@ void gui_editor_2dview_draw_fn( void* ptr ) { gui_draw_push_clip( x + offx, y + offy, w - offx, h - offy - EDITORVIEW_TOOLBAR_OFFSET ); gui_editor_2dview_draw_polygons( view, x + offx, y + offy ); gui_editor_2dview_draw_walls( view, x + offx, y + offy ); + gui_editor_2dview_draw_entities( view, x + offx, y + offy ); gui_editor_2dview_draw_sprites( view, x + offx, y + offy ); gui_editor_2dview_draw_player( view, x + offx, y + offy ); gui_editor_2dview_draw_origin( view, x + offx, y + offy ); gui_editor_2dview_draw_poly_preview( view ); + gui_editor_2dview_draw_object_preview( view ); gui_draw_pop_clip(); gui_draw_push_clip( x, y + 16, view->w, view->h ); @@ -882,6 +905,42 @@ void gui_editor_2dview_input_select_drag_sprite( GUI_EDITOR_2DVIEW* view ) { } } +void gui_editor_2dview_draw_object_preview( GUI_EDITOR_2DVIEW* view ) { + if( editor->tool.type != EDITOR_TOOL_OBJECT || !view->held ) + return; + + VEC2 pos = gui_editor_2dview_world3_to_screen( view, view->pending_object_pos ); + I32 px = (I32)pos.x; + I32 py = (I32)pos.y; + + if( view->pending_object_undo_type == EDITOR_SELECT_ENT ) { + gui_draw_line( px - 6, py, px + 6, py, CLR::WHITE( 0.8f ) ); + gui_draw_line( px, py - 6, px, py + 6, CLR::WHITE( 0.8f ) ); + return; + } + + F32 wantedsize = editor->spritesize; + gui_draw_rect( + (I32)( pos.x - wantedsize * 0.5f ), + (I32)( pos.y - wantedsize * 0.5f ), + (I32)wantedsize, + (I32)wantedsize, + CLR::WHITE( 0.8f ) + ); +} + +void gui_editor_2dview_input_select_drag_entity( GUI_EDITOR_2DVIEW* view ) { + MAP_ENTITY* e = (MAP_ENTITY*)view->curdrag; + + gui_editor_2dview_snap_plane( &e->pos ); + + VEC2 mv = gui_editor_2dview_input_get_drag_vec( view ); + if( !is_zero( mv ) ) { + gui_editor_2dview_add_plane_delta( &e->pos, mv ); + gui_editor_2dview_input_select_onmove( view ); + } +} + void gui_editor_2dview_input_select_drag_origin( GUI_EDITOR_2DVIEW* view ) { WORLD_MAP* m = editor->map; @@ -928,6 +987,7 @@ void gui_editor_2dview_input_select_drag( GUI_EDITOR_2DVIEW* view ) { switch( view->dragtype ) { case EDITOR_SELECT_WALL: gui_editor_2dview_input_select_drag_wall( view ); break; case EDITOR_SELECT_POLY: gui_editor_2dview_input_select_drag_polygon( view ); break; + case EDITOR_SELECT_ENT: gui_editor_2dview_input_select_drag_entity( view ); break; case EDITOR_SELECT_SPRITE: gui_editor_2dview_input_select_drag_sprite( view ); break; case EDITOR_SELECT_ORIGIN: gui_editor_2dview_input_select_drag_origin( view ); break; case EDITOR_SELECT_WVERTEX: @@ -1055,6 +1115,30 @@ void gui_editor_2dview_input_select_sprites( GUI_EDITOR_2DVIEW* view ) { gui_editor_2dview_input_select_drag( view ); } +void gui_editor_2dview_input_select_entities( GUI_EDITOR_2DVIEW* view ) { + WORLD_MAP* m = editor->map; + + I32 mx, my; + gui_cursor_pos( &mx, &my ); + + VEC2 mpos = { (F32)mx, (F32)my }; + F32 mindist = editor->spritesize; + for( U32 i = 0; i < m->entities.size; ++i ) { + MAP_ENTITY* e = &m->entities[i]; + VEC2 screen = gui_editor_2dview_world3_to_screen( view, e->pos ); + F32 dist = vec_dist( mpos, screen ); + if( dist < mindist ) { + mindist = dist; + gui_editor_2dview_select( view, e, EDITOR_SELECT_ENT ); + } + } + + if( !view->curselect && !view->held ) + return; + + gui_editor_2dview_input_select_drag( view ); +} + void gui_editor_2dview_input_select_origin( GUI_EDITOR_2DVIEW* view ) { WORLD_MAP* m = editor->map; @@ -1090,6 +1174,8 @@ void gui_editor_2dview_input_tool_select( GUI_EDITOR_2DVIEW* view ) { view->seltype = EDITOR_SELECT_NONE; gui_editor_2dview_input_select_origin( view ); if( view->curselect ) return; + gui_editor_2dview_input_select_entities( view ); + if( view->curselect ) return; gui_editor_2dview_input_select_sprites( view ); if( view->curselect ) return; gui_editor_2dview_input_select_walls( view ); @@ -1230,13 +1316,35 @@ void gui_editor_2dview_input_tool_poly( GUI_EDITOR_2DVIEW* view ) { view->poly_end = world; } -void gui_editor_2dview_input_tool_ent( GUI_EDITOR_2DVIEW* view ) { - -} - -void gui_editor_2dview_input_tool_sprite( GUI_EDITOR_2DVIEW* view ) { +void gui_editor_2dview_input_tool_object( GUI_EDITOR_2DVIEW* view ) { U8 m1 = gui_mbutton_down( 0 ); if( !m1 ) { + if( view->held ) { + if( view->pending_object_undo_type == EDITOR_SELECT_ENT ) { + MAP_ENTITY ent{}; + ent.pos = view->pending_object_pos; + ent.classid = editor->tool.entclass; + + I32 idx = editor->map->entities.size; + editor->map->entities.push( ent ); + editor_undo_record_create_entity( editor, idx ); + } + else if( view->pending_object_undo_type == EDITOR_SELECT_SPRITE ) { + MAP_SPRITE news{}; + news.pos = view->pending_object_pos; + news.size = { 20.f, 20.f }; + news.clr = { 1.f, 1.f, 1.f, 1.f }; + news.tex = 0; + + I32 idx = editor->map->sprites.size; + editor->map->sprites.push( news ); + editor_undo_record_create_sprite( editor, idx ); + } + } + view->pending_object_undo_idx = -1; + view->pending_object_undo_type = EDITOR_SELECT_NONE; + view->curdrag = 0; + view->dragtype = EDITOR_SELECT_NONE; view->held = 0; return; } @@ -1246,17 +1354,10 @@ void gui_editor_2dview_input_tool_sprite( GUI_EDITOR_2DVIEW* view ) { VEC2 world = gui_editor_2dview_screen_to_world( view, mx, my ); if( !view->held ) { - VEC3 wpos = gui_editor_2dview_plane_to_world3( world, gui_editor_2dview_placement_z() ); - MAP_SPRITE news; - news.pos = wpos; - gui_editor_2dview_snap_plane( &news.pos ); - news.size = { 20.f, 20.f }; - news.clr = { 1.f, 1.f, 1.f, 1.f }; - news.tex = 0; - - I32 idx = editor->map->sprites.size; - editor->map->sprites.push( news ); - view->curdrag = &editor->map->sprites[idx]; + view->pending_object_pos = gui_editor_2dview_plane_to_world3( world, gui_editor_2dview_placement_z() ); + gui_editor_2dview_snap_plane( &view->pending_object_pos ); + view->pending_object_undo_idx = -1; + view->pending_object_undo_type = editor->tool.objecttype == EDITOR_OBJECT_ENTITY ? EDITOR_SELECT_ENT : EDITOR_SELECT_SPRITE; view->held = 1; view->oldmx = mx; @@ -1266,7 +1367,8 @@ void gui_editor_2dview_input_tool_sprite( GUI_EDITOR_2DVIEW* view ) { return; } - gui_editor_2dview_input_select_drag_sprite( view ); + view->pending_object_pos = gui_editor_2dview_plane_to_world3( world, gui_editor_2dview_placement_z() ); + gui_editor_2dview_snap_plane( &view->pending_object_pos ); } @@ -1281,8 +1383,7 @@ void gui_editor_2dview_input_tool_draw( GUI_EDITOR_2DVIEW* view ) { switch( editor->tool.type ) { case EDITOR_TOOL_WALL: return gui_editor_2dview_input_tool_wall( view ); case EDITOR_TOOL_POLY: return gui_editor_2dview_input_tool_poly( view ); - case EDITOR_TOOL_ENT: return gui_editor_2dview_input_tool_ent( view ); - case EDITOR_TOOL_SPRITE: return gui_editor_2dview_input_tool_sprite( view ); + case EDITOR_TOOL_OBJECT: return gui_editor_2dview_input_tool_object( view ); default: return; } } @@ -1321,6 +1422,11 @@ void gui_editor_view2d_delete_obj( GUI_EDITOR_2DVIEW* view ) { if( idx != -1 ) editor->map->sprites.erase( idx ); }; break; + case EDITOR_SELECT_ENT: { + I32 idx = editor->map->entities.idx_of( (MAP_ENTITY*)it ); + if( idx != -1 ) + editor->map->entities.erase( idx ); + }; break; case EDITOR_SELECT_PVERTEX: { I32 vidx = -1, idx = editor->map->polygons.idx_where( fn( MAP_POLYGON* p ) { vidx = p->vertices.idx_where( fn( MAP_VERTEX* v ) { @@ -1424,8 +1530,7 @@ void gui_editor_2dview_input_fn( void* ptr ) { case EDITOR_TOOL_SELECT: return gui_editor_2dview_input_tool_select( view ); case EDITOR_TOOL_WALL: case EDITOR_TOOL_POLY: - case EDITOR_TOOL_SPRITE: - case EDITOR_TOOL_ENT: + case EDITOR_TOOL_OBJECT: return gui_editor_2dview_input_tool_draw( view ); default: return gui_editor_2dview_input_tool_none( view ); } @@ -1508,6 +1613,8 @@ GUI_EDITOR_2DVIEW* gui_editor_2dview( I32 x, I32 y, I32 w, I32 h ) { view->poly_start = { 0.f, 0.f }; view->poly_end = { 0.f, 0.f }; view->pending_wall_undo_idx = -1; + view->pending_object_undo_idx = -1; + view->pending_object_undo_type = EDITOR_SELECT_NONE; GUI_BASE* parent = gui_get_view(); if( !parent ) diff --git a/src/game/world/map.cpp b/src/game/world/map.cpp index ba6bb00..a44f191 100644 --- a/src/game/world/map.cpp +++ b/src/game/world/map.cpp @@ -303,6 +303,40 @@ STAT map_sprites_from_section( WORLD_MAP* m, GAME_DATA* g, CFG_SECTION* sprites, return parsec > 0 ? STAT_OK : STAT_ERR; } +STAT map_entities_from_section( WORLD_MAP* m, GAME_DATA*, CFG_SECTION* entities, U32 entityc ) { + if( !entityc ) { + dlog( "map_entities_from_section() : no entities in %s\n", m->name ); + return STAT_OK; + } + + U32 parsec = 0; + char entitysec[256] = { 0 }; + for( U32 i = 0; i < entityc; ++i ) { + MAP_ENTITY entity{}; + + sprintf( entitysec, "%d", i ); + CFG_SECTION* s = cfg_section( entities, entitysec ); + if( !s ) { + dlog( "map_entities_from_section() : missing entity %d in %s\n", i, m->name ); + continue; + } + + CFG_INT* classid = cfg_int( s, "classid" ); + CFG_VEC3* pos = cfg_vec3( s, "pos" ); + if( !classid || !pos ) { + dlog( "map_entities_from_section() : invalid entity %d in %s\n", i, m->name ); + continue; + } + + entity.classid = (U32)classid->value; + entity.pos = pos->value; + m->entities.push( entity ); + ++parsec; + } + + return parsec > 0 ? STAT_OK : STAT_ERR; +} + STAT map_skybox_from_section( CFG_SECTION* s, GAME_DATA* g, WORLD_MAP* m ) { CFG_SECTION* props = cfg_section( s, "props" ); if( !props ) @@ -496,6 +530,13 @@ WORLD_MAP* map_from_file( GAME_DATA* game, const char* path ) { if( !OK( map_sprites_from_section( m, game, sprites, spritec->value ) ) ) { delete m; return 0; } } + CFG_INT* entityc = cfg_int( s, "entitycount" ); + if( entityc ) { + CFG_SECTION* entities = cfg_section( s, "entities" ); + if( !entities ) { dlog( errstr, path, "entities" ); delete m; return 0; } + if( !OK( map_entities_from_section( m, game, entities, entityc->value ) ) ) { delete m; return 0; } + } + CFG_VEC3* startpos = cfg_vec3( s, "startpos" ); if( !startpos ) { dlog( errstr, path, "startpos" ); @@ -537,6 +578,7 @@ void map_serialize_info( CFG_SECTION* s, WORLD_MAP* map ) { cfg_int( "polycount", s, map->polygons.size ); cfg_int( "propcount", s, map->props.size ); cfg_int( "spritecount", s, map->sprites.size ); + cfg_int( "entitycount", s, map->entities.size ); cfg_vec3( "startpos", s, map->startpos ); cfg_float( "startang", s, map->startang ); } @@ -597,6 +639,19 @@ void map_serialize_sprites( CFG_SECTION* s, WORLD_MAP* map ) { } ); } +void map_serialize_entities( CFG_SECTION* s, WORLD_MAP* map ) { + CFG_SECTION* entities = cfg_section_new( "entities", s ); + char name[64]; + + U32 i = 0; + map->entities.each( fn( MAP_ENTITY* e ) { + sprintf( name, "%d", i++ ); + CFG_SECTION* entitysec = cfg_section_new( name, entities ); + cfg_int( "classid", entitysec, e->classid ); + cfg_vec3( "pos", entitysec, e->pos ); + } ); +} + void map_serialize_props( CFG_SECTION* s, WORLD_MAP* map ) { CFG_SECTION* props = cfg_section_new( "props", s ); char name[64]; @@ -623,6 +678,7 @@ CFG_SECTION* map_serialize( WORLD_MAP* map ) { map_serialize_walls( s, map ); map_serialize_polygons( s, map ); map_serialize_sprites( s, map ); + map_serialize_entities( s, map ); return s; } diff --git a/src/game/world/map.h b/src/game/world/map.h index 29022db..f3f56f8 100644 --- a/src/game/world/map.h +++ b/src/game/world/map.h @@ -68,6 +68,7 @@ struct MAP_SPRITE { }; struct MAP_ENTITY { + VEC3 pos; U32 classid; LIST props; }; @@ -82,6 +83,7 @@ struct WORLD_MAP { LIST walls; LIST polygons; LIST sprites; + LIST entities; LIST props; MAP_SKYBOX skybox; F32 w; diff --git a/src/gui/list.cpp b/src/gui/list.cpp index 2feff47..0a2a3d3 100644 --- a/src/gui/list.cpp +++ b/src/gui/list.cpp @@ -66,10 +66,9 @@ void gui_list_input_fn( void* ptr ) { if( m1 ) { if( !list->held ) { GUI_LIST_ENTRY* e = &list->plist->data[idx]; - I32 prevval = *list->pval; *list->pval = e->val; - if( list->cb && prevval != *list->pval ) + if( list->cb ) list->cb( list ); } list->held = 1; -- cgit v1.2.3