From 372f23024163e0fab57a847ddda41f06857efd7e Mon Sep 17 00:00:00 2001 From: aura Date: Mon, 27 Jul 2026 11:55:57 +0200 Subject: undo/redo props - WIP --- src/editor/editor.cpp | 161 ++++++++++++++++++++++++++++++----------- src/editor/editor.h | 65 ++++++++++++++--- src/editor/properties.cpp | 31 +++++++- src/editor/properties.h | 35 +++++++-- src/editor/view2d.cpp | 180 +++++++++++++++++++++++++++++++++++----------- 5 files changed, 372 insertions(+), 100 deletions(-) (limited to 'src/editor') diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index c96f908..72ca4d2 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -2,6 +2,7 @@ #include "../game.h" #include "../game/object.h" +#include "properties.h" GAME_EDITOR* editor = 0; @@ -31,7 +32,7 @@ void editor_clear_gui_state_refs( GAME_EDITOR* e ) { e->gui.header_viewtype = 0; } -void editor_push_undo_action( GAME_EDITOR* e, const GAME_EDITOR::EDITOR_UNDO_ACTION& action ) { +void editor_push_undo_action( GAME_EDITOR* e, const EDITOR_UNDO_ACTION& action ) { if( !e ) return; @@ -265,11 +266,11 @@ void editor_undo_record_create_walls( GAME_EDITOR* e, I32 start_idx, I32 count ) if( count <= 0 ) return; - GAME_EDITOR::EDITOR_UNDO_ACTION action{}; + EDITOR_UNDO_ACTION action{}; action.type = EDITOR_UNDO_CREATE_WALLS; - action.start_idx = start_idx; + action.create.start_idx = start_idx; for( I32 i = 0; i < count; ++i ) - action.walls.push( map->walls[start_idx + i] ); + action.create.walls.push( map->walls[start_idx + i] ); editor_push_undo_action( e, action ); } @@ -282,10 +283,11 @@ void editor_undo_record_create_poly( GAME_EDITOR* e, I32 start_idx ) { if( start_idx < 0 || start_idx >= (I32)map->polygons.size ) return; - GAME_EDITOR::EDITOR_UNDO_ACTION action{}; - action.type = EDITOR_UNDO_CREATE_POLY; - action.start_idx = start_idx; - action.polys.push( map->polygons[start_idx] ); + EDITOR_UNDO_ACTION action{ + .type = EDITOR_UNDO_CREATE_POLY, + .create = { .start_idx = start_idx } + }; + action.create.polys.push( map->polygons[start_idx] ); editor_push_undo_action( e, action ); } @@ -298,10 +300,12 @@ void editor_undo_record_create_sprite( GAME_EDITOR* e, I32 start_idx ) { 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_UNDO_ACTION action = { + .type = EDITOR_UNDO_CREATE_SPRITES, + .create = { .start_idx = start_idx } + }; + + action.create.sprites.push( map->sprites[start_idx] ); editor_push_undo_action( e, action ); } @@ -314,18 +318,36 @@ void editor_undo_record_create_entity( GAME_EDITOR* e, I32 start_idx ) { 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_UNDO_ACTION action{ + .type = EDITOR_UNDO_CREATE_ENTITIES, + .create = { .start_idx = start_idx } + }; + action.create.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 ) { +// NOTE: +// this is a util func. +// individual components create records manually +void editor_undo_record_change_prop( GAME_EDITOR* e, FNV1A hash, void* oldv, void* newv, U32 size ) { if( !e || !e->map ) - return 0; + return; + + EDITOR_UNDO_ACTION rec{ .type = EDITOR_UNDO_EDITPROPS }; + for( auto e : e->gui.props->curselect ) { + EDITOR_UNDO_PROPS::CHANGES changes{}; + + changes.props.push( { hash, size, oldv, newv } ); + changes.obj = (EOBJECT*)e.obj; + rec.props.changes.push( changes ); + } + + e->undo_actions.push( rec ); +} + +U8 editor_apply_undo_action_reverse_create( GAME_EDITOR* e, EDITOR_UNDO_ACTION& action ) { WORLD_MAP* map = e->map; U8 clear_props_select = 0; U8 clear_view_select = 0; @@ -333,13 +355,13 @@ static U8 editor_apply_undo_action_reverse( GAME_EDITOR* e, GAME_EDITOR::EDITOR_ switch( action.type ) { case EDITOR_UNDO_CREATE_WALLS: { - I32 count = (I32)action.walls.size; + I32 count = (I32)action.create.walls.size; for( I32 i = count - 1; i >= 0; --i ) { if( !map->walls.size ) break; - I32 expected = action.start_idx + i; - I32 idx = editor_find_wall_idx( map, action.walls[i], expected ); + I32 expected = action.create.start_idx + i; + I32 idx = editor_find_wall_idx( map, action.create.walls[i], expected ); if( idx < 0 ) idx = editor_clamp_valid_idx( expected, (I32)map->walls.size ); @@ -350,15 +372,15 @@ static U8 editor_apply_undo_action_reverse( GAME_EDITOR* e, GAME_EDITOR::EDITOR_ } } break; case EDITOR_UNDO_CREATE_POLY: { - I32 count = (I32)action.polys.size; + I32 count = (I32)action.create.polys.size; if( count <= 0 ) return 0; for( I32 i = count - 1; i >= 0; --i ) { if( !map->polygons.size ) break; - I32 expected = action.start_idx + i; - I32 idx = i < (I32)action.polys.size ? editor_find_poly_idx( map, action.polys[i], expected ) : -1; + I32 expected = action.create.start_idx + i; + I32 idx = i < (I32)action.create.polys.size ? editor_find_poly_idx( map, action.create.polys[i], expected ) : -1; if( idx < 0 ) idx = editor_clamp_valid_idx( expected, (I32)map->polygons.size ); @@ -369,13 +391,13 @@ static U8 editor_apply_undo_action_reverse( GAME_EDITOR* e, GAME_EDITOR::EDITOR_ } } break; case EDITOR_UNDO_CREATE_SPRITES: { - I32 count = (I32)action.sprites.size; + I32 count = (I32)action.create.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 ); + I32 expected = action.create.start_idx + i; + I32 idx = editor_find_sprite_idx( map, action.create.sprites[i], expected ); if( idx < 0 ) idx = editor_clamp_valid_idx( expected, (I32)map->sprites.size ); @@ -385,13 +407,13 @@ static U8 editor_apply_undo_action_reverse( GAME_EDITOR* e, GAME_EDITOR::EDITOR_ } } break; case EDITOR_UNDO_CREATE_ENTITIES: { - I32 count = (I32)action.entities.size; + I32 count = (I32)action.create.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 ); + I32 expected = action.create.start_idx + i; + I32 idx = editor_find_entity_idx( map, action.create.entities[i], expected ); if( idx < 0 ) idx = editor_clamp_valid_idx( expected, (I32)map->entities.size ); @@ -422,41 +444,88 @@ static U8 editor_apply_undo_action_reverse( GAME_EDITOR* e, GAME_EDITOR::EDITOR_ return 1; } -static U8 editor_apply_undo_action_forward( GAME_EDITOR* e, GAME_EDITOR::EDITOR_UNDO_ACTION& action ) { +U8 editor_apply_undo_action_reverse_props( GAME_EDITOR* e, EDITOR_UNDO_ACTION& action ) { + EDITOR_UNDO_PROPS* c = &action.props; + + for( auto it : c->changes ) { + EOBJECT* obj = it.obj; + for( auto it2 : it.props ) { + EDITOR_PROP* p = eprop_from_hash( obj, it2.hash ); + if( !p ) { + dlog( "editor_apply_undo_action_reverse_props() : warn: prop not found %x\n", it2.hash ); + return 0; + } + + void* data = eprop_ptr( p ); + memcpy( data, it2.oldval.data, p->size ); + } + } + + return 1; +} + +U8 editor_apply_undo_action_reverse( GAME_EDITOR* e, EDITOR_UNDO_ACTION& action ) { if( !e || !e->map ) return 0; + if( action.type == EDITOR_UNDO_EDITPROPS ) + return editor_apply_undo_action_reverse_props( e, action ); + + return editor_apply_undo_action_reverse_create( e, action ); +} + +U8 editor_apply_undo_action_forward_props( GAME_EDITOR* e, EDITOR_UNDO_ACTION& action ) { + EDITOR_UNDO_PROPS* c = &action.props; + + for( auto it : c->changes ) { + EOBJECT* obj = it.obj; + for( auto it2 : it.props ) { + EDITOR_PROP* p = eprop_from_hash( obj, it2.hash ); + if( !p ) { + dlog( "editor_apply_undo_action_forward_props() : warn: prop not found %x\n", it2.hash ); + return 0; + } + + void* data = eprop_ptr( p ); + memcpy( data, it2.newval.data, p->size ); + } + } + + return 1; +} + +U8 editor_apply_undo_action_forward_create( GAME_EDITOR* e, EDITOR_UNDO_ACTION& action ) { WORLD_MAP* map = e->map; switch( action.type ) { case EDITOR_UNDO_CREATE_WALLS: { - if( !action.walls.size ) + if( !action.create.walls.size ) return 0; - action.walls.each( fn( MAP_WALL* w ) { + action.create.walls.each( fn( MAP_WALL* w ) { map->walls.push( *w ); } ); } break; case EDITOR_UNDO_CREATE_POLY: { - if( !action.polys.size ) + if( !action.create.polys.size ) return 0; - action.polys.each( fn( MAP_POLYGON* p ) { + action.create.polys.each( fn( MAP_POLYGON* p ) { map->polygons.push( *p ); } ); } break; case EDITOR_UNDO_CREATE_SPRITES: { - if( !action.sprites.size ) + if( !action.create.sprites.size ) return 0; - action.sprites.each( fn( MAP_SPRITE* s ) { + action.create.sprites.each( fn( MAP_SPRITE* s ) { map->sprites.push( *s ); } ); } break; case EDITOR_UNDO_CREATE_ENTITIES: { - if( !action.entities.size ) + if( !action.create.entities.size ) return 0; - action.entities.each( fn( MAP_ENTITY* ent ) { + action.create.entities.each( fn( MAP_ENTITY* ent ) { map->entities.push( *ent ); } ); } break; @@ -469,11 +538,21 @@ static U8 editor_apply_undo_action_forward( GAME_EDITOR* e, GAME_EDITOR::EDITOR_ return 1; } +U8 editor_apply_undo_action_forward( GAME_EDITOR* e, EDITOR_UNDO_ACTION& action ) { + if( !e || !e->map ) + return 0; + + if( action.type == EDITOR_UNDO_EDITPROPS ) + return editor_apply_undo_action_forward_props( e, action ); + + return editor_apply_undo_action_forward_create( e, action ); +} + U8 editor_undo( GAME_EDITOR* e ) { if( !e || !e->map || !e->undo_actions.size ) return 0; - GAME_EDITOR::EDITOR_UNDO_ACTION action = e->undo_actions.pop(); + EDITOR_UNDO_ACTION action = e->undo_actions.pop(); if( !editor_apply_undo_action_reverse( e, action ) ) { e->undo_actions.push( action ); return 0; @@ -487,7 +566,7 @@ U8 editor_redo( GAME_EDITOR* e ) { if( !e || !e->map || !e->redo_actions.size ) return 0; - GAME_EDITOR::EDITOR_UNDO_ACTION action = e->redo_actions.pop(); + EDITOR_UNDO_ACTION action = e->redo_actions.pop(); if( !editor_apply_undo_action_forward( e, action ) ) { e->redo_actions.push( action ); return 0; diff --git a/src/editor/editor.h b/src/editor/editor.h index 4050029..b506d9c 100644 --- a/src/editor/editor.h +++ b/src/editor/editor.h @@ -34,12 +34,13 @@ enum EditorSelectType_t { EDITOR_SELECT_SURFPROPS }; -enum EditorUndoType_t { +enum EditorActionType_t { EDITOR_UNDO_NONE = 0, EDITOR_UNDO_CREATE_WALLS = 1, EDITOR_UNDO_CREATE_POLY = 2, EDITOR_UNDO_CREATE_SPRITES = 3, - EDITOR_UNDO_CREATE_ENTITIES = 4 + EDITOR_UNDO_CREATE_ENTITIES = 4, + EDITOR_UNDO_EDITPROPS = 5 }; enum EditorInfoBoxType_t { @@ -96,6 +97,55 @@ struct GAME_EDITOR_TOOL { I32 objecttype; }; +struct EDITOR_UNDO_PROPS { + struct PROP_VALUE { + LIST oldval{}; + LIST newval{}; + FNV1A hash; + U32 size; + + PROP_VALUE() = default; + PROP_VALUE( FNV1A _hash, U32 _size ) { + this->hash = _hash; + this->size = _size; + + this->oldval.resize( _size ); + this->newval.resize( _size ); + } + PROP_VALUE( FNV1A _hash, U32 _size, void* oldv, void* newv ) { + this->hash = _hash; + this->size = _size; + + this->oldval.resize( _size ); + this->newval.resize( _size ); + memcpy( this->oldval.data, oldv, _size ); + memcpy( this->newval.data, newv, _size ); + } + }; + + struct CHANGES { + EOBJECT* obj; + LIST props; + }; + LIST changes; +}; + +struct EDITOR_UNDO_CREATE { + U8 objtype{}; + I32 start_idx{}; + + LIST walls{}; + LIST polys{}; + LIST sprites{}; + LIST entities{}; +}; + +struct EDITOR_UNDO_ACTION { + U8 type; + EDITOR_UNDO_PROPS props; + EDITOR_UNDO_CREATE create; +}; + struct GAME_EDITOR { GAME_DATA* game; @@ -150,14 +200,6 @@ struct GAME_EDITOR { LIST map_list{}; - struct EDITOR_UNDO_ACTION { - U8 type{}; - I32 start_idx{}; - LIST walls{}; - LIST polys{}; - LIST sprites{}; - LIST entities{}; - }; LIST undo_actions{}; LIST redo_actions{}; }; @@ -214,6 +256,7 @@ extern void editor_undo_record_create_walls( GAME_EDITOR* e, I32 start_idx, I32 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 void editor_undo_record_change_prop( GAME_EDITOR* e, FNV1A hash, void* oldv, void* newv, U32 size ); extern U8 editor_undo( GAME_EDITOR* e ); extern U8 editor_redo( GAME_EDITOR* e ); @@ -261,6 +304,8 @@ struct GUI_EDITOR_2DVIEW : GUI_VIEW { I32 pending_wall_undo_idx; I32 pending_object_undo_idx; U8 pending_object_undo_type; + + EDITOR_UNDO_PROPS pending_undo_props{}; }; struct GUI_EDITOR_PROPVIEW : GUI_VIEW { diff --git a/src/editor/properties.cpp b/src/editor/properties.cpp index a9835e4..109e622 100644 --- a/src/editor/properties.cpp +++ b/src/editor/properties.cpp @@ -111,6 +111,8 @@ U8 gui_editor_propview_is_selected( GUI_EDITOR_PROPVIEW *e, void *what, U8 selty void gui_editor_propview_sync_props_value( GUI_EDITOR_PROPVIEW* view, EDITOR_PROP* prop ) { void* valuep = eprop_ptr( prop ); + + EDITOR_UNDO_ACTION rec{ .type = EDITOR_UNDO_EDITPROPS }; for( auto& it : view->curselect ) { EOBJECT* eobj = (EOBJECT*)it.obj; EDITOR_PROP* otherp = eprop_from_name( eobj, prop->displayname ); @@ -118,10 +120,22 @@ void gui_editor_propview_sync_props_value( GUI_EDITOR_PROPVIEW* view, EDITOR_PRO if( otherp && otherp->type == prop->type ) { void* targetp = eprop_ptr( otherp ); - if( targetp != valuep ) + if( targetp != valuep ) { + EDITOR_UNDO_PROPS::CHANGES changes; + + void* oldval = alloca( prop->size ); + memcpy( oldval, targetp, prop->size ); memcpy( targetp, valuep, prop->size ); + + changes.props.push( { prop->hash, prop->size, oldval, targetp } ); + changes.obj = eobj; + rec.props.changes.push( changes ); + } } } + + if( rec.props.changes.size ) + editor->undo_actions.push( rec ); } void gui_editor_propview_sync_props_value( GUI_EDITOR_PROPVIEW* view, const char* propname ) { @@ -134,6 +148,10 @@ void gui_editor_propview_sync_props_value( GUI_EDITOR_PROPVIEW* view, const char } void gui_editor_propview_sync_props_fdiff( GUI_EDITOR_PROPVIEW* view, EDITOR_PROP* prop, F32* fdiff, U32 valc ) { + if( !fdiff ) + return; + + EDITOR_UNDO_ACTION rec{ .type = EDITOR_UNDO_EDITPROPS }; for( auto& it : view->curselect ) { EOBJECT* eobj = (EOBJECT*)it.obj; EDITOR_PROP* otherp = eprop_from_name( eobj, prop->displayname ); @@ -144,10 +162,21 @@ void gui_editor_propview_sync_props_fdiff( GUI_EDITOR_PROPVIEW* view, EDITOR_PRO if( targetp == eprop_ptr( prop ) ) continue; + EDITOR_UNDO_PROPS::CHANGES changes; + F32* oldval = (F32*)alloca( prop->size ); + memcpy( oldval, targetp, prop->size ); + for( U32 i = 0; i < valc; ++i ) targetp[i] += fdiff[i]; + + changes.props.push( { prop->hash, prop->size, oldval, targetp } ); + changes.obj = eobj; + rec.props.changes.push( changes ); } } + + if( rec.props.changes.size ) + editor->undo_actions.push( rec ); } void gui_editor_propview_sync_props_fdiff( GUI_EDITOR_PROPVIEW* view, const char* propname, F32* fdiff, U32 valc ) { diff --git a/src/editor/properties.h b/src/editor/properties.h index 810c075..d4fc3bb 100644 --- a/src/editor/properties.h +++ b/src/editor/properties.h @@ -6,6 +6,7 @@ #include #include "../util/string.h" #include "../util/fnv.h" +#include "../util/profiler.h" #define EPROP( _type, name, value, display ) \ _type name = value; \ @@ -90,6 +91,7 @@ struct EOBJECT { LIST eprops{}; }; + template concept __eobject_base = __is_base_of(EOBJECT, T); @@ -112,6 +114,7 @@ struct EDITOR_PROP { _parent->eprops.push( { offset } ); + parentoff = pdata - parent; offset = _this - pdata; dataoff = offset; } @@ -133,6 +136,7 @@ struct EDITOR_PROP { _parent->eprops.push( { offset } ); + parentoff = pdata - parent; offset = _this - pdata; dataoff = offset; } @@ -154,6 +158,7 @@ struct EDITOR_PROP { _parent->eprops.push( { offset } ); + parentoff = pdata - parent; offset = _this - pdata; dataoff = offset; } @@ -175,11 +180,13 @@ struct EDITOR_PROP { _parent->eprops.push( { offset } ); + parentoff = pdata - parent; offset = _this - pdata; dataoff = offset; } U64 dataoff; + U64 parentoff; U8 type; STR displayname; F64 min{ -INFINITY }; @@ -220,22 +227,40 @@ inline EDITOR_PROP* eprop_from_ref( EOBJECT* obj, EPROP_ENTRY e ) { return (EDITOR_PROP*)( (U64)obj + e.offset ); } +inline EDITOR_PROP* eprop_from_ptr( EOBJECT* obj, void* ptr ) { + if( (U64)ptr < (U64)obj ) { + trace( "eprop_from_ptr() : error : invalid pointer access, obj = %llx, ptr = %llx\n", (U64)obj, (U64)ptr ); + return 0; + } + + U64 off = ( (U64)ptr - (U64)obj ); + for( auto& it : obj->eprops ) { + EDITOR_PROP* p = eprop_from_ref( obj, it ); + if( off == p->parentoff ) + return p; + } + + return 0; +} + inline void* eprop_ptr( EDITOR_PROP* prop ) { return (void*)( (U64)prop - prop->dataoff); } -inline EDITOR_PROP* eprop_from_name( EOBJECT* obj, STR name ) { - FNV1A fnv = fnv1a( name ); - +inline EDITOR_PROP* eprop_from_hash( EOBJECT* obj, FNV1A hash ) { for( auto& it : obj->eprops ) { EDITOR_PROP* prop = eprop_from_ref( obj, it ); - if( fnv == prop->hash ) + if( hash == prop->hash ) return prop; } - return 0; } +inline EDITOR_PROP* eprop_from_name( EOBJECT* obj, STR name ) { + FNV1A fnv = fnv1a( name ); + return eprop_from_hash( obj, fnv ); +} + template const U8 eprop_type() { return EDITOR_PROP::__eprop_type::type; diff --git a/src/editor/view2d.cpp b/src/editor/view2d.cpp index c4738dd..cca83d1 100644 --- a/src/editor/view2d.cpp +++ b/src/editor/view2d.cpp @@ -4,7 +4,6 @@ #include "../render/gl_2d.h" #include "../game/object.h" #include "../game/objlist.h" -#include "../game/world/bsp.h" const I32 EDITORVIEW_TITLE_OFFSET = 15; const I32 EDITORVIEW_GUTTERS_OFFSETX = 22; @@ -839,36 +838,9 @@ VEC2 gui_editor_2dview_input_get_drag_vec( GUI_EDITOR_2DVIEW* view ) { } U8 gui_editor_2dview_input_select_drag_obj( GUI_EDITOR_2DVIEW* view, void* obj, U8 seltype ); -U8 gui_editor_2dview_move_group( GUI_EDITOR_2DVIEW* view ) { - U8 update = 0; - if( view->dragtype == EDITOR_SELECT_WVERTEX || view->dragtype == EDITOR_SELECT_PVERTEX ) - return update; - - GUI_EDITOR_PROPVIEW* props = editor->gui.props; - // update all selected objects - for( auto& it : props->curselect ) { - if( it.obj == view->curdrag ) { - update = 1; - continue; - } +U8 gui_editor_2dview_drag_group( GUI_EDITOR_2DVIEW* view ); - // special case where a wall is selected but we are dragging one of its vertices - if( props->curselect.size == 1 && it.seltype == EDITOR_SELECT_WALL ) { - MAP_WALL* w = (MAP_WALL*)it.obj; - if( &w->start == view->curdrag || &w->end == view->curdrag ) { - update = 1; - continue; - } - } - - if( gui_editor_2dview_input_select_drag_obj( view, it.obj, it.seltype ) ) - update = 1; - } - - return update; -} - -void gui_editor_2dview_input_select_onmove( GUI_EDITOR_2DVIEW* view ) { +void gui_editor_2dview_input_select_onmove( GUI_EDITOR_2DVIEW* view, EDITOR_UNDO_PROPS* undo ) { I32 mx, my; gui_cursor_pos( &mx, &my ); @@ -883,7 +855,7 @@ void gui_editor_2dview_input_select_onmove( GUI_EDITOR_2DVIEW* view ) { GUI_EDITOR_PROPVIEW* props = editor->gui.props; U8 update = !is_zero( gui_editor_2dview_input_get_drag_vec( view ) ); if( update && gui_editor_propview_is_selected( props, view->curdrag, view->dragtype ) ) { - if( gui_editor_2dview_move_group( view ) ) + if( gui_editor_2dview_drag_group( view ) ) gui_editor_propview_update( props ); } @@ -894,45 +866,109 @@ void gui_editor_2dview_input_select_onmove( GUI_EDITOR_2DVIEW* view ) { view->dragmoved = 1; } -void gui_editor_2dview_input_select_drag_wall( GUI_EDITOR_2DVIEW* view, MAP_WALL* s ) { +void gui_editor_2dview_set_undo_prop( GUI_EDITOR_2DVIEW* view, EOBJECT* target, FNV1A hash, U32 size, void* oldv, void* newv = 0 ) { + if( !target ) + return; + + EDITOR_UNDO_PROPS::CHANGES* target_change = view->pending_undo_props.changes.where( + [&]( EDITOR_UNDO_PROPS::CHANGES* c ) { return c->obj == target; } + ); + + if( !target_change ) { + target_change = view->pending_undo_props.changes.push( { .obj = target } ); + } + + EDITOR_UNDO_PROPS::PROP_VALUE* target_prop = target_change->props.where( + [&]( EDITOR_UNDO_PROPS::PROP_VALUE* p ) { return p->hash == hash; } + ); + + if( !target_prop ) { + target_prop = target_change->props.push( { hash, size } ); + memcpy( target_prop->oldval.data, oldv, size ); + } + + if( target_prop->size != size ) { + return dlog( "gui_editor_2dview_set_undo_prop() : prop size mismatch" ); + } + + if( newv ) + memcpy( target_prop->newval.data, newv, size ); +} + +void gui_editor_2dview_input_select_drag_wall( + GUI_EDITOR_2DVIEW* view, + MAP_WALL* s +) { gui_editor_2dview_snap_plane( &s->start ); gui_editor_2dview_snap_plane( &s->end ); VEC2 mv = gui_editor_2dview_input_get_drag_vec( view ); if( !is_zero( mv ) ) { + VEC3 olds = s->start; + VEC3 olde = s->end; gui_editor_2dview_add_plane_delta( &s->start, mv ); gui_editor_2dview_add_plane_delta( &s->end, mv ); + + EDITOR_PROP* sprop = eprop_from_ptr( s, &s->start ); + EDITOR_PROP* eprop = eprop_from_ptr( s, &s->end ); + + gui_editor_2dview_set_undo_prop( view, s, sprop->hash, sprop->size, &olds, &s->start ); + gui_editor_2dview_set_undo_prop( view, s, eprop->hash, eprop->size, &olde, &s->end ); + map_check_bounds( editor->map ); } } -void gui_editor_2dview_input_select_drag_pos( GUI_EDITOR_2DVIEW* view, VEC3* v ) { +void gui_editor_2dview_input_select_drag_wallpos( + GUI_EDITOR_2DVIEW* view, + VEC3* v +) { gui_editor_2dview_snap_plane( v ); VEC2 mv = gui_editor_2dview_input_get_drag_vec( view ); if( !is_zero( mv ) ) { + VEC3 oldv = *v; gui_editor_2dview_add_plane_delta( v, mv ); map_check_bounds( editor->map ); + + // giga hack + EDITOR_PROP* p = (EDITOR_PROP*)( (U64)v + sizeof(VEC3) ); + MAP_WALL* s = (MAP_WALL*)( (U64)v - p->parentoff ); + gui_editor_2dview_set_undo_prop( view, s, p->hash, p->size, &oldv, v ); } } -void gui_editor_2dview_input_select_drag_vertex( GUI_EDITOR_2DVIEW* view, MAP_VERTEX* v ) { +void gui_editor_2dview_input_select_drag_vertex( + GUI_EDITOR_2DVIEW* view, + MAP_VERTEX* v +) { gui_editor_2dview_snap_plane( &v->pos ); VEC2 mv = gui_editor_2dview_input_get_drag_vec( view ); if( !is_zero( mv ) ) { + VEC3 oldv = v->pos; gui_editor_2dview_add_plane_delta( &v->pos, mv ); map_check_bounds( editor->map ); + + EDITOR_PROP* p = eprop_from_ptr( v, &v->pos ); + gui_editor_2dview_set_undo_prop( view, v, p->hash, p->size, &oldv, &v->pos ); } } -void gui_editor_2dview_input_select_drag_polygon( GUI_EDITOR_2DVIEW* view, MAP_POLYGON* p ) { +void gui_editor_2dview_input_select_drag_polygon( + GUI_EDITOR_2DVIEW* view, + MAP_POLYGON* p +) { VEC2 mv = gui_editor_2dview_input_get_drag_vec( view ); if( !is_zero( mv ) ) { p->vertices.each( fn( MAP_VERTEX* v ) { + VEC3 oldv = v->pos; gui_editor_2dview_snap_plane( &v->pos ); gui_editor_2dview_add_plane_delta( &v->pos, mv ); + + EDITOR_PROP* p = eprop_from_ptr( v, &v->pos ); + gui_editor_2dview_set_undo_prop( view, v, p->hash, p->size, &oldv, &v->pos ); } ); map_polygon_calc_bounds( p ); @@ -940,31 +976,51 @@ void gui_editor_2dview_input_select_drag_polygon( GUI_EDITOR_2DVIEW* view, MAP_P } } -void gui_editor_2dview_input_select_drag_sprite( GUI_EDITOR_2DVIEW* view, MAP_SPRITE* s ) { +void gui_editor_2dview_input_select_drag_sprite( + GUI_EDITOR_2DVIEW* view, + MAP_SPRITE* s +) { gui_editor_2dview_snap_plane( &s->pos ); VEC2 mv = gui_editor_2dview_input_get_drag_vec( view ); if( !is_zero( mv ) ) { + VEC3 oldv = s->pos; gui_editor_2dview_add_plane_delta( &s->pos, mv ); + + EDITOR_PROP* p = eprop_from_ptr( s, &s->pos ); + gui_editor_2dview_set_undo_prop( view, s, p->hash, p->size, &oldv, &s->pos ); } } - -void gui_editor_2dview_input_select_drag_entity( GUI_EDITOR_2DVIEW* view, MAP_ENTITY* e ) { +void gui_editor_2dview_input_select_drag_entity( + GUI_EDITOR_2DVIEW* view, + MAP_ENTITY* e +) { gui_editor_2dview_snap_plane( &e->pos ); VEC2 mv = gui_editor_2dview_input_get_drag_vec( view ); if( !is_zero( mv ) ) { + VEC3 oldv = e->pos; gui_editor_2dview_add_plane_delta( &e->pos, mv ); + + EDITOR_PROP* p = eprop_from_ptr( e, &e->pos ); + gui_editor_2dview_set_undo_prop( view, e, p->hash, p->size, &oldv, &e->pos ); } } -void gui_editor_2dview_input_select_drag_origin( GUI_EDITOR_2DVIEW* view, WORLD_MAP* m ) { +void gui_editor_2dview_input_select_drag_origin( + GUI_EDITOR_2DVIEW* view, + WORLD_MAP* m +) { gui_editor_2dview_snap_plane( &m->startpos ); VEC2 mv = gui_editor_2dview_input_get_drag_vec( view ); if( !is_zero( mv ) ) { + VEC3 oldv = m->startpos; gui_editor_2dview_add_plane_delta( &m->startpos, mv ); + + EDITOR_PROP* p = eprop_from_ptr( m, &m->startpos ); + gui_editor_2dview_set_undo_prop( view, m, p->hash, p->size, &oldv, &m->startpos ); } } @@ -980,7 +1036,7 @@ U8 gui_editor_2dview_input_select_drag_obj( GUI_EDITOR_2DVIEW* view, void* obj, case EDITOR_SELECT_SPRITE: gui_editor_2dview_input_select_drag_sprite( view, (MAP_SPRITE*)p ); return 1; case EDITOR_SELECT_ORIGIN: gui_editor_2dview_input_select_drag_origin( view, (WORLD_MAP*)p ); return 1; case EDITOR_SELECT_PVERTEX: gui_editor_2dview_input_select_drag_vertex( view, (MAP_VERTEX*)p ); return 1; - case EDITOR_SELECT_WVERTEX: gui_editor_2dview_input_select_drag_pos( view, (VEC3*)p ); return 1; + case EDITOR_SELECT_WVERTEX: gui_editor_2dview_input_select_drag_wallpos( view, (VEC3*)p ); return 1; } return 0; @@ -990,9 +1046,45 @@ void gui_editor_2dview_input_select_ondrop( GUI_EDITOR_2DVIEW* view ) { if( !view->dragmoved ) gui_editor_propview_select( editor->gui.props, view->curdrag, view->dragtype, !input.keys[SDL_SCANCODE_LCTRL] ); + if( view->pending_undo_props.changes.size ) { + EDITOR_UNDO_ACTION rec{ .type = EDITOR_UNDO_EDITPROPS, .props = view->pending_undo_props }; + editor->undo_actions.push( rec ); + view->pending_undo_props = {}; + } + view->curdrag = 0; } +U8 gui_editor_2dview_drag_group( GUI_EDITOR_2DVIEW* view ) { + U8 update = 0; + if( view->dragtype == EDITOR_SELECT_WVERTEX || view->dragtype == EDITOR_SELECT_PVERTEX ) + return update; + + GUI_EDITOR_PROPVIEW* props = editor->gui.props; + // update all selected objects + for( auto& it : props->curselect ) { + if( it.obj == view->curdrag ) { + update = 1; + continue; + } + + // special case where a wall is selected but we are dragging one of its vertices + if( props->curselect.size == 1 && it.seltype == EDITOR_SELECT_WALL ) { + MAP_WALL* w = (MAP_WALL*)it.obj; + if( &w->start == view->curdrag || &w->end == view->curdrag ) { + update = 1; + continue; + } + } + + if( gui_editor_2dview_input_select_drag_obj( view, it.obj, it.seltype ) ) { + update = 1; + } + } + + return update; +} + void gui_editor_2dview_input_select_drag( GUI_EDITOR_2DVIEW* view ) { U8 m1 = gui_mbutton_down( 0 ); I32 mx, my; @@ -1017,15 +1109,17 @@ void gui_editor_2dview_input_select_drag( GUI_EDITOR_2DVIEW* view ) { return; } + EDITOR_UNDO_ACTION rec{ .type = EDITOR_UNDO_EDITPROPS }; + EDITOR_UNDO_PROPS::CHANGES changes{ .obj = (EOBJECT*)view->curdrag }; if( !gui_editor_2dview_input_select_drag_obj( view, view->curdrag, view->dragtype ) ) { view->oldmx = mx; view->oldmy = my; return; } - VEC2 mv = gui_editor_2dview_input_get_drag_vec( view ); - if( !is_zero( mv ) ) { - gui_editor_2dview_input_select_onmove( view ); + if( !is_zero( gui_editor_2dview_input_get_drag_vec( view ) ) ) { + gui_editor_2dview_input_select_onmove( view, &rec.props ); + rec.props.changes.push( changes ); } } -- cgit v1.2.3