Josh Engebretson 10 лет назад
Родитель
Сommit
fb22420af7

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

@@ -18,7 +18,6 @@ class ComponentInspector extends Atomic.UISection {
         super();
         super();
 
 
         this.subscribeToEvent("WidgetEvent", (data) => this.handleWidgetEvent(data));
         this.subscribeToEvent("WidgetEvent", (data) => this.handleWidgetEvent(data));
-
     }
     }
 
 
     handleWidgetEvent(ev: Atomic.UIWidgetEvent) {
     handleWidgetEvent(ev: Atomic.UIWidgetEvent) {
@@ -96,6 +95,8 @@ class ComponentInspector extends Atomic.UISection {
         this.component = component;
         this.component = component;
         this.text = component.typeName;
         this.text = component.typeName;
 
 
+        this.subscribeToEvent(component, "HistoryComponentChangedUndoRedo", (ev) => this.handleHistoryComponentChangedUndoRedo(ev));
+
         // For JSComponents append the filename
         // For JSComponents append the filename
         if (component.typeName == "JSComponent") {
         if (component.typeName == "JSComponent") {
 
 
@@ -587,6 +588,23 @@ class ComponentInspector extends Atomic.UISection {
         }
         }
     }
     }
 
 
+    handleHistoryComponentChangedUndoRedo(ev) {
+
+      for (var i in this.bindings) {
+          this.bindings[i].objectLocked = true;
+      }
+
+      for (var i in this.bindings) {
+          this.bindings[i].setWidgetValueFromObject();
+      }
+
+      for (var i in this.bindings) {
+          this.bindings[i].objectLocked = false;
+      }
+
+    }
+
+
     component: Atomic.Component;
     component: Atomic.Component;
     bindings: Array<DataBinding> = new Array();
     bindings: Array<DataBinding> = new Array();
 
 

+ 69 - 5
Script/AtomicEditor/ui/frames/inspector/DataBinding.ts

@@ -26,6 +26,8 @@ class DataBinding {
         fd.id = "Vera";
         fd.id = "Vera";
         fd.size = 11;
         fd.size = 11;
 
 
+        var editFields: Array<Atomic.UIEditField> = [];
+
         var enumSource = null;
         var enumSource = null;
 
 
         if (attrInfo.type == Atomic.VAR_BOOL) {
         if (attrInfo.type == Atomic.VAR_BOOL) {
@@ -66,6 +68,8 @@ class DataBinding {
                 lp.width = 140;
                 lp.width = 140;
                 field.layoutParams = lp;
                 field.layoutParams = lp;
 
 
+                editFields.push(field);
+
                 widget = field;
                 widget = field;
             }
             }
 
 
@@ -80,6 +84,8 @@ class DataBinding {
             lp.width = 140;
             lp.width = 140;
             field.layoutParams = lp;
             field.layoutParams = lp;
 
 
+            editFields.push(field);
+
             widget = field;
             widget = field;
 
 
         }
         }
@@ -93,6 +99,8 @@ class DataBinding {
             lp.width = 140;
             lp.width = 140;
             field.layoutParams = lp;
             field.layoutParams = lp;
 
 
+            editFields.push(field);
+
             widget = field;
             widget = field;
         }
         }
         else if (attrInfo.type == Atomic.VAR_VECTOR3 || attrInfo.type == Atomic.VAR_QUATERNION) {
         else if (attrInfo.type == Atomic.VAR_VECTOR3 || attrInfo.type == Atomic.VAR_QUATERNION) {
@@ -135,6 +143,7 @@ class DataBinding {
             }
             }
 
 
         } else if (attrInfo.type == Atomic.VAR_VECTOR2) {
         } else if (attrInfo.type == Atomic.VAR_VECTOR2) {
+
             var layout = new Atomic.UILayout();
             var layout = new Atomic.UILayout();
             widget = layout;
             widget = layout;
             layout.spacing = 0;
             layout.spacing = 0;
@@ -168,7 +177,6 @@ class DataBinding {
                 parent.gravity = Atomic.UI_GRAVITY_LEFT_RIGHT;
                 parent.gravity = Atomic.UI_GRAVITY_LEFT_RIGHT;
                 parent.layoutDistribution = Atomic.UI_LAYOUT_DISTRIBUTION_GRAVITY;
                 parent.layoutDistribution = Atomic.UI_LAYOUT_DISTRIBUTION_GRAVITY;
 
 
-
                 var lp = new Atomic.UILayoutParams();
                 var lp = new Atomic.UILayoutParams();
                 lp.width = 140;
                 lp.width = 140;
                 o.editField.layoutParams = lp;
                 o.editField.layoutParams = lp;
@@ -214,7 +222,7 @@ class DataBinding {
 
 
                         if (dragObject.object && dragObject.object.typeName == "Asset") {
                         if (dragObject.object && dragObject.object.typeName == "Asset") {
 
 
-                            var asset = <ToolCore.Asset> dragObject.object;
+                            var asset = <ToolCore.Asset>dragObject.object;
 
 
                             if (asset.importerTypeName == importerName) {
                             if (asset.importerTypeName == importerName) {
                                 importer = asset.importer;
                                 importer = asset.importer;
@@ -250,6 +258,11 @@ class DataBinding {
 
 
             var binding = new DataBinding(object, attrInfo, widget);
             var binding = new DataBinding(object, attrInfo, widget);
             binding.enumSource = enumSource;
             binding.enumSource = enumSource;
+
+            for (var i in editFields) {
+                binding.subscribeToFocusChange(field);
+            }
+
             return binding;
             return binding;
 
 
         }
         }
@@ -259,6 +272,7 @@ class DataBinding {
     }
     }
 
 
     setWidgetValueFromObject() {
     setWidgetValueFromObject() {
+
         if (this.widgetLocked)
         if (this.widgetLocked)
             return;
             return;
 
 
@@ -340,13 +354,13 @@ class DataBinding {
         } else if (attrInfo.type == Atomic.VAR_RESOURCEREF && attrInfo.resourceTypeName) {
         } else if (attrInfo.type == Atomic.VAR_RESOURCEREF && attrInfo.resourceTypeName) {
 
 
             // for cached resources, use the asset name, otherwise use the resource path name
             // 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 = "";
             var text = "";
             if (resource) {
             if (resource) {
                 text = resource.name;
                 text = resource.name;
                 var asset = ToolCore.assetDatabase.getAssetByCachePath(resource.name);
                 var asset = ToolCore.assetDatabase.getAssetByCachePath(resource.name);
                 if (asset)
                 if (asset)
-                  text = asset.name;
+                    text = asset.name;
             }
             }
 
 
             widget["editField"].text = text;
             widget["editField"].text = text;
@@ -445,6 +459,37 @@ class DataBinding {
 
 
     }
     }
 
 
+    subscribeToFocusChange(widget: Atomic.UIWidget) {
+
+        widget.subscribeToEvent(widget, "UIWidgetFocusChanged", (ev) => this.handleUIWidgetFocusChangedEvent(ev));
+
+    }
+
+    handleUIWidgetFocusChangedEvent(ev: Atomic.UIWidgetFocusChangedEvent) {
+
+        // does our object have a scene property? (Node/Component)
+        var scene = this.object["scene"];
+
+        if (!scene)
+            return;
+
+        if (ev.focused) {
+
+            // gaining focus, save initial value
+            this.initalEditValue = ev.widget.text;
+
+        } else {
+
+            if (this.initalEditValue != ev.widget.text) {
+
+                // focus changed, with value change record in history
+                this.createHistorySnapshot();
+
+            }
+
+        }
+
+    }
 
 
     handleWidgetEvent(ev: Atomic.UIWidgetEvent): boolean {
     handleWidgetEvent(ev: Atomic.UIWidgetEvent): boolean {
 
 
@@ -459,6 +504,7 @@ class DataBinding {
 
 
                 this.object.setAttribute(this.attrInfo.name, Number(ev.refid) - 1);
                 this.object.setAttribute(this.attrInfo.name, Number(ev.refid) - 1);
                 this.setWidgetValueFromObject();
                 this.setWidgetValueFromObject();
+                this.createHistorySnapshot();
 
 
             }
             }
 
 
@@ -474,13 +520,19 @@ class DataBinding {
 
 
             }
             }
 
 
-
         }
         }
 
 
         if (ev.type == Atomic.UI_EVENT_TYPE_CHANGED) {
         if (ev.type == Atomic.UI_EVENT_TYPE_CHANGED) {
+
             if (this.widget == ev.target || this.widget.isAncestorOf(ev.target)) {
             if (this.widget == ev.target || this.widget.isAncestorOf(ev.target)) {
                 //EditorUI.getCurrentResourceEditor().setModified(true);
                 //EditorUI.getCurrentResourceEditor().setModified(true);
                 this.setObjectValueFromWidget(ev.target);
                 this.setObjectValueFromWidget(ev.target);
+
+                // create a history snapshot, UIEditFields handle this when losing focus
+                if (ev.target.getTypeName() != "UIEditField") {
+                    this.createHistorySnapshot();
+                }
+
                 return true;
                 return true;
             }
             }
         }
         }
@@ -489,6 +541,16 @@ class DataBinding {
 
 
     }
     }
 
 
+    createHistorySnapshot() {
+
+        if (this.object.getTypeName() == "Node") {
+            this.object.sendEvent("HistoryNodeChanged", { scene: this.object["scene"], node: this.object });
+        } else if (this.object["node"]) { // TODO: need better component detection
+            this.object.sendEvent("HistoryComponentChanged", { scene: this.object["scene"], component: this.object });
+        }
+
+    }
+
     object: Atomic.Serializable;
     object: Atomic.Serializable;
     objectLocked: boolean = true;
     objectLocked: boolean = true;
     widgetLocked: boolean = false;
     widgetLocked: boolean = false;
@@ -496,6 +558,8 @@ class DataBinding {
     widget: Atomic.UIWidget;
     widget: Atomic.UIWidget;
     enumSource: Atomic.UISelectItemSource;
     enumSource: Atomic.UISelectItemSource;
 
 
+    initalEditValue: string;
+
 }
 }
 
 
 export = DataBinding;
 export = DataBinding;

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

@@ -120,6 +120,8 @@ class NodeInspector extends ScriptWidget {
 
 
         this.bindings = new Array();
         this.bindings = new Array();
 
 
+        this.subscribeToEvent(node, "HistoryNodeChangedUndoRedo", (ev) => this.handleHistoryNodeChangedUndoRedo(ev));        
+
         this.node = node;
         this.node = node;
 
 
         this.isPrefab = this.detectPrefab(node);
         this.isPrefab = this.detectPrefab(node);
@@ -314,6 +316,22 @@ class NodeInspector extends ScriptWidget {
 
 
     }
     }
 
 
+    handleHistoryNodeChangedUndoRedo(ev) {
+
+      for (var i in this.bindings) {
+          this.bindings[i].objectLocked = true;
+      }
+
+      for (var i in this.bindings) {
+          this.bindings[i].setWidgetValueFromObject();
+      }
+
+      for (var i in this.bindings) {
+          this.bindings[i].objectLocked = false;
+      }
+
+    }
+
     isPrefab: boolean;
     isPrefab: boolean;
     node: Atomic.Node;
     node: Atomic.Node;
     nodeLayout: Atomic.UILayout;
     nodeLayout: Atomic.UILayout;

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

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

+ 12 - 0
Source/Atomic/UI/UI.cpp

@@ -750,6 +750,18 @@ bool UI::OnWidgetDying(tb::TBWidget *widget)
     return false;
     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)
 void UI::ShowDebugHud(bool value)
 {
 {
     SystemUI::DebugHud* hud = GetSubsystem<SystemUI::DebugHud>();
     SystemUI::DebugHud* hud = GetSubsystem<SystemUI::DebugHud>();

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

@@ -119,7 +119,9 @@ private:
 
 
     // TBWidgetListener
     // TBWidgetListener
     void OnWidgetDelete(tb::TBWidget *widget);
     void OnWidgetDelete(tb::TBWidget *widget);
-    bool OnWidgetDying(tb::TBWidget *widget);
+    bool OnWidgetDying(tb::TBWidget *widget);    
+    void OnWidgetFocusChanged(tb::TBWidget *widget, bool focused);
+
 
 
     tb::TBWidget* rootWidget_;
     tb::TBWidget* rootWidget_;
     UIRenderer* renderer_;
     UIRenderer* renderer_;

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

@@ -103,4 +103,11 @@ EVENT(E_UISHORTCUT, UIShortcut)
 
 
 }
 }
 
 
+EVENT(E_UIWIDGETFOCUSCHANGED, UIWidgetFocusChanged)
+{
+    PARAM(P_WIDGET, Widget);             // UIWidget pointer
+    PARAM(P_FOCUSED, Focused);             // bool
+}
+
+
 }
 }

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

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

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

@@ -0,0 +1,236 @@
+
+#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),
+    locked_(false)
+{
+    SubscribeToEvent(E_HISTORYNODEADDED, HANDLER(SceneEditHistory, HandleHistoryNodeAdded));
+    SubscribeToEvent(E_HISTORYNODEREMOVED, HANDLER(SceneEditHistory, HandleHistoryNodeRemoved));
+    SubscribeToEvent(E_HISTORYNODECHANGED, HANDLER(SceneEditHistory, HandleHistoryNodeChanged));
+
+    SubscribeToEvent(E_HISTORYCOMPONENTCHANGED, HANDLER(SceneEditHistory, HandleHistoryComponentChanged));
+
+    SubscribeToEvent(E_EDITORACTIVENODECHANGE, HANDLER(SceneEditHistory, HandleEditorActiveNodeChange));
+}
+
+SceneEditHistory::~SceneEditHistory()
+{
+
+}
+
+void SceneEditHistory::AddUndoOp(SceneHistoryOp* op)
+{
+    undoHistory_.Push(op);
+
+    // adding a new undo op invalids redos
+    for (unsigned i = 0; i < redoHistory_.Size(); i++)
+    {
+        delete redoHistory_[i];
+    }
+
+    if (activeNode_.NotNull())
+    {
+        if (op->type_ == SCENEHISTORYOP_NODECHANGED)
+        {
+            activeNodeCurrentState_.Clear();
+            activeNode_->Animatable::Save(activeNodeCurrentState_);
+            activeNodeCurrentState_.Seek(0);
+        }
+
+        if (op->type_ == SCENEHISTORYOP_COMPONENTCHANGED)
+        {
+            ComponentChangedOp* ccop = (ComponentChangedOp*) op;
+            currentComponentStates_[ccop->component_].Clear();
+            ccop->component_->Animatable::Save(currentComponentStates_[ccop->component_]);
+            currentComponentStates_[ccop->component_].Seek(0);
+        }
+    }
+
+    redoHistory_.Clear();
+}
+
+void SceneEditHistory::HandleHistoryNodeAdded(StringHash eventType, VariantMap& eventData)
+{
+    if (locked_)
+        return;
+
+    Scene* scene = static_cast<Scene*>(eventData[HistoryNodeAdded::P_SCENE].GetPtr());
+    if (scene != scene_)
+        return;
+
+    Node* node = static_cast<Node*>(eventData[HistoryNodeAdded::P_NODE].GetPtr());
+
+    NodeAddedOp* op = new NodeAddedOp(node);
+    AddUndoOp(op);
+}
+
+void SceneEditHistory::HandleHistoryNodeChanged(StringHash eventType, VariantMap& eventData)
+{
+    if (locked_)
+        return;
+
+    Scene* scene = static_cast<Scene*>(eventData[HistoryNodeChanged::P_SCENE].GetPtr());
+    if (scene != scene_)
+        return;
+
+    Node* node = static_cast<Node*>(eventData[HistoryNodeChanged::P_NODE].GetPtr());
+
+    NodeChangedOp* op = new NodeChangedOp(node, activeNodeCurrentState_);
+
+    if (activeNodeCurrentState_.GetSize() != op->newState_.GetSize() ||
+            memcmp(activeNodeCurrentState_.GetData(), op->newState_.GetData(), op->newState_.GetSize()))
+    {
+        activeNodeCurrentState_ = op->newState_;
+        AddUndoOp(op);
+    }
+    else
+    {
+        delete op;
+    }
+
+}
+
+void SceneEditHistory::HandleHistoryNodeRemoved(StringHash eventType, VariantMap& eventData)
+{
+    if (locked_)
+        return;
+}
+
+void SceneEditHistory::HandleEditorActiveNodeChange(StringHash eventType, VariantMap& eventData)
+{
+    Node* node = static_cast<Node*>(eventData[EditorActiveNodeChange::P_NODE].GetPtr());
+
+    if (activeNode_ == node)
+        return;
+
+    activeNode_ = node;
+    activeNodeCurrentState_.Clear();
+    currentComponentStates_.Clear();
+
+    if (activeNode_.Null())
+        return;
+
+    node->Animatable::Save(activeNodeCurrentState_);
+    activeNodeCurrentState_.Seek(0);
+
+    const Vector<SharedPtr<Component> >& comps = activeNode_->GetComponents();
+    for (unsigned i = 0; i < comps.Size(); i++)
+    {
+        comps[i]->Animatable::Save(currentComponentStates_[comps[i]]);
+        currentComponentStates_[comps[i]].Seek(0);
+    }
+}
+
+void SceneEditHistory::HandleHistoryComponentChanged(StringHash eventType, VariantMap& eventData)
+{
+    if (locked_)
+        return;
+
+    Component* component = static_cast<Component*>(eventData[HistoryComponentChanged::P_COMPONENT].GetPtr());
+
+    if (!component || component->GetNode() != activeNode_)
+        return;
+
+    assert(currentComponentStates_.Contains(component));
+
+    ComponentChangedOp* op = new ComponentChangedOp(component, currentComponentStates_[component]);
+
+    if (currentComponentStates_[component].GetSize() != op->newState_.GetSize() ||
+            memcmp(currentComponentStates_[component].GetData(), op->newState_.GetData(), op->newState_.GetSize()))
+    {
+        activeNodeCurrentState_ = op->newState_;
+        AddUndoOp(op);
+    }
+    else
+    {
+        delete op;
+    }
+
+
+}
+
+void SceneEditHistory::BeginUndoRedo()
+{
+    //VariantMap evData;
+    //evData[SceneUndoRedoBegin::P_SCENE] = scene_;
+    //SendEvent(E_SCENEUNDOREDOBEGIN, evData);
+}
+
+void SceneEditHistory::EndUndoRedo()
+{
+    //VariantMap evData;
+    //evData[SceneUndoRedoEnd::P_SCENE] = scene_;
+    //SendEvent(E_SCENEUNDOREDOEND, evData);
+}
+
+void SceneEditHistory::Undo()
+{
+    if (!undoHistory_.Size())
+        return;
+
+    SceneHistoryOp* op = undoHistory_.Back();
+    undoHistory_.Pop();
+
+    op->Undo();
+
+    VariantMap eventData;
+    if (op->type_ == SCENEHISTORYOP_NODECHANGED)
+    {
+        NodeChangedOp* ncop = (NodeChangedOp*) op;
+        eventData[HistoryNodeChangedUndoRedo::P_NODE] = ncop->node_;
+        ncop->node_->SendEvent(E_HISTORYNODECHANGEDUNDOREDO, eventData);
+    }
+    else if (op->type_ == SCENEHISTORYOP_COMPONENTCHANGED)
+    {
+        ComponentChangedOp* ccop = (ComponentChangedOp*) op;
+        eventData[HistoryComponentChangedUndoRedo::P_COMPONENT] = ccop->component_;
+        ccop->component_->SendEvent(E_HISTORYCOMPONENTCHANGEDUNDOREDO, eventData);
+    }
+
+    redoHistory_.Push(op);
+}
+
+void SceneEditHistory::Redo()
+{
+    if (!redoHistory_.Size())
+        return;
+
+    SceneHistoryOp* op = redoHistory_.Back();
+    redoHistory_.Pop();
+
+    op->Redo();
+
+    VariantMap eventData;
+    if (op->type_ == SCENEHISTORYOP_NODECHANGED)
+    {
+        NodeChangedOp* ncop = (NodeChangedOp*) op;
+        eventData[HistoryNodeChangedUndoRedo::P_NODE] = ncop->node_;
+        ncop->node_->SendEvent(E_HISTORYNODECHANGEDUNDOREDO, eventData);
+    }
+    else if (op->type_ == SCENEHISTORYOP_COMPONENTCHANGED)
+    {
+        ComponentChangedOp* ccop = (ComponentChangedOp*) op;
+        eventData[HistoryComponentChangedUndoRedo::P_COMPONENT] = ccop->component_;
+        ccop->component_->SendEvent(E_HISTORYCOMPONENTCHANGEDUNDOREDO, eventData);
+    }
+
+    undoHistory_.Push(op);
+
+}
+
+}

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

@@ -0,0 +1,72 @@
+//
+// 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 Scene;
+class Node;
+class Component;
+
+}
+
+namespace AtomicEditor
+{
+
+class SceneHistoryOp;
+
+/// 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();
+
+    void Lock() { locked_ = true; }
+    void Unlock() { locked_ = false; }
+
+private:
+
+    void HandleEditorActiveNodeChange(StringHash eventType, VariantMap& eventData);
+
+    void HandleHistoryNodeAdded(StringHash eventType, VariantMap& eventData);
+    void HandleHistoryNodeRemoved(StringHash eventType, VariantMap& eventData);
+    void HandleHistoryNodeChanged(StringHash eventType, VariantMap& eventData);
+
+    void HandleHistoryComponentChanged(StringHash eventType, VariantMap& eventData);
+
+
+    void AddUndoOp(SceneHistoryOp* op);
+
+    void BeginUndoRedo();
+    void EndUndoRedo();
+
+    WeakPtr<Scene> scene_;
+
+    WeakPtr<Node> activeNode_;
+    VectorBuffer activeNodeCurrentState_;
+
+    HashMap<Component*, VectorBuffer> currentComponentStates_;
+
+    bool locked_;
+    PODVector<SceneHistoryOp*> undoHistory_;
+    PODVector<SceneHistoryOp*> redoHistory_;
+
+};
+
+}

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

@@ -0,0 +1,92 @@
+
+#include <Atomic/Scene/Node.h>
+#include <Atomic/Scene/Component.h>
+
+#include "SceneEditOps.h"
+
+namespace AtomicEditor
+{
+
+NodeEditOp::NodeEditOp(Node* node)
+{
+    node_ = node;
+
+    // parent at the time of the operation
+    parent_ = node->GetParent();
+}
+
+NodeAddedOp::NodeAddedOp(Node* node) : NodeEditOp(node)
+{
+    type_ = SCENEHISTORYOP_NODEADDED;
+
+}
+
+bool NodeAddedOp::Undo()
+{
+    node_->Remove();
+    return true;
+}
+
+bool NodeAddedOp::Redo()
+{
+    parent_->AddChild(node_);
+    return true;
+}
+
+NodeChangedOp::NodeChangedOp(Node* node, const VectorBuffer& prevState) : NodeEditOp(node)
+{
+    type_ = SCENEHISTORYOP_NODECHANGED;
+
+    prevState_ = prevState;
+    node->Animatable::Save(newState_);
+    newState_.Seek(0);
+}
+
+bool NodeChangedOp::Undo()
+{
+    node_->Animatable::Load(prevState_);
+    prevState_.Seek(0);
+    return true;
+}
+
+bool NodeChangedOp::Redo()
+{
+    node_->Animatable::Load(newState_);
+    newState_.Seek(0);
+    return true;
+}
+
+//------------ Component Ops
+
+ComponentEditOp::ComponentEditOp(Component* component)
+{
+    component_ = component;
+
+}
+
+ComponentChangedOp::ComponentChangedOp(Component* component, const VectorBuffer& prevState) : ComponentEditOp(component)
+{
+    type_ = SCENEHISTORYOP_COMPONENTCHANGED;
+
+    prevState_ = prevState;
+    component_->Animatable::Save(newState_);
+    newState_.Seek(0);
+}
+
+bool ComponentChangedOp::Undo()
+{
+    component_->Animatable::Load(prevState_);
+    prevState_.Seek(0);
+    return true;
+}
+
+bool ComponentChangedOp::Redo()
+{
+    component_->Animatable::Load(newState_);
+    newState_.Seek(0);
+    return true;
+}
+
+
+
+}

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

@@ -0,0 +1,119 @@
+//
+// 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;
+
+}
+
+using namespace Atomic;
+
+namespace AtomicEditor
+{
+
+enum SceneHistoryOpType
+{
+    SCENEHISTORYOP_UNKNOWN = 0,
+    SCENEHISTORYOP_NODEADDED,
+    SCENEHISTORYOP_NODEREMOVED,
+    SCENEHISTORYOP_NODECHANGED,
+
+    SCENEHISTORYOP_COMPONENTCHANGED
+};
+
+class SceneHistoryOp
+{
+
+public:
+
+    SceneHistoryOp() { type_ = SCENEHISTORYOP_UNKNOWN; }
+    virtual ~SceneHistoryOp() { }
+
+    virtual bool Undo() = 0;
+    virtual bool Redo() = 0;
+
+    SceneHistoryOpType type_;
+
+};
+
+class NodeEditOp : public SceneHistoryOp
+{
+protected:
+
+    NodeEditOp(Node* node);
+
+public:
+
+    SharedPtr<Node> node_;
+    SharedPtr<Node> parent_;
+
+};
+
+class NodeAddedOp : public NodeEditOp
+{
+
+public:
+
+    NodeAddedOp(Node* node);
+
+    bool Undo();
+    bool Redo();
+};
+
+class NodeChangedOp : public NodeEditOp
+{
+
+public:
+
+    NodeChangedOp(Node* node, const VectorBuffer& prevState);
+
+    bool Undo();
+    bool Redo();
+
+    VectorBuffer prevState_;
+    VectorBuffer newState_;
+
+};
+
+class ComponentEditOp : public SceneHistoryOp
+{
+protected:
+
+    ComponentEditOp(Component* component);
+
+public:
+
+    SharedPtr<Component> component_;
+
+};
+
+class ComponentChangedOp : public ComponentEditOp
+{
+
+public:
+
+    ComponentChangedOp(Component* component, const VectorBuffer& prevState);
+
+    bool Undo();
+    bool Redo();
+
+    VectorBuffer prevState_;
+    VectorBuffer newState_;
+
+};
+
+
+}

+ 22 - 0
Source/AtomicEditor/Editors/SceneEditor3D/SceneEditor3D.cpp

@@ -29,6 +29,7 @@
 #include "../../EditorMode/AEEditorEvents.h"
 #include "../../EditorMode/AEEditorEvents.h"
 
 
 #include "SceneEditor3D.h"
 #include "SceneEditor3D.h"
+#include "SceneEditHistory.h"
 #include "SceneEditor3DEvents.h"
 #include "SceneEditor3DEvents.h"
 
 
 using namespace ToolCore;
 using namespace ToolCore;
@@ -97,6 +98,8 @@ SceneEditor3D ::SceneEditor3D(Context* context, const String &fullpath, UITabCon
 
 
     SubscribeToEvent(scene_, E_NODEREMOVED, HANDLER(SceneEditor3D, HandleNodeRemoved));
     SubscribeToEvent(scene_, E_NODEREMOVED, HANDLER(SceneEditor3D, HandleNodeRemoved));
 
 
+    editHistory_ = new SceneEditHistory(context_, scene_);
+
 }
 }
 
 
 SceneEditor3D::~SceneEditor3D()
 SceneEditor3D::~SceneEditor3D()
@@ -146,6 +149,14 @@ bool SceneEditor3D::OnEvent(const TBWidgetEvent &ev)
             Close();
             Close();
             //RequestClose();
             //RequestClose();
         }
         }
+        else if (ev.ref_id == TBIDC("undo"))
+        {
+            Undo();
+        }
+        else if (ev.ref_id == TBIDC("redo"))
+        {
+            Redo();
+        }
     }
     }
 
 
     if (ev.type == EVENT_TYPE_CLICK)
     if (ev.type == EVENT_TYPE_CLICK)
@@ -263,4 +274,15 @@ bool SceneEditor3D::Save()
 
 
 }
 }
 
 
+void SceneEditor3D::Undo()
+{
+    editHistory_->Undo();
+}
+
+void SceneEditor3D::Redo()
+{
+    editHistory_->Redo();
+}
+
+
 }
 }

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

@@ -30,6 +30,8 @@ class Octree;
 namespace AtomicEditor
 namespace AtomicEditor
 {
 {
 
 
+class SceneEditHistory;
+
 class SceneEditor3D: public ResourceEditor
 class SceneEditor3D: public ResourceEditor
 {
 {
     OBJECT(SceneEditor3D);
     OBJECT(SceneEditor3D);
@@ -56,6 +58,9 @@ public:
 
 
 private:
 private:
 
 
+    void Undo();
+    void Redo();
+
     void HandleUpdate(StringHash eventType, VariantMap& eventData);
     void HandleUpdate(StringHash eventType, VariantMap& eventData);
     void HandleEditorActiveNodeChange(StringHash eventType, VariantMap& eventData);
     void HandleEditorActiveNodeChange(StringHash eventType, VariantMap& eventData);
     void HandlePlayStarted(StringHash eventType, VariantMap& eventData);
     void HandlePlayStarted(StringHash eventType, VariantMap& eventData);
@@ -74,6 +79,8 @@ private:
     WeakPtr<Node> selectedNode_;
     WeakPtr<Node> selectedNode_;
     SharedPtr<Node> clipboardNode_;
     SharedPtr<Node> clipboardNode_;
 
 
+    SharedPtr<SceneEditHistory> editHistory_;
+
 };
 };
 
 
 }
 }

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

@@ -28,4 +28,42 @@ EVENT(E_GIZMOMOVED, GizmoMoved)
 
 
 }
 }
 
 
+EVENT(E_HISTORYNODEADDED, HistoryNodeAdded)
+{
+    PARAM(P_SCENE, Scene);            // Scene
+    PARAM(P_NODE, Node);            // Node
+}
+
+EVENT(E_HISTORYNODEREMOVED, HistoryNodeRemoved)
+{
+    PARAM(P_SCENE, Scene);            // Scene
+    PARAM(P_NODE, Node);             // Node
+}
+
+EVENT(E_HISTORYNODECHANGED, HistoryNodeChanged)
+{
+    PARAM(P_SCENE, Scene);            // Scene
+    PARAM(P_NODE, Node);              // Node
+}
+
+EVENT(E_HISTORYCOMPONENTCHANGED, HistoryComponentChanged)
+{
+    PARAM(P_SCENE, Scene);            // Scene
+    PARAM(P_COMPONENT, Component);    // Component
+}
+
+EVENT(E_HISTORYNODECHANGEDUNDOREDO, HistoryNodeChangedUndoRedo)
+{
+    PARAM(P_SCENE, Scene);            // Scene
+    PARAM(P_NODE, Node);    // Node
+}
+
+EVENT(E_HISTORYCOMPONENTCHANGEDUNDOREDO, HistoryComponentChangedUndoRedo)
+{
+    PARAM(P_SCENE, Scene);            // Scene
+    PARAM(P_COMPONENT, Component);    // Component
+}
+
+
+
 }
 }

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

@@ -44,6 +44,7 @@
 
 
 #include "SceneView3D.h"
 #include "SceneView3D.h"
 #include "SceneEditor3D.h"
 #include "SceneEditor3D.h"
+#include "SceneEditor3DEvents.h"
 
 
 using namespace ToolCore;
 using namespace ToolCore;
 
 
@@ -108,7 +109,6 @@ SceneView3D ::SceneView3D(Context* context, SceneEditor3D *sceneEditor) :
 
 
     SetIsFocusable(true);
     SetIsFocusable(true);
 
 
-
 }
 }
 
 
 SceneView3D::~SceneView3D()
 SceneView3D::~SceneView3D()
@@ -516,6 +516,11 @@ void SceneView3D::HandleDragEnded(StringHash eventType, VariantMap& eventData)
         VariantMap neventData;
         VariantMap neventData;
         neventData[EditorActiveNodeChange::P_NODE] = dragNode_;
         neventData[EditorActiveNodeChange::P_NODE] = dragNode_;
         SendEvent(E_EDITORACTIVENODECHANGE, neventData);
         SendEvent(E_EDITORACTIVENODECHANGE, neventData);
+
+        VariantMap heventData;
+        heventData[HistoryNodeAdded::P_NODE] = dragNode_;
+        heventData[HistoryNodeAdded::P_SCENE] = scene_;
+        SendEvent(E_HISTORYNODEADDED, heventData);
     }
     }
 
 
     if (dragObject && dragObject->GetObject()->GetType() == ToolCore::Asset::GetTypeStatic())
     if (dragObject && dragObject->GetObject()->GetType() == ToolCore::Asset::GetTypeStatic())