Browse Source

Merge pull request #412 from AtomicGameEngine/JME-ATOMIC-377

Initial undo/redo support for node add/remove and scene inspector edits
JoshEngebretson 10 years ago
parent
commit
63860e0716
32 changed files with 887 additions and 85 deletions
  1. 1 16
      Script/AtomicEditor/ui/frames/HierarchyFrame.ts
  2. 14 1
      Script/AtomicEditor/ui/frames/inspector/ComponentInspector.ts
  3. 43 6
      Script/AtomicEditor/ui/frames/inspector/DataBinding.ts
  4. 14 0
      Script/AtomicEditor/ui/frames/inspector/NodeInspector.ts
  5. 1 0
      Script/AtomicEditor/ui/frames/menus/HierarchyFrameMenu.ts
  6. 6 1
      Script/TypeScript/AtomicWork.d.ts
  7. 18 1
      Source/Atomic/UI/UI.cpp
  8. 4 1
      Source/Atomic/UI/UI.h
  9. 6 0
      Source/Atomic/UI/UIEditField.cpp
  10. 18 0
      Source/Atomic/UI/UIEvents.h
  11. 5 0
      Source/Atomic/UI/UIInlineSelect.cpp
  12. 54 37
      Source/Atomic/UI/UIInput.cpp
  13. 20 1
      Source/Atomic/UI/UIWidget.cpp
  14. 2 2
      Source/Atomic/UI/UIWidget.h
  15. 0 1
      Source/AtomicEditor/Editors/JSResourceEditor.cpp
  16. 1 0
      Source/AtomicEditor/Editors/ResourceEditor.cpp
  17. 3 0
      Source/AtomicEditor/Editors/ResourceEditor.h
  18. 17 2
      Source/AtomicEditor/Editors/SceneEditor3D/Gizmo3D.cpp
  19. 1 0
      Source/AtomicEditor/Editors/SceneEditor3D/Gizmo3D.h
  20. 138 0
      Source/AtomicEditor/Editors/SceneEditor3D/SceneEditHistory.cpp
  21. 56 0
      Source/AtomicEditor/Editors/SceneEditor3D/SceneEditHistory.h
  22. 109 0
      Source/AtomicEditor/Editors/SceneEditor3D/SceneEditOps.cpp
  23. 118 0
      Source/AtomicEditor/Editors/SceneEditor3D/SceneEditOps.h
  24. 61 11
      Source/AtomicEditor/Editors/SceneEditor3D/SceneEditor3D.cpp
  25. 11 0
      Source/AtomicEditor/Editors/SceneEditor3D/SceneEditor3D.h
  26. 33 0
      Source/AtomicEditor/Editors/SceneEditor3D/SceneEditor3DEvents.h
  27. 40 1
      Source/AtomicEditor/Editors/SceneEditor3D/SceneView3D.cpp
  28. 3 0
      Source/AtomicEditor/Editors/SceneEditor3D/SceneView3D.h
  29. 21 0
      Source/ThirdParty/TurboBadger/tb_editfield.cpp
  30. 2 0
      Source/ThirdParty/TurboBadger/tb_editfield.h
  31. 57 3
      Source/ThirdParty/TurboBadger/tb_inline_select.cpp
  32. 10 1
      Source/ThirdParty/TurboBadger/tb_inline_select.h

+ 1 - 16
Script/AtomicEditor/ui/frames/HierarchyFrame.ts

@@ -68,11 +68,6 @@ class HierarchyFrame extends Atomic.UIWidget {
 
         this.subscribeToEvent("ComponentAdded", (ev: Atomic.ComponentAddedEvent) => {
 
-            //var resourceEditor = EditorUI.getCurrentResourceEditor();
-            //if (resourceEditor) {
-            //    resourceEditor.setModified(true);
-            //}
-
             if (!ev.component || ev.component.typeName != "PrefabComponent") return;
 
             var node = ev.node;
@@ -89,11 +84,6 @@ class HierarchyFrame extends Atomic.UIWidget {
 
         this.subscribeToEvent("ComponentRemoved", (ev: Atomic.ComponentRemovedEvent) => {
 
-            //var resourceEditor = EditorUI.getCurrentResourceEditor();
-            //if (resourceEditor) {
-            //    resourceEditor.setModified(true);
-            //}
-
             if (!ev.component || ev.component.typeName != "PrefabComponent") return;
 
             var node = ev.node;
@@ -142,8 +132,6 @@ class HierarchyFrame extends Atomic.UIWidget {
         if (!node.parent || node.scene != this.scene)
             return;
 
-        //EditorUI.getCurrentResourceEditor().setModified(true);
-
         var parentID = this.nodeIDToItemID[node.parent.id];
 
         var childItemID = this.recursiveAddNode(parentID, node);
@@ -158,8 +146,6 @@ class HierarchyFrame extends Atomic.UIWidget {
         if (!this.scene)
             return;
 
-        //EditorUI.getCurrentResourceEditor().setModified(true);
-
         var node = ev.node;
 
         if (this.filterNode(node))
@@ -219,8 +205,7 @@ class HierarchyFrame extends Atomic.UIWidget {
 
                 var node = this.scene.getNode(selectedId);
                 if (node) {
-
-                    node.removeAllComponents();
+                    this.scene.sendEvent("SceneEditNodeAddedRemoved", { scene:this.scene, node:node, added:false});
                     node.remove();
 
                 }

+ 14 - 1
Script/AtomicEditor/ui/frames/inspector/ComponentInspector.ts

@@ -18,7 +18,6 @@ class ComponentInspector extends Atomic.UISection {
         super();
 
         this.subscribeToEvent("WidgetEvent", (data) => this.handleWidgetEvent(data));
-
     }
 
     handleWidgetEvent(ev: Atomic.UIWidgetEvent) {
@@ -94,8 +93,12 @@ class ComponentInspector extends Atomic.UISection {
     inspect(component: Atomic.Component) {
 
         this.component = component;
+
         this.text = component.typeName;
 
+        component.scene.sendEvent("SceneEditSerializable", { serializable: component, operation: 0});
+        this.subscribeToEvent(component, "SceneEditSerializableUndoRedo", (data) => this.handleSceneEditSerializableUndoRedoEvent(data));
+
         // For JSComponents append the filename
         if (component.typeName == "JSComponent") {
 
@@ -293,6 +296,16 @@ class ComponentInspector extends Atomic.UISection {
 
     }
 
+    handleSceneEditSerializableUndoRedoEvent(ev) {
+
+      for (var i in this.bindings) {
+          this.bindings[i].objectLocked = true;
+          this.bindings[i].setWidgetValueFromObject();
+          this.bindings[i].objectLocked = false;
+      }
+
+    }
+
     // Move these to a mixing class
 
     addPrefabUI(layout: Atomic.UILayout) {

+ 43 - 6
Script/AtomicEditor/ui/frames/inspector/DataBinding.ts

@@ -26,6 +26,8 @@ class DataBinding {
         fd.id = "Vera";
         fd.size = 11;
 
+        var editWidgets: Array<Atomic.UIWidget> = [];
+
         var enumSource = null;
 
         if (attrInfo.type == Atomic.VAR_BOOL) {
@@ -65,7 +67,7 @@ class DataBinding {
                 var lp = new Atomic.UILayoutParams();
                 lp.width = 140;
                 field.layoutParams = lp;
-
+                editWidgets.push(field);
                 widget = field;
             }
 
@@ -79,6 +81,7 @@ class DataBinding {
             var lp = new Atomic.UILayoutParams();
             lp.width = 140;
             field.layoutParams = lp;
+            editWidgets.push(field);
 
             widget = field;
 
@@ -92,7 +95,7 @@ class DataBinding {
             var lp = new Atomic.UILayoutParams();
             lp.width = 140;
             field.layoutParams = lp;
-
+            editWidgets.push(field);
             widget = field;
         }
         else if (attrInfo.type == Atomic.VAR_VECTOR3 || attrInfo.type == Atomic.VAR_QUATERNION) {
@@ -105,6 +108,7 @@ class DataBinding {
 
             for (var i: any = 0; i < 3; i++) {
                 var select = new Atomic.UIInlineSelect();
+                editWidgets.push(select);
                 select.id = String(i + 1);
                 select.fontDescription = fd;
                 select.skinBg = "InspectorVectorAttrName";
@@ -127,6 +131,7 @@ class DataBinding {
             for (var i: any = 0; i < 4; i++) {
 
                 var select = new Atomic.UIInlineSelect();
+                editWidgets.push(select);
                 select.id = String(i + 1);
                 select.fontDescription = fd;
                 select.setLimits(-10000000, 10000000);
@@ -144,6 +149,7 @@ class DataBinding {
 
             for (var i: any = 0; i < 2; i++) {
                 var select = new Atomic.UIInlineSelect();
+                editWidgets.push(select);
                 select.id = String(i + 1);
                 select.fontDescription = fd;
                 select.skinBg = "InspectorVectorAttrName";
@@ -214,7 +220,7 @@ class DataBinding {
 
                         if (dragObject.object && dragObject.object.typeName == "Asset") {
 
-                            var asset = <ToolCore.Asset> dragObject.object;
+                            var asset = <ToolCore.Asset>dragObject.object;
 
                             if (asset.importerTypeName == importerName) {
                                 importer = asset.importer;
@@ -250,6 +256,11 @@ class DataBinding {
 
             var binding = new DataBinding(object, attrInfo, widget);
             binding.enumSource = enumSource;
+
+            for (var i in editWidgets) {
+                editWidgets[i].subscribeToEvent(editWidgets[i], "UIWidgetEditComplete", (ev) => binding.handleUIWidgetEditCompleteEvent(ev));
+            }
+
             return binding;
 
         }
@@ -340,13 +351,13 @@ class DataBinding {
         } else if (attrInfo.type == Atomic.VAR_RESOURCEREF && attrInfo.resourceTypeName) {
 
             // for cached resources, use the asset name, otherwise use the resource path name
-            var resource = <Atomic.Resource> object.getAttribute(attrInfo.name);
+            var resource = <Atomic.Resource>object.getAttribute(attrInfo.name);
             var text = "";
             if (resource) {
                 text = resource.name;
                 var asset = ToolCore.assetDatabase.getAssetByCachePath(resource.name);
                 if (asset)
-                  text = asset.name;
+                    text = asset.name;
             }
 
             widget["editField"].text = text;
@@ -445,6 +456,17 @@ class DataBinding {
 
     }
 
+    handleUIWidgetEditCompleteEvent(ev) {
+
+      // TODO: once new base class stuff is in, should be able to check for type
+      var scene = this.object["scene"];
+
+      if (!scene)
+          return;
+
+      scene.sendEvent("SceneEditSerializable", { serializable: this.object, operation: 1 });
+
+    }
 
     handleWidgetEvent(ev: Atomic.UIWidgetEvent): boolean {
 
@@ -459,6 +481,10 @@ class DataBinding {
 
                 this.object.setAttribute(this.attrInfo.name, Number(ev.refid) - 1);
                 this.setWidgetValueFromObject();
+                // TODO: once new base class stuff is in, should be able to check for type
+                if (this.object["scene"])
+                    this.object["scene"].sendEvent("SceneEditSerializable", { serializable: this.object, operation: 1 });
+
 
             }
 
@@ -478,9 +504,20 @@ class DataBinding {
         }
 
         if (ev.type == Atomic.UI_EVENT_TYPE_CHANGED) {
+
             if (this.widget == ev.target || this.widget.isAncestorOf(ev.target)) {
-                //EditorUI.getCurrentResourceEditor().setModified(true);
+
                 this.setObjectValueFromWidget(ev.target);
+
+                // UIEditField and UIInline select changes are handled by edit complete event
+                // Otherwise, we would get multiple edit snapshots
+                if (ev.target.getTypeName() != "UIEditField" && ev.target.getTypeName() != "UIInlineSelect") {
+
+                    // TODO: once new base class stuff is in, should be able to check for type
+                    if (this.object["scene"])
+                        this.object["scene"].sendEvent("SceneEditSerializable", { serializable: this.object, operation: 1 });
+                }
+
                 return true;
             }
         }

+ 14 - 0
Script/AtomicEditor/ui/frames/inspector/NodeInspector.ts

@@ -122,6 +122,9 @@ class NodeInspector extends ScriptWidget {
 
         this.node = node;
 
+        node.scene.sendEvent("SceneEditSerializable", { serializable: node, operation: 0});
+        this.subscribeToEvent(node, "SceneEditSerializableUndoRedo", (data) => this.handleSceneEditSerializableUndoRedoEvent(data));        
+
         this.isPrefab = this.detectPrefab(node);
 
         var fd = new Atomic.UIFontDescription();
@@ -314,6 +317,17 @@ class NodeInspector extends ScriptWidget {
 
     }
 
+    handleSceneEditSerializableUndoRedoEvent(ev) {
+
+      for (var i in this.bindings) {
+          this.bindings[i].objectLocked = true;
+          this.bindings[i].setWidgetValueFromObject();
+          this.bindings[i].objectLocked = false;
+      }
+
+    }
+
+
     isPrefab: boolean;
     node: Atomic.Node;
     nodeLayout: Atomic.UILayout;

+ 1 - 0
Script/AtomicEditor/ui/frames/menus/HierarchyFrameMenu.ts

@@ -38,6 +38,7 @@ class HierarchyFrameMenus extends Atomic.ScriptObject {
                 if (node) {
 
                     child = node.createChild("Node");
+                    node.scene.sendEvent("SceneEditNodeAddedRemoved", { scene:node.scene, node:node, added:true});
 
                 }
 

+ 6 - 1
Script/TypeScript/AtomicWork.d.ts

@@ -116,6 +116,11 @@ declare module Atomic {
         touch: boolean;
     }
 
+    export interface UIWidgetFocusChangedEvent {
+        widget: UIWidget;
+        focused: boolean;
+    }
+
     export interface UIWidgetDeletedEvent {
 
         widget: UIWidget;
@@ -251,7 +256,7 @@ declare module Editor {
   export interface GizmoAxisModeChangedEvent {
     mode:AxisMode;
   }
-  
+
 }
 
 declare module ToolCore {

+ 18 - 1
Source/Atomic/UI/UI.cpp

@@ -750,6 +750,18 @@ bool UI::OnWidgetDying(tb::TBWidget *widget)
     return false;
 }
 
+void UI::OnWidgetFocusChanged(TBWidget *widget, bool focused)
+{
+    if (widgetWrap_.Contains(widget))
+    {
+        VariantMap evData;
+        UIWidget* uiWidget = widgetWrap_[widget];
+        evData[UIWidgetFocusChanged::P_WIDGET]  = uiWidget;
+        evData[UIWidgetFocusChanged::P_FOCUSED]  = focused;
+        uiWidget->SendEvent(E_UIWIDGETFOCUSCHANGED, evData);
+    }
+}
+
 void UI::ShowDebugHud(bool value)
 {
     SystemUI::DebugHud* hud = GetSubsystem<SystemUI::DebugHud>();
@@ -813,10 +825,15 @@ SystemUI::MessageBox* UI::ShowSystemMessageBox(const String& title, const String
 
 }
 
-
 UIWidget* UI::GetWidgetAt(int x, int y, bool include_children)
 {
     return WrapWidget(rootWidget_->GetWidgetAt(x, y, include_children));
 }
 
+bool UI::OnWidgetInvokeEvent(tb::TBWidget *widget, const tb::TBWidgetEvent &ev)
+{
+    return false;
+}
+
+
 }

+ 4 - 1
Source/Atomic/UI/UI.h

@@ -119,7 +119,10 @@ private:
 
     // TBWidgetListener
     void OnWidgetDelete(tb::TBWidget *widget);
-    bool OnWidgetDying(tb::TBWidget *widget);
+    bool OnWidgetDying(tb::TBWidget *widget);    
+    void OnWidgetFocusChanged(tb::TBWidget *widget, bool focused);
+    bool OnWidgetInvokeEvent(tb::TBWidget *widget, const tb::TBWidgetEvent &ev);
+
 
     tb::TBWidget* rootWidget_;
     UIRenderer* renderer_;

+ 6 - 0
Source/Atomic/UI/UIEditField.cpp

@@ -196,6 +196,12 @@ void UIEditField::SetTextAlign(UI_TEXT_ALIGN align)
 
 bool UIEditField::OnEvent(const tb::TBWidgetEvent &ev)
 {
+    if (ev.type == EVENT_TYPE_CUSTOM && ev.ref_id == TBIDC("edit_complete"))
+    {
+        SendEvent(E_UIWIDGETEDITCOMPLETE);
+        return true;
+    }
+
     return UIWidget::OnEvent(ev);
 }
 

+ 18 - 0
Source/Atomic/UI/UIEvents.h

@@ -103,4 +103,22 @@ EVENT(E_UISHORTCUT, UIShortcut)
 
 }
 
+EVENT(E_UIWIDGETFOCUSCHANGED, UIWidgetFocusChanged)
+{
+    PARAM(P_WIDGET, Widget);             // UIWidget pointer
+    PARAM(P_FOCUSED, Focused);             // bool
+}
+EVENT(E_UIWIDGETFOCUSESCAPED, UIWidgetFocusEscaped)
+{
+}
+
+EVENT(E_UIWIDGETEDITCOMPLETE, UIWidgetEditComplete)
+{
+}
+
+EVENT(E_UIUNHANDLEDSHORTCUT, UIUnhandledShortcut)
+{
+    PARAM(P_REFID, RefID); // string tbid
+}
+
 }

+ 5 - 0
Source/Atomic/UI/UIInlineSelect.cpp

@@ -70,6 +70,11 @@ void UIInlineSelect::SetLimits(double minimum, double maximum)
 
 bool UIInlineSelect::OnEvent(const tb::TBWidgetEvent &ev)
 {
+    if (ev.type == EVENT_TYPE_CUSTOM && ev.ref_id == TBIDC("edit_complete"))
+    {
+        SendEvent(E_UIWIDGETEDITCOMPLETE);
+        return true;
+    }
     return UIWidget::OnEvent(ev);
 }
 

+ 54 - 37
Source/Atomic/UI/UIInput.cpp

@@ -213,7 +213,7 @@ void UI::HandleTouchEnd(StringHash eventType, VariantMap& eventData)
     rootWidget_->InvokePointerUp(px, py, TB_MODIFIER_NONE, true, touchId);
 }
 
-static bool InvokeShortcut(int key, SPECIAL_KEY special_key, MODIFIER_KEYS modifierkeys, bool down)
+static bool InvokeShortcut(UI* ui, int key, SPECIAL_KEY special_key, MODIFIER_KEYS modifierkeys, bool down)
 {
 #ifdef __APPLE__
     bool shortcut_key = (modifierkeys & TB_SUPER) ? true : false;
@@ -286,15 +286,20 @@ static bool InvokeShortcut(int key, SPECIAL_KEY special_key, MODIFIER_KEYS modif
 
     }
 
-    if (!eventWidget)
+    if (!eventWidget || !eventWidget->InvokeEvent(ev))
+    {
+        VariantMap evData;
+        evData[UIUnhandledShortcut::P_REFID] = id;
+        ui->SendEvent(E_UIUNHANDLEDSHORTCUT, evData);
         return false;
+    }
 
-    return eventWidget->InvokeEvent(ev);
+    return true;
 }
 
-static bool InvokeKey(TBWidget* root, unsigned int key, SPECIAL_KEY special_key, MODIFIER_KEYS modifierkeys, bool keydown)
+static bool InvokeKey(UI* ui, TBWidget* root, unsigned int key, SPECIAL_KEY special_key, MODIFIER_KEYS modifierkeys, bool keydown)
 {
-    if (InvokeShortcut(key, special_key, modifierkeys, keydown))
+    if (InvokeShortcut(ui, key, special_key, modifierkeys, keydown))
         return true;
     root->InvokeKey(key, special_key, modifierkeys, keydown);
     return true;
@@ -303,6 +308,11 @@ static bool InvokeKey(TBWidget* root, unsigned int key, SPECIAL_KEY special_key,
 
 void UI::HandleKey(bool keydown, int keycode, int scancode)
 {
+    if (keydown && (keycode == KEY_ESC || keycode == KEY_RETURN || keycode == KEY_RETURN2 || keycode == KEY_KP_ENTER)
+            && TBWidget::focused_widget)
+    {
+        SendEvent(E_UIWIDGETFOCUSESCAPED);
+    }
 
 #ifdef ATOMIC_PLATFORM_WINDOWS
     if (keycode == KEY_LCTRL || keycode == KEY_RCTRL)
@@ -322,95 +332,102 @@ void UI::HandleKey(bool keydown, int keycode, int scancode)
 #endif
     MODIFIER_KEYS mod = GetModifierKeys(qualifiers, superdown);
 
+    SPECIAL_KEY specialKey = TB_KEY_UNDEFINED;
+
     switch (keycode)
     {
     case KEY_RETURN:
     case KEY_RETURN2:
     case KEY_KP_ENTER:
-        InvokeKey(rootWidget_, 0, TB_KEY_ENTER, mod, keydown);
+        specialKey =  TB_KEY_ENTER;
         break;
-
     case KEY_F1:
-        InvokeKey(rootWidget_, 0, TB_KEY_F1, mod, keydown);
+        specialKey = TB_KEY_F1;
         break;
     case KEY_F2:
-        InvokeKey(rootWidget_, 0, TB_KEY_F2, mod, keydown);
+        specialKey = TB_KEY_F2;
         break;
     case KEY_F3:
-        InvokeKey(rootWidget_, 0, TB_KEY_F3, mod, keydown);
+        specialKey = TB_KEY_F3;
         break;
     case KEY_F4:
-        InvokeKey(rootWidget_, 0, TB_KEY_F4, mod, keydown);
+        specialKey = TB_KEY_F4;
         break;
     case KEY_F5:
-        InvokeKey(rootWidget_, 0, TB_KEY_F5, mod, keydown);
+        specialKey = TB_KEY_F5;
         break;
     case KEY_F6:
-        InvokeKey(rootWidget_, 0, TB_KEY_F6, mod, keydown);
+        specialKey = TB_KEY_F6;
         break;
     case KEY_F7:
-        InvokeKey(rootWidget_, 0, TB_KEY_F7, mod, keydown);
+        specialKey = TB_KEY_F7;
         break;
     case KEY_F8:
-        InvokeKey(rootWidget_, 0, TB_KEY_F8, mod, keydown);
+        specialKey = TB_KEY_F8;
         break;
     case KEY_F9:
-        InvokeKey(rootWidget_, 0, TB_KEY_F9, mod, keydown);
+        specialKey = TB_KEY_F9;
         break;
     case KEY_F10:
-        InvokeKey(rootWidget_, 0, TB_KEY_F10, mod, keydown);
+        specialKey = TB_KEY_F10;
         break;
     case KEY_F11:
-        InvokeKey(rootWidget_, 0, TB_KEY_F11, mod, keydown);
+        specialKey = TB_KEY_F11;
         break;
     case KEY_F12:
-        InvokeKey(rootWidget_, 0, TB_KEY_F12, mod, keydown);
+        specialKey = TB_KEY_F12;
         break;
     case KEY_LEFT:
-        InvokeKey(rootWidget_, 0, TB_KEY_LEFT, mod, keydown);
+        specialKey = TB_KEY_LEFT;
         break;
     case KEY_UP:
-        InvokeKey(rootWidget_, 0, TB_KEY_UP, mod, keydown);
+        specialKey = TB_KEY_UP;
         break;
     case KEY_RIGHT:
-        InvokeKey(rootWidget_, 0, TB_KEY_RIGHT, mod, keydown);
+        specialKey = TB_KEY_RIGHT;
         break;
     case KEY_DOWN:
-        InvokeKey(rootWidget_, 0, TB_KEY_DOWN, mod, keydown);
+        specialKey = TB_KEY_DOWN;
         break;
     case KEY_PAGEUP:
-        InvokeKey(rootWidget_, 0, TB_KEY_PAGE_UP, mod, keydown);
+        specialKey = TB_KEY_PAGE_UP;
         break;
     case KEY_PAGEDOWN:
-        InvokeKey(rootWidget_, 0, TB_KEY_PAGE_DOWN, mod, keydown);
+        specialKey = TB_KEY_PAGE_DOWN;
         break;
     case KEY_HOME:
-        InvokeKey(rootWidget_, 0, TB_KEY_HOME, mod, keydown);
+        specialKey = TB_KEY_HOME;
         break;
     case KEY_END:
-        InvokeKey(rootWidget_, 0, TB_KEY_END, mod, keydown);
+        specialKey = TB_KEY_END;
         break;
     case KEY_INSERT:
-        InvokeKey(rootWidget_, 0, TB_KEY_INSERT, mod, keydown);
+        specialKey = TB_KEY_INSERT;
         break;
     case KEY_TAB:
-        InvokeKey(rootWidget_, 0, TB_KEY_TAB, mod, keydown);
+        specialKey = TB_KEY_TAB;
         break;
     case KEY_DELETE:
-        InvokeKey(rootWidget_, 0, TB_KEY_DELETE, mod, keydown);
+        specialKey = TB_KEY_DELETE;
         break;
     case KEY_BACKSPACE:
-        InvokeKey(rootWidget_, 0, TB_KEY_BACKSPACE, mod, keydown);
+        specialKey = TB_KEY_BACKSPACE;
         break;
     case KEY_ESC:
-        InvokeKey(rootWidget_, 0, TB_KEY_ESC, mod, keydown);
+        specialKey =  TB_KEY_ESC;
         break;
-    default:
+    }
+
+    if (specialKey == TB_KEY_UNDEFINED)
+    {
         if (mod & TB_SUPER)
         {
-            InvokeKey(rootWidget_, keycode, TB_KEY_UNDEFINED, mod, keydown);
+            InvokeKey(this, rootWidget_, keycode, TB_KEY_UNDEFINED, mod, keydown);
         }
-
+    }
+    else
+    {
+        InvokeKey(this, rootWidget_, 0, specialKey, mod, keydown);
     }
 
 }
@@ -477,8 +494,8 @@ void UI::HandleTextInput(StringHash eventType, VariantMap& eventData)
 
     for (unsigned i = 0; i < text.Length(); i++)
     {
-        InvokeKey(rootWidget_, text[i], TB_KEY_UNDEFINED, TB_MODIFIER_NONE, true);
-        InvokeKey(rootWidget_, text[i], TB_KEY_UNDEFINED, TB_MODIFIER_NONE, false);
+        InvokeKey(this, rootWidget_, text[i], TB_KEY_UNDEFINED, TB_MODIFIER_NONE, true);
+        InvokeKey(this, rootWidget_, text[i], TB_KEY_UNDEFINED, TB_MODIFIER_NONE, false);
     }
 
 }

+ 20 - 1
Source/Atomic/UI/UIWidget.cpp

@@ -39,7 +39,8 @@ namespace Atomic
 
 UIWidget::UIWidget(Context* context, bool createWidget) : Object(context),
     widget_(0),
-    preferredSize_(new UIPreferredSize())
+    preferredSize_(new UIPreferredSize()),
+    multiTouch_(false)
 {
     AddRef();
 
@@ -738,6 +739,24 @@ bool UIWidget::OnEvent(const tb::TBWidgetEvent &ev)
     return false;
 }
 
+bool UIWidget::GetCaptured()
+{
+    if (!widget_)
+        return false;
+
+    return widget_->IsCaptured();
+
+}
+
+void UIWidget::SetCapturing(bool capturing)
+{
+    if (!widget_)
+        return;
+
+    widget_->SetCapturing(capturing);
+}
+
+
 void UIWidget::InvalidateLayout()
 {
     if (!widget_)

+ 2 - 2
Source/Atomic/UI/UIWidget.h

@@ -247,8 +247,9 @@ class UIWidget : public Object, public tb::TBWidgetDelegate
 
     bool IsMultiTouch() { return multiTouch_; }
 
-    void SetCapturing(bool capturing) { widget_->SetCapturing(capturing); }
+    bool GetCaptured();
 
+    void SetCapturing(bool capturing);
     bool GetCapturing() { return widget_->GetCapturing(); }
 
     void InvokeShortcut(const String& shortcut);
@@ -269,7 +270,6 @@ protected:
 
     SharedPtr<UIDragObject> dragObject_;
 
-
     bool multiTouch_;
 
 };

+ 0 - 1
Source/AtomicEditor/Editors/JSResourceEditor.cpp

@@ -220,7 +220,6 @@ void JSResourceEditor::OnChange(TBStyleEdit* styleEdit)
 {
     textDelta_ = 0.25f;
     textDirty_ = true;
-    modified_ = true;
 
     SetModified(true);
 

+ 1 - 0
Source/AtomicEditor/Editors/ResourceEditor.cpp

@@ -174,6 +174,7 @@ void ResourceEditor::InvokeShortcut(const String& shortcut)
 
 void ResourceEditor::SetModified(bool modified)
 {
+    modified_ = modified;
     if (modified)
     {
         String filename = GetFileNameAndExtension(fullpath_);

+ 3 - 0
Source/AtomicEditor/Editors/ResourceEditor.h

@@ -46,6 +46,9 @@ public:
 
     const String& GetFullPath() { return fullpath_; }
 
+    virtual void Undo() {}
+    virtual void Redo() {}
+
     virtual bool Save() { return true; }
 
     UIWidget* GetRootContentWidget() { return rootContentWidget_; }

+ 17 - 2
Source/AtomicEditor/Editors/SceneEditor3D/Gizmo3D.cpp

@@ -21,7 +21,8 @@ namespace AtomicEditor
 {
 
 
-Gizmo3D::Gizmo3D(Context* context) : Object(context)
+Gizmo3D::Gizmo3D(Context* context) : Object(context),
+    dragging_(false)
 {
     ResourceCache* cache = GetSubsystem<ResourceCache>();
 
@@ -169,7 +170,18 @@ void Gizmo3D::Use()
     // Recalculate axes only when not left-dragging
     bool drag = input->GetMouseButtonDown(MOUSEB_LEFT);// && (Abs(input->GetMouseMoveX()) > 3 || Abs(input->GetMouseMoveY()) > 3);
     if (!drag)
+    {
+        if (dragging_)
+        {
+            VariantMap eventData;
+            eventData[SceneEditSerializable::P_SERIALIZABLE] = editNodes_->At(0);
+            eventData[SceneEditSerializable::P_OPERATION] = 1;
+            scene_->SendEvent(E_SCENEEDITSERIALIZABLE, eventData);
+            dragging_ = false;
+        }
+
         CalculateGizmoAxes();
+    }
 
     gizmoAxisX_.Update(cameraRay, scale, drag, camera_->GetNode());
     gizmoAxisY_.Update(cameraRay, scale, drag, camera_->GetNode());
@@ -209,7 +221,7 @@ void Gizmo3D::Use()
         gizmoAxisZ_.lastSelected_ = gizmoAxisZ_.selected_;
     }
 
-    if (drag)
+    if (drag && Selected())
         Drag();
 
 }
@@ -359,6 +371,8 @@ void Gizmo3D::Drag()
 {
     bool moved = false;
 
+    dragging_ = true;
+
     float scale = gizmoNode_->GetScale().x_;
 
     if (editMode_ == EDIT_MOVE)
@@ -428,6 +442,7 @@ void Gizmo3D::SetEditMode(EditMode mode)
 
 void Gizmo3D::Hide()
 {
+    gizmoAxisX_.selected_ = gizmoAxisY_.selected_ = gizmoAxisZ_.selected_ = false;
     gizmo_->SetEnabled(false);
 }
 

+ 1 - 0
Source/AtomicEditor/Editors/SceneEditor3D/Gizmo3D.h

@@ -152,6 +152,7 @@ private:
     AxisMode axisMode_;
 
     Vector<Node *> *editNodes_;
+    bool dragging_;
 
 };
 

+ 138 - 0
Source/AtomicEditor/Editors/SceneEditor3D/SceneEditHistory.cpp

@@ -0,0 +1,138 @@
+
+#include <Atomic/IO/Log.h>
+#include <Atomic/Core/Timer.h>
+#include <Atomic/Scene/Scene.h>
+#include <Atomic/Scene/Component.h>
+#include <Atomic/Scene/SceneEvents.h>
+
+#include "../../EditorMode/AEEditorEvents.h"
+
+#include "SceneEditOps.h"
+#include "SceneEditor3DEvents.h"
+#include "SceneEditHistory.h"
+
+namespace AtomicEditor
+{
+
+SceneEditHistory::SceneEditHistory(Context* context, Scene* scene) :
+    Object(context),
+    scene_(scene)
+{
+    SubscribeToEvent(scene_, E_SCENEEDITSERIALIZABLE, HANDLER(SceneEditHistory, HandleSceneEditSerializable));    
+    SubscribeToEvent(scene_, E_SCENEEDITNODEADDEDREMOVED, HANDLER(SceneEditHistory, HandleSceneEditNodeAddedRemoved));
+}
+
+SceneEditHistory::~SceneEditHistory()
+{
+
+}
+
+void SceneEditHistory::AddUndoOp(SceneEditOp* op)
+{
+    undoHistory_.Push(op);
+
+    scene_->SendEvent(E_SCENEEDITSCENEMODIFIED);
+
+    for (unsigned i = 0; i < redoHistory_.Size(); i++)
+    {
+        delete redoHistory_[i];
+    }
+
+    redoHistory_.Clear();
+}
+
+void SceneEditHistory::HandleSceneEditSerializableUndoRedo(StringHash eventType, VariantMap& eventData)
+{
+    SharedPtr<Serializable> serial(static_cast<Serializable*>(eventData[SceneEditSerializableUndoRedo::P_SERIALIZABLE].GetPtr()));
+
+    if (editStates_.Contains(serial))
+    {
+        scene_->SendEvent(E_SCENEEDITSCENEMODIFIED);
+        editStates_[serial] = eventData[SceneEditSerializableUndoRedo::P_STATE].GetVectorBuffer();
+    }
+}
+
+void SceneEditHistory::HandleSceneEditNodeAddedRemoved(StringHash eventType, VariantMap& eventData)
+{
+    bool added = eventData[SceneEditNodeAddedRemoved::P_ADDED].GetBool();
+    Node* node = static_cast<Node*>(eventData[SceneEditNodeAddedRemoved::P_NODE].GetPtr());
+    NodeAddedRemovedOp* op = new NodeAddedRemovedOp(node, added);
+    AddUndoOp(op);
+}
+
+void SceneEditHistory::HandleSceneEditSerializable(StringHash eventType, VariantMap& eventData)
+{
+
+    int editop = eventData[SceneEditSerializable::P_OPERATION].GetInt();
+
+    SharedPtr<Serializable> serial(static_cast<Serializable*>(eventData[SceneEditSerializable::P_SERIALIZABLE].GetPtr()));
+
+    if (editop == 0) // begin
+    {
+        if (editStates_.Contains(serial))
+            return;
+
+        SubscribeToEvent(serial, E_SCENEEDITSERIALIZABLEUNDOREDO, HANDLER(SceneEditHistory, HandleSceneEditSerializableUndoRedo));
+        VectorBuffer& vb = editStates_[serial];
+        vb.Clear();
+        serial->Serializable::Save(vb);
+        vb.Seek(0);
+    }
+    else if (editop == 1) // change
+    {
+        if (!editStates_.Contains(serial))
+            return;
+
+        VectorBuffer& beginState = editStates_[serial];
+        VectorBuffer deltaState;
+        serial->Serializable::Save(deltaState);
+        deltaState.Seek(0);
+
+        if (beginState.GetSize() != deltaState.GetSize() ||
+                memcmp(beginState.GetData(), deltaState.GetData(), deltaState.GetSize()))
+        {
+            SerializableEditOp* op = new SerializableEditOp(serial, beginState, deltaState);
+            AddUndoOp(op);
+            editStates_[serial] = deltaState;
+        }
+    }
+    else if (editop == 2) // end
+    {
+        if (!editStates_.Contains(serial))
+            return;
+
+        UnsubscribeFromEvent(serial, E_SCENEEDITSERIALIZABLEUNDOREDO);
+
+        editStates_.Erase(serial);
+    }
+}
+
+void SceneEditHistory::Undo()
+{
+    if (!undoHistory_.Size())
+        return;
+
+    SceneEditOp* op = undoHistory_.Back();
+    undoHistory_.Pop();
+
+    op->Undo();
+
+    redoHistory_.Push(op);
+
+}
+
+void SceneEditHistory::Redo()
+{
+    if (!redoHistory_.Size())
+        return;
+
+    SceneEditOp* op = redoHistory_.Back();
+    redoHistory_.Pop();
+
+    op->Redo();
+
+    undoHistory_.Push(op);
+
+}
+
+}

+ 56 - 0
Source/AtomicEditor/Editors/SceneEditor3D/SceneEditHistory.h

@@ -0,0 +1,56 @@
+//
+// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
+// LICENSE: Atomic Game Engine Editor and Tools EULA
+// Please see LICENSE_ATOMIC_EDITOR_AND_TOOLS.md in repository root for
+// license information: https://github.com/AtomicGameEngine/AtomicGameEngine
+//
+
+#pragma once
+
+#include <Atomic/Core/Object.h>
+
+using namespace Atomic;
+namespace Atomic
+{
+
+class Serializable;
+class Scene;
+
+}
+
+namespace AtomicEditor
+{
+
+class SceneEditOp;
+
+/// Simple scene history to support undo/redo via snapshots of scene state
+class SceneEditHistory: public Object
+{
+    OBJECT(SceneEditHistory);
+
+public:
+
+    SceneEditHistory(Context* context, Scene* scene);
+    virtual ~SceneEditHistory();
+
+    void Undo();
+    void Redo();
+
+private:
+
+    void HandleSceneEditSerializable(StringHash eventType, VariantMap& eventData);
+    void HandleSceneEditSerializableUndoRedo(StringHash eventType, VariantMap& eventData);
+    void HandleSceneEditNodeAddedRemoved(StringHash eventType, VariantMap& eventData);
+
+    void AddUndoOp(SceneEditOp* op);
+
+    WeakPtr<Scene> scene_;
+
+    HashMap< SharedPtr<Serializable>, VectorBuffer > editStates_;
+
+    PODVector<SceneEditOp*> undoHistory_;
+    PODVector<SceneEditOp*> redoHistory_;
+
+};
+
+}

+ 109 - 0
Source/AtomicEditor/Editors/SceneEditor3D/SceneEditOps.cpp

@@ -0,0 +1,109 @@
+
+#include <Atomic/IO/Log.h>
+#include <Atomic/Scene/Node.h>
+#include <Atomic/Scene/Component.h>
+
+#include "SceneEditOps.h"
+#include "SceneEditor3DEvents.h"
+
+namespace AtomicEditor
+{
+
+
+NodeEditOp::NodeEditOp(Node* node)
+{
+    node_ = node;
+
+    // parent at the time of the operation
+    parent_ = node->GetParent();
+}
+
+NodeAddedRemovedOp::NodeAddedRemovedOp(Node* node, bool added) : NodeEditOp(node),
+    added_(added)
+{
+    type_ = SCENEEDIT_NODE_ADDED_REMOVED;
+}
+
+bool NodeAddedRemovedOp::Undo()
+{
+    if (added_)
+    {
+        node_->Remove();
+    }
+    else
+    {
+        parent_->AddChild(node_);
+    }
+
+    return true;
+}
+
+bool NodeAddedRemovedOp::Redo()
+{
+    if (added_)
+    {
+        parent_->AddChild(node_);
+    }
+    else
+    {
+        node_->Remove();
+    }
+
+    return true;
+}
+
+SerializableEditOp::SerializableEditOp(Serializable* serializable, const VectorBuffer& beginState, const VectorBuffer& endState) : SceneEditOp(),
+    serializable_(serializable),
+    beginState_(beginState),
+    endState_(endState)
+{
+    type_ = SCENEEDIT_SERIALIZABLE_EDIT;
+
+    if (!beginState_.GetSize())
+    {
+        LOGERRORF("Zero size beginState");
+    }
+
+    if (!endState_.GetSize())
+    {
+        LOGERRORF("Zero size endState");
+    }
+}
+
+bool SerializableEditOp::Undo()
+{    
+    if (!serializable_->Serializable::Load(beginState_))
+    {
+        LOGERRORF("Error loading beginState");
+    }
+    beginState_.Seek(0);
+
+    VariantMap eventData;
+    eventData[SceneEditSerializableUndoRedo::P_SERIALIZABLE] = serializable_;
+    eventData[SceneEditSerializableUndoRedo::P_UNDO] = true;
+    eventData[SceneEditSerializableUndoRedo::P_STATE] = beginState_;
+    serializable_->SendEvent(E_SCENEEDITSERIALIZABLEUNDOREDO, eventData);
+
+    return true;
+}
+
+bool SerializableEditOp::Redo()
+{
+
+    if (!serializable_->Serializable::Load(endState_))
+    {
+        LOGERRORF("Error loading endState");
+    }
+
+    endState_.Seek(0);
+
+    VariantMap eventData;
+    eventData[SceneEditSerializableUndoRedo::P_SERIALIZABLE] = serializable_;
+    eventData[SceneEditSerializableUndoRedo::P_UNDO] = false;
+    eventData[SceneEditSerializableUndoRedo::P_STATE] = endState_;
+    serializable_->SendEvent(E_SCENEEDITSERIALIZABLEUNDOREDO, eventData);
+
+    return true;
+}
+
+}

+ 118 - 0
Source/AtomicEditor/Editors/SceneEditor3D/SceneEditOps.h

@@ -0,0 +1,118 @@
+//
+// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
+// LICENSE: Atomic Game Engine Editor and Tools EULA
+// Please see LICENSE_ATOMIC_EDITOR_AND_TOOLS.md in repository root for
+// license information: https://github.com/AtomicGameEngine/AtomicGameEngine
+//
+
+#pragma once
+
+#include <Atomic/Core/Object.h>
+#include <Atomic/IO/VectorBuffer.h>
+
+namespace Atomic
+{
+
+class Scene;
+class Node;
+class Component;
+class Serializable;
+
+}
+
+using namespace Atomic;
+
+namespace AtomicEditor
+{
+
+enum SceneEditType
+{
+    SCENEEDIT_UNKNOWN = 0,
+    SCENEEDIT_NODE_ADDED_REMOVED,
+    SCENEEDIT_COMPONENT_ADDED_REMOVED,
+    SCENEEDIT_SERIALIZABLE_EDIT,
+};
+
+class SceneEditOp
+{
+
+public:
+
+    SceneEditOp() { type_ = SCENEEDIT_UNKNOWN; }
+    virtual ~SceneEditOp() { }
+
+    virtual bool Undo() = 0;
+    virtual bool Redo() = 0;
+
+    SceneEditType type_;
+
+};
+
+class NodeEditOp : public SceneEditOp
+{
+protected:
+
+    NodeEditOp(Node* node);
+
+public:
+
+    SharedPtr<Node> node_;
+    SharedPtr<Node> parent_;
+
+};
+
+class NodeAddedRemovedOp : public NodeEditOp
+{
+
+public:
+
+    NodeAddedRemovedOp(Node* node, bool added);
+
+    bool added_;
+
+    bool Undo();
+    bool Redo();
+};
+
+class ComponentEditOp : public SceneEditOp
+{
+protected:
+
+    ComponentEditOp(Component* component);
+
+public:
+
+    SharedPtr<Component> component_;
+
+};
+
+class ComponentAddedRemovedOp : public ComponentEditOp
+{
+
+public:
+
+    ComponentAddedRemovedOp(Node* node, bool added);
+
+    bool added_;
+
+    bool Undo();
+    bool Redo();
+};
+
+class SerializableEditOp : public SceneEditOp
+{
+
+public:
+
+    SerializableEditOp(Serializable* serializable, const VectorBuffer& beginState, const VectorBuffer& endState);
+
+    SharedPtr<Serializable> serializable_;
+    VectorBuffer beginState_;
+    VectorBuffer endState_;
+
+    bool Undo();
+    bool Redo();
+};
+
+
+}

+ 61 - 11
Source/AtomicEditor/Editors/SceneEditor3D/SceneEditor3D.cpp

@@ -29,6 +29,7 @@
 #include "../../EditorMode/AEEditorEvents.h"
 
 #include "SceneEditor3D.h"
+#include "SceneEditHistory.h"
 #include "SceneEditor3DEvents.h"
 
 using namespace ToolCore;
@@ -56,11 +57,11 @@ SceneEditor3D ::SceneEditor3D(Context* context, const String &fullpath, UITabCon
     // EARLY ACCESS
     if (fullpath.Find(String("ToonTown")) != String::NPOS)
     {
-          sceneView_->GetCameraNode()->SetWorldPosition(Vector3(-119.073f, 76.1121f, 16.47763f));
-          Quaternion q(0.55f, 0.14f,  0.8f, -0.2f);
-          sceneView_->SetYaw(q.YawAngle());
-          sceneView_->SetPitch(q.PitchAngle());
-          sceneView_->GetCameraNode()->SetWorldRotation(q);
+        sceneView_->GetCameraNode()->SetWorldPosition(Vector3(-119.073f, 76.1121f, 16.47763f));
+        Quaternion q(0.55f, 0.14f,  0.8f, -0.2f);
+        sceneView_->SetYaw(q.YawAngle());
+        sceneView_->SetPitch(q.PitchAngle());
+        sceneView_->GetCameraNode()->SetWorldRotation(q);
     }
     else
     {
@@ -95,8 +96,13 @@ SceneEditor3D ::SceneEditor3D(Context* context, const String &fullpath, UITabCon
     SubscribeToEvent(E_EDITORPLAYSTARTED, HANDLER(SceneEditor3D, HandlePlayStarted));
     SubscribeToEvent(E_EDITORPLAYSTOPPED, HANDLER(SceneEditor3D, HandlePlayStopped));
 
+    SubscribeToEvent(scene_, E_NODEADDED, HANDLER(SceneEditor3D, HandleNodeAdded));
     SubscribeToEvent(scene_, E_NODEREMOVED, HANDLER(SceneEditor3D, HandleNodeRemoved));
 
+    SubscribeToEvent(scene_, E_SCENEEDITSCENEMODIFIED, HANDLER(SceneEditor3D, HandleSceneEditSceneModified));
+
+    editHistory_ = new SceneEditHistory(context_, scene_);
+
 }
 
 SceneEditor3D::~SceneEditor3D()
@@ -112,7 +118,12 @@ bool SceneEditor3D::OnEvent(const TBWidgetEvent &ev)
         {
             if (selectedNode_)
             {
-                selectedNode_->RemoveAllComponents();
+                VariantMap editData;
+                editData[SceneEditNodeAddedRemoved::P_SCENE] = scene_;
+                editData[SceneEditNodeAddedRemoved::P_NODE] = selectedNode_;
+                editData[SceneEditNodeAddedRemoved::P_ADDED] = false;
+                scene_->SendEvent(E_SCENEEDITNODEADDEDREMOVED, editData);
+
                 selectedNode_->Remove();
                 selectedNode_ = 0;
             }
@@ -138,13 +149,29 @@ bool SceneEditor3D::OnEvent(const TBWidgetEvent &ev)
                 VariantMap eventData;
                 eventData[EditorActiveNodeChange::P_NODE] = pasteNode;
                 SendEvent(E_EDITORACTIVENODECHANGE, eventData);
+
+                VariantMap editData;
+                editData[SceneEditNodeAddedRemoved::P_SCENE] = scene_;
+                editData[SceneEditNodeAddedRemoved::P_NODE] = pasteNode;
+                editData[SceneEditNodeAddedRemoved::P_ADDED] = true;
+
+                scene_->SendEvent(E_SCENEEDITNODEADDEDREMOVED, editData);
             }
-        } 
+        }
         else if (ev.ref_id == TBIDC("close"))
         {
-            //Don't check for unsaved changes yet
-            Close();
-            //RequestClose();
+            RequestClose();
+            return true;
+        }
+        else if (ev.ref_id == TBIDC("undo"))
+        {
+            Undo();
+            return true;
+        }
+        else if (ev.ref_id == TBIDC("redo"))
+        {
+            Redo();
+            return true;
         }
     }
 
@@ -191,6 +218,15 @@ void SceneEditor3D::SelectNode(Node* node)
 
 }
 
+void SceneEditor3D::HandleNodeAdded(StringHash eventType, VariantMap& eventData)
+{
+    // Node does not have values set here
+
+    //Node* node =  static_cast<Node*>(eventData[NodeAdded::P_NODE].GetPtr());
+    //LOGINFOF("Node Added: %s", node->GetName().CString());
+}
+
+
 void SceneEditor3D::HandleNodeRemoved(StringHash eventType, VariantMap& eventData)
 {
     Node* node = (Node*) (eventData[NodeRemoved::P_NODE].GetPtr());
@@ -198,7 +234,6 @@ void SceneEditor3D::HandleNodeRemoved(StringHash eventType, VariantMap& eventDat
         SelectNode(0);
 }
 
-
 void SceneEditor3D::HandleUpdate(StringHash eventType, VariantMap& eventData)
 {
     Vector<Node*> editNodes;
@@ -263,4 +298,19 @@ bool SceneEditor3D::Save()
 
 }
 
+void SceneEditor3D::Undo()
+{
+    editHistory_->Undo();
+}
+
+void SceneEditor3D::Redo()
+{
+    editHistory_->Redo();
+}
+
+void SceneEditor3D::HandleSceneEditSceneModified(StringHash eventType, VariantMap& eventData)
+{
+    SetModified(true);    
+}
+
 }

+ 11 - 0
Source/AtomicEditor/Editors/SceneEditor3D/SceneEditor3D.h

@@ -30,6 +30,8 @@ class Octree;
 namespace AtomicEditor
 {
 
+class SceneEditHistory;
+
 class SceneEditor3D: public ResourceEditor
 {
     OBJECT(SceneEditor3D);
@@ -54,6 +56,9 @@ public:
     void Close(bool navigateToAvailableResource = true);
     bool Save();
 
+    void Undo();
+    void Redo();
+
 private:
 
     void HandleUpdate(StringHash eventType, VariantMap& eventData);
@@ -62,8 +67,12 @@ private:
     void HandlePlayStopped(StringHash eventType, VariantMap& eventData);
     void HandleGizmoEditModeChanged(StringHash eventType, VariantMap& eventData);
     void HandleGizmoAxisModeChanged(StringHash eventType, VariantMap& eventData);
+
+    void HandleNodeAdded(StringHash eventType, VariantMap& eventData);
     void HandleNodeRemoved(StringHash eventType, VariantMap& eventData);
 
+    void HandleSceneEditSceneModified(StringHash eventType, VariantMap& eventData);
+
     SharedPtr<Scene> scene_;
 
     // TODO: multiple views
@@ -74,6 +83,8 @@ private:
     WeakPtr<Node> selectedNode_;
     SharedPtr<Node> clipboardNode_;
 
+    SharedPtr<SceneEditHistory> editHistory_;
+
 };
 
 }

+ 33 - 0
Source/AtomicEditor/Editors/SceneEditor3D/SceneEditor3DEvents.h

@@ -28,4 +28,37 @@ EVENT(E_GIZMOMOVED, GizmoMoved)
 
 }
 
+EVENT(E_SCENEEDITSCENEMODIFIED, SceneEditSceneModified)
+{
+
+}
+
+EVENT(E_SCENEEDITNODEADDEDREMOVED, SceneEditNodeAddedRemoved)
+{
+    PARAM(P_SCENE, Scene);             // Scene
+    PARAM(P_NODE, Node);               // Node
+    PARAM(P_ADDED, Added);             // bool
+}
+
+EVENT(E_SCENEEDITCOMPONENTADDEDREMOVED, SceneEditComponentAddedRemoved)
+{
+    PARAM(P_SCENE, Scene);             // Scene
+    PARAM(P_COMPONENT, Component);     // Component
+    PARAM(P_ADDED, Added);             // bool
+}
+
+EVENT(E_SCENEEDITSERIALIZABLE, SceneEditSerializable)
+{
+    PARAM(P_SERIALIZABLE, Serializable);     // Serializable
+    PARAM(P_OPERATION, Operation);           // int (0: begin, 1: change, 2: end)
+}
+
+EVENT(E_SCENEEDITSERIALIZABLEUNDOREDO, SceneEditSerializableUndoRedo)
+{
+    PARAM(P_SERIALIZABLE, Serializable);     // Serializable
+    PARAM(P_STATE, State);                   // State (VectorBuffer);
+    PARAM(P_UNDO, Undo);                     // bool (true: undo, false: redo)
+}
+
+
 }

+ 40 - 1
Source/AtomicEditor/Editors/SceneEditor3D/SceneView3D.cpp

@@ -44,6 +44,7 @@
 
 #include "SceneView3D.h"
 #include "SceneEditor3D.h"
+#include "SceneEditor3DEvents.h"
 
 using namespace ToolCore;
 
@@ -106,8 +107,10 @@ SceneView3D ::SceneView3D(Context* context, SceneEditor3D *sceneEditor) :
     SubscribeToEvent(this, E_DRAGEXITWIDGET, HANDLER(SceneView3D, HandleDragExitWidget));
     SubscribeToEvent(this, E_DRAGENDED, HANDLER(SceneView3D, HandleDragEnded));
 
-    SetIsFocusable(true);
+    SubscribeToEvent(E_UIUNHANDLEDSHORTCUT, HANDLER(SceneView3D, HandleUIUnhandledShortcut));
+    SubscribeToEvent(E_UIWIDGETFOCUSESCAPED, HANDLER(SceneView3D, HandleUIWidgetFocusEscaped));
 
+    SetIsFocusable(true);
 
 }
 
@@ -260,6 +263,29 @@ bool SceneView3D::MouseInView()
 
 }
 
+void SceneView3D::HandleUIUnhandledShortcut(StringHash eventType, VariantMap& eventData)
+{
+    if (!enabled_)
+        return;
+
+    unsigned id = eventData[UIUnhandledShortcut::P_REFID].GetUInt();
+
+    if (id == TBIDC("undo"))
+        sceneEditor_->Undo();
+    else if (id == TBIDC("redo"))
+        sceneEditor_->Redo();
+
+    return;
+
+}
+
+void SceneView3D::HandleUIWidgetFocusEscaped(StringHash eventType, VariantMap& eventData)
+{
+    if (!enabled_)
+        return;
+
+    SetFocus();
+}
 
 void SceneView3D::HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData)
 {
@@ -364,6 +390,12 @@ void SceneView3D::SelectNode(Node* node)
 
 bool SceneView3D::OnEvent(const TBWidgetEvent &ev)
 {
+    if (ev.type == EVENT_TYPE_SHORTCUT)
+    {
+        if (ev.ref_id == TBIDC("close"))
+            return false;
+    }
+
     return sceneEditor_->OnEvent(ev);
 }
 
@@ -516,6 +548,13 @@ void SceneView3D::HandleDragEnded(StringHash eventType, VariantMap& eventData)
         VariantMap neventData;
         neventData[EditorActiveNodeChange::P_NODE] = dragNode_;
         SendEvent(E_EDITORACTIVENODECHANGE, neventData);
+
+        VariantMap editData;
+        editData[SceneEditNodeAddedRemoved::P_SCENE] = scene_;
+        editData[SceneEditNodeAddedRemoved::P_NODE] = dragNode_;
+        editData[SceneEditNodeAddedRemoved::P_ADDED] = true;
+        scene_->SendEvent(E_SCENEEDITNODEADDEDREMOVED, editData);
+
     }
 
     if (dragObject && dragObject->GetObject()->GetType() == ToolCore::Asset::GetTypeStatic())

+ 3 - 0
Source/AtomicEditor/Editors/SceneEditor3D/SceneView3D.h

@@ -67,6 +67,9 @@ private:
     void HandleEditorActiveNodeChange(StringHash eventType, VariantMap& eventData);
     void HandleNodeRemoved(StringHash eventType, VariantMap& eventData);
 
+    void HandleUIWidgetFocusEscaped(StringHash eventType, VariantMap& eventData);
+    void HandleUIUnhandledShortcut(StringHash eventType, VariantMap& eventData);
+
     void DrawNodeDebug(Node* node, DebugRenderer* debug, bool drawNode = true);
 
     void MoveCamera(float timeStep);

+ 21 - 0
Source/ThirdParty/TurboBadger/tb_editfield.cpp

@@ -357,6 +357,27 @@ void TBEditField::OnFocusChanged(bool focused)
 {
 	m_style_edit.Focus(focused);
 
+    if (focused)
+    {
+        if (!m_style_edit.packed.styling_on)
+            GetText(m_initial_edit_text);
+    }
+    else
+    {
+        if (!m_style_edit.packed.styling_on)
+        {
+            TBStr curText;
+            GetText(curText);
+            if (!curText.Equals(m_initial_edit_text))
+            {
+                TBWidgetEvent ev(EVENT_TYPE_CUSTOM);
+                ev.ref_id = TBIDC("edit_complete");
+                // forward to delegate
+                TBWidget::OnEvent(ev);
+            }
+        }
+    }
+
     TBWidget::OnFocusChanged(focused);
 }
 

+ 2 - 0
Source/ThirdParty/TurboBadger/tb_editfield.h

@@ -190,6 +190,8 @@ private:
 	int m_virtual_width;
 	void UpdateScrollbarVisibility(bool multiline);
 
+    TBStr m_initial_edit_text;
+
 	// == TBStyleEditListener =======================
 	virtual void OnChange();
 	virtual bool OnEnter();

+ 57 - 3
Source/ThirdParty/TurboBadger/tb_inline_select.cpp

@@ -18,7 +18,8 @@ namespace tb {
 TBInlineSelect::TBInlineSelect()
 	: m_value(0)
 	, m_min(0)
-	, m_max(100)
+    , m_max(100)
+    , m_modified(false)
 {
 	SetSkinBg(TBIDC("TBInlineSelect"));
 	AddChild(&m_layout);
@@ -41,10 +42,15 @@ TBInlineSelect::TBInlineSelect()
 	m_editfield.SetTextAlign(TB_TEXT_ALIGN_CENTER);
 	m_editfield.SetEditType(EDIT_TYPE_NUMBER);
 	m_editfield.SetText("0");
+
+    m_editfield.AddListener(this);
+
 }
 
 TBInlineSelect::~TBInlineSelect()
 {
+    m_editfield.RemoveListener(this);
+
 	m_layout.RemoveChild(&m_buttons[1]);
 	m_layout.RemoveChild(&m_editfield);
 	m_layout.RemoveChild(&m_buttons[0]);
@@ -87,8 +93,8 @@ void TBInlineSelect::SetValueInternal(double value, bool update_text)
 		m_editfield.SetText(strval);
 	}
 
-	TBWidgetEvent ev(EVENT_TYPE_CHANGED);
-	InvokeEvent(ev);
+    TBWidgetEvent ev(EVENT_TYPE_CHANGED);
+    InvokeEvent(ev);
 
 	// Warning: Do nothing here since the event might have deleted us.
 	//          If needed, check if we are alive using a safe pointer first.
@@ -126,7 +132,55 @@ bool TBInlineSelect::OnEvent(const TBWidgetEvent &ev)
 		m_editfield.GetText(text);
         SetValueInternal((double) atof(text), false);
 	}
+
+    // catch mouse up/mouse down on buttons for modifications
+    if (ev.target == &m_buttons[0] || ev.target == &m_buttons[1])
+    {
+        if (ev.type == EVENT_TYPE_POINTER_DOWN)
+        {
+            m_modified = true;
+        }
+        else if (ev.type == EVENT_TYPE_POINTER_UP)
+        {
+            if (m_modified)
+                InvokeModifiedEvent();
+        }
+    }
+
 	return false;
 }
 
+void TBInlineSelect::InvokeModifiedEvent()
+{
+    TBWidgetEvent ev(EVENT_TYPE_CUSTOM);
+    ev.ref_id = TBIDC("edit_complete");
+    // forward to delegate
+    TBWidget::OnEvent(ev);
+    m_modified = false;
+    m_editfield.GetText(m_initial_edit_value);
+}
+
+bool TBInlineSelect::OnWidgetInvokeEvent(TBWidget *widget, const TBWidgetEvent &ev)
+{
+    return false;
+}
+
+void TBInlineSelect::OnWidgetFocusChanged(TBWidget *widget, bool focused)
+{
+    if (widget == &m_editfield)
+    {
+        if (focused)
+            m_editfield.GetText(m_initial_edit_value);
+        else
+        {
+            TBStr editvalue;
+            m_editfield.GetText(editvalue);
+            if (m_modified || !editvalue.Equals(m_initial_edit_value.CStr()))
+            {
+                InvokeModifiedEvent();
+            }
+        }
+    }
+}
+
 }; // namespace tb

+ 10 - 1
Source/ThirdParty/TurboBadger/tb_inline_select.h

@@ -19,7 +19,7 @@ namespace tb {
 	FIX: Should also be possible to set a list of strings that will be
 		shown instead of numbers.
 */
-class TBInlineSelect : public TBWidget
+class TBInlineSelect : public TBWidget, private TBWidgetListener
 {
 public:
 	// For safe typecasting
@@ -53,8 +53,17 @@ protected:
 	TBEditField m_editfield;
     double m_value;
     double m_min, m_max;
+    bool m_modified;
 
     void SetValueInternal(double value, bool update_text);
+
+private:
+
+    TBStr m_initial_edit_value;
+
+    void InvokeModifiedEvent();
+    void OnWidgetFocusChanged(TBWidget *widget, bool focused);
+    bool OnWidgetInvokeEvent(TBWidget *widget, const TBWidgetEvent &ev);
 };
 
 }; // namespace tb