Browse Source

Undo actions for attribute edit, node/component enable and gizmo drag.

Lasse Öörni 12 years ago
parent
commit
1a98b50646

+ 13 - 3
Bin/Data/Scripts/Editor/AttributeEditor.as

@@ -2,7 +2,7 @@
 //
 //
 // Functions that you must implement:
 // Functions that you must implement:
 // - void SetAttributeEditorID(UIElement@ attrEdit, Array<Serializable@>@ serializables);
 // - void SetAttributeEditorID(UIElement@ attrEdit, Array<Serializable@>@ serializables);
-// - void PostEditAttribute(Array<Serializable@>@ serializables, uint index);
+// - void PostEditAttribute(Array<Serializable@>@ serializables, uint index, const Array<Variant>& oldValues);
 // - Array<Serializable@>@ GetAttributeEditorTargets(UIElement@ attrEdit);
 // - Array<Serializable@>@ GetAttributeEditorTargets(UIElement@ attrEdit);
 
 
 const uint MIN_NODE_ATTRIBUTES = 4;
 const uint MIN_NODE_ATTRIBUTES = 4;
@@ -747,12 +747,17 @@ void EditAttribute(StringHash eventType, VariantMap& eventData)
     uint coordinate = attrEdit.vars["Coordinate"].GetUInt();
     uint coordinate = attrEdit.vars["Coordinate"].GetUInt();
     bool intermediateEdit = eventType == TEXT_CHANGED_EVENT_TYPE;
     bool intermediateEdit = eventType == TEXT_CHANGED_EVENT_TYPE;
 
 
+    // Store old values so that PostEditAttribute can create undo actions
+    Array<Variant> oldValues;
+    for (uint i = 0; i < serializables.length; ++i)
+        oldValues.Push(serializables[i].attributes[index]);
+
     StoreAttributeEditor(parent, serializables, index, subIndex, coordinate);
     StoreAttributeEditor(parent, serializables, index, subIndex, coordinate);
     for (uint i = 0; i < serializables.length; ++i)
     for (uint i = 0; i < serializables.length; ++i)
         serializables[i].ApplyAttributes();
         serializables[i].ApplyAttributes();
 
 
     // Do the editor logic after attribute has been edited.
     // Do the editor logic after attribute has been edited.
-    PostEditAttribute(serializables, index);
+    PostEditAttribute(serializables, index, oldValues);
 
 
     // If not an intermediate edit, reload the editor fields with validated values
     // If not an intermediate edit, reload the editor fields with validated values
     // (attributes may have interactions; therefore we load everything, not just the value being edited)
     // (attributes may have interactions; therefore we load everything, not just the value being edited)
@@ -914,6 +919,11 @@ void PickResourceDone(StringHash eventType, VariantMap& eventData)
         return;
         return;
     }
     }
 
 
+    // Store old values so that PostEditAttribute can create undo actions
+    Array<Variant> oldValues;
+    for (uint i = 0; i < resourceTargets.length; ++i)
+        oldValues.Push(resourceTargets[i].attributes[resourcePickIndex]);
+
     for (uint i = 0; i < resourceTargets.length; ++i)
     for (uint i = 0; i < resourceTargets.length; ++i)
     {
     {
         Serializable@ target = resourceTargets[i];
         Serializable@ target = resourceTargets[i];
@@ -949,7 +959,7 @@ void PickResourceDone(StringHash eventType, VariantMap& eventData)
         }
         }
     }
     }
 
 
-    PostEditAttribute(resourceTargets, resourcePickIndex);
+    PostEditAttribute(resourceTargets, resourcePickIndex, oldValues);
     UpdateAttributeInspector(false);
     UpdateAttributeInspector(false);
 
 
     resourceTargets.Clear();
     resourceTargets.Clear();

+ 167 - 2
Bin/Data/Scripts/Editor/EditorActions.as

@@ -149,7 +149,8 @@ class CreateComponentAction : EditAction
         Node@ node = editorScene.GetNode(nodeID);
         Node@ node = editorScene.GetNode(nodeID);
         if (node !is null)
         if (node !is null)
         {
         {
-            Component@ component = node.CreateComponent(componentData.root.GetAttribute("type"), componentID < FIRST_LOCAL_ID ? REPLICATED : LOCAL, componentID);
+            Component@ component = node.CreateComponent(componentData.root.GetAttribute("type"), componentID < FIRST_LOCAL_ID ?
+                REPLICATED : LOCAL, componentID);
             component.LoadXML(componentData.root);
             component.LoadXML(componentData.root);
             component.ApplyAttributes();
             component.ApplyAttributes();
         }
         }
@@ -177,7 +178,8 @@ class DeleteComponentAction : EditAction
         Node@ node = editorScene.GetNode(nodeID);
         Node@ node = editorScene.GetNode(nodeID);
         if (node !is null)
         if (node !is null)
         {
         {
-            Component@ component = node.CreateComponent(componentData.root.GetAttribute("type"), componentID < FIRST_LOCAL_ID ? REPLICATED : LOCAL, componentID);
+            Component@ component = node.CreateComponent(componentData.root.GetAttribute("type"), componentID < FIRST_LOCAL_ID ?
+                REPLICATED : LOCAL, componentID);
             component.LoadXML(componentData.root);
             component.LoadXML(componentData.root);
             component.ApplyAttributes();
             component.ApplyAttributes();
         }
         }
@@ -193,4 +195,167 @@ class DeleteComponentAction : EditAction
             node.RemoveComponent(component);
             node.RemoveComponent(component);
         }
         }
     }
     }
+}
+
+enum AttributeTargetType
+{
+    TARGET_NODE,
+    TARGET_COMPONENT,
+    TARGET_UIELEMENT
+}
+
+class EditAttributeAction : EditAction
+{
+    // \todo How to identify UI elements for undo/redo? They don't have IDs
+    uint targetID;
+    AttributeTargetType targetType;
+    uint attrIndex;
+    Variant undoValue;
+    Variant redoValue;
+
+    void Define(Serializable@ target, uint index, const Variant&in oldValue)
+    {
+        attrIndex = index;
+        undoValue = oldValue;
+        redoValue = target.attributes[index];
+
+        if (cast<Node>(target) !is null)
+        {
+            Node@ targetNode = target;
+            targetType = TARGET_NODE;
+            targetID = targetNode.id;
+        }
+        else if (cast<Component>(target) !is null)
+        {
+            Component@ targetComponent = target;
+            targetType = TARGET_COMPONENT;
+            targetID = targetComponent.id;
+        }
+        else
+        {
+            targetType = TARGET_UIELEMENT;
+        }
+    }
+
+    Serializable@ GetTarget()
+    {
+        switch (targetType)
+        {
+        case TARGET_NODE:
+            return editorScene.GetNode(targetID);
+        case TARGET_COMPONENT:
+            return editorScene.GetComponent(targetID);
+        default:
+            break;
+        }
+        
+        return null;
+    }
+
+    void Undo()
+    {
+        Serializable@ target = GetTarget();
+        if (target !is null)
+        {
+            target.attributes[attrIndex] = undoValue;
+            target.ApplyAttributes();
+            // Can't know if need a full update, so assume true
+            attributesFullDirty = true;
+            // Apply side effects
+            PostEditAttribute(target, attrIndex);
+        }
+    }
+
+    void Redo()
+    {
+        Serializable@ target = GetTarget();
+        if (target !is null)
+        {
+            target.attributes[attrIndex] = redoValue;
+            target.ApplyAttributes();
+            // Can't know if need a full update, so assume true
+            attributesFullDirty = true;
+            // Apply side effects
+            PostEditAttribute(target, attrIndex);
+        }
+    }
+}
+
+class ToggleNodeEnabledAction : EditAction
+{
+    uint nodeID;
+    bool undoValue;
+    
+    void Define(Node@ node, bool oldEnabled)
+    {
+        nodeID = node.id;
+        undoValue = oldEnabled;
+    }
+
+    void Undo()
+    {
+        Node@ node = editorScene.GetNode(nodeID);
+        if (node !is null)
+            node.SetEnabled(undoValue, true);
+    }
+
+    void Redo()
+    {
+        Node@ node = editorScene.GetNode(nodeID);
+        if (node !is null)
+            node.SetEnabled(!undoValue, true);
+    }
+}
+
+class Transform
+{
+    Vector3 position;
+    Quaternion rotation;
+    Vector3 scale;
+    
+    void Define(Node@ node)
+    {
+        position = node.position;
+        rotation = node.rotation;
+        scale = node.scale;
+    }
+
+    void Apply(Node@ node)
+    {
+        node.SetTransform(position, rotation, scale);
+    }
+}
+
+class EditNodeTransformAction : EditAction
+{
+    uint nodeID;
+    Transform undoTransform;
+    Transform redoTransform;
+
+    void Define(Node@ node, const Transform&in oldTransform)
+    {
+        nodeID = node.id;
+        undoTransform = oldTransform;
+        redoTransform.Define(node);
+    }
+
+    void Undo()
+    {
+        Node@ node = editorScene.GetNode(nodeID);
+        if (node !is null)
+        {
+            undoTransform.Apply(node);
+            UpdateNodeAttributes();
+        }
+    }
+    
+    void Redo()
+    {
+        Node@ node = editorScene.GetNode(nodeID);
+        if (node !is null)
+        {
+            redoTransform.Apply(node);
+            UpdateNodeAttributes();
+        }
+    }
 }
 }

+ 44 - 0
Bin/Data/Scripts/Editor/EditorGizmo.as

@@ -9,6 +9,11 @@ const float rotSensitivity = 50.0;
 
 
 EditMode lastGizmoMode;
 EditMode lastGizmoMode;
 
 
+// For undo
+bool previousGizmoDrag;
+bool needGizmoUndo;
+Array<Transform> oldGizmoTransforms;
+
 class GizmoAxis
 class GizmoAxis
 {
 {
     Ray axisRay;
     Ray axisRay;
@@ -196,7 +201,11 @@ void GizmoMoved()
 void UseGizmo()
 void UseGizmo()
 {
 {
     if (gizmo is null || !gizmo.enabled || editMode == EDIT_SELECT)
     if (gizmo is null || !gizmo.enabled || editMode == EDIT_SELECT)
+    {
+        StoreGizmoEditActions();
+        previousGizmoDrag = false;
         return;
         return;
+    }
 
 
     IntVector2 pos = ui.cursorPosition;
     IntVector2 pos = ui.cursorPosition;
     if (ui.GetElementAt(pos) !is null)
     if (ui.GetElementAt(pos) !is null)
@@ -231,6 +240,14 @@ void UseGizmo()
 
 
     if (drag)
     if (drag)
     {
     {
+        // Store initial transforms for undo when gizmo drag started
+        if (!previousGizmoDrag)
+        {
+            oldGizmoTransforms.Resize(editNodes.length);
+            for (uint i = 0; i < editNodes.length; ++i)
+                oldGizmoTransforms[i].Define(editNodes[i]);
+        }
+
         bool moved = false;
         bool moved = false;
 
 
         if (editMode == EDIT_MOVE)
         if (editMode == EDIT_MOVE)
@@ -281,8 +298,16 @@ void UseGizmo()
         {
         {
             GizmoMoved();
             GizmoMoved();
             UpdateNodeAttributes();
             UpdateNodeAttributes();
+            needGizmoUndo = true;
         }
         }
     }
     }
+    else
+    {
+        if (previousGizmoDrag)
+            StoreGizmoEditActions();
+    }
+
+    previousGizmoDrag = drag;
 }
 }
 
 
 bool IsGizmoSelected()
 bool IsGizmoSelected()
@@ -421,3 +446,22 @@ bool ScaleNodes(Vector3 adjust)
 
 
     return moved;
     return moved;
 }
 }
+
+void StoreGizmoEditActions()
+{
+    if (needGizmoUndo && editNodes.length > 0 && oldGizmoTransforms.length == editNodes.length)
+    {
+        EditActionGroup group;
+        
+        for (uint i = 0; i < editNodes.length; ++i)
+        {
+            EditNodeTransformAction action;
+            action.Define(editNodes[i], oldGizmoTransforms[i]);
+            group.actions.Push(action);
+        }
+        
+        SaveEditActionGroup(group);
+    }
+
+    needGizmoUndo = false;
+}

+ 25 - 10
Bin/Data/Scripts/Editor/EditorNodeWindow.as

@@ -8,6 +8,7 @@ XMLFile@ componentXMLResource;
 
 
 bool applyMaterialList = true;
 bool applyMaterialList = true;
 bool attributesDirty = false;
 bool attributesDirty = false;
+bool attributesFullDirty = false;
 
 
 const String STRIKED_OUT = "——";   // Two unicode EM DASH (U+2014)
 const String STRIKED_OUT = "——";   // Two unicode EM DASH (U+2014)
 const ShortStringHash NODE_IDS_VAR("NodeIDs");
 const ShortStringHash NODE_IDS_VAR("NodeIDs");
@@ -130,6 +131,8 @@ Array<Serializable@> ToSerializableArray(Array<Node@> nodes)
 void UpdateAttributeInspector(bool fullUpdate = true)
 void UpdateAttributeInspector(bool fullUpdate = true)
 {
 {
     attributesDirty = false;
     attributesDirty = false;
+    if (fullUpdate)
+        attributesFullDirty = false;
 
 
     // If full update delete all containers and added them back as necessary
     // If full update delete all containers and added them back as necessary
     if (fullUpdate)
     if (fullUpdate)
@@ -291,20 +294,32 @@ void UpdateAttributeInspectorIcons()
     }
     }
 }
 }
 
 
-void PostEditAttribute(Array<Serializable@>@ serializables, uint index)
+void PostEditAttribute(Array<Serializable@>@ serializables, uint index, const Array<Variant>& oldValues)
 {
 {
-    // If a StaticModel/AnimatedModel/Skybox model was changed, apply a possibly different material list
-    if (applyMaterialList && serializables[0].attributeInfos[index].name == "Model")
+    // Create undo actions for the edits
+    EditActionGroup group;
+    for (uint i = 0; i < serializables.length; ++i)
     {
     {
-        for (uint i = 0; i < serializables.length; ++i)
-        {
-            StaticModel@ staticModel = cast<StaticModel>(serializables[i]);
-            if (staticModel !is null)
-                ApplyMaterialList(staticModel);
-        }
+        EditAttributeAction action;
+        action.Define(serializables[i], index, oldValues[i]);
+        group.actions.Push(action);
     }
     }
+    
+    SaveEditActionGroup(group);
 
 
-    SetSceneModified();
+    for (uint i = 0; i < serializables.length; ++i)
+        PostEditAttribute(serializables[i], index);
+}
+
+void PostEditAttribute(Serializable@ serializable, uint index)
+{
+    // If a StaticModel/AnimatedModel/Skybox model was changed, apply a possibly different material list
+    if (applyMaterialList && serializable.attributeInfos[index].name == "Model")
+    {
+        StaticModel@ staticModel = cast<StaticModel>(serializable);
+        if (staticModel !is null)
+            ApplyMaterialList(staticModel);
+    }
 }
 }
 
 
 void SetAttributeEditorID(UIElement@ attrEdit, Array<Serializable@>@ serializables)
 void SetAttributeEditorID(UIElement@ attrEdit, Array<Serializable@>@ serializables)

+ 58 - 4
Bin/Data/Scripts/Editor/EditorScene.as

@@ -513,44 +513,80 @@ bool SceneToggleEnable()
 
 
     ui.cursor.shape = CS_BUSY;
     ui.cursor.shape = CS_BUSY;
 
 
+    EditActionGroup group;
+
     // Toggle enabled state of nodes recursively
     // Toggle enabled state of nodes recursively
     for (uint i = 0; i < selectedNodes.length; ++i)
     for (uint i = 0; i < selectedNodes.length; ++i)
     {
     {
         // Do not attempt to disable the Scene
         // Do not attempt to disable the Scene
         if (selectedNodes[i].typeName == "Node")
         if (selectedNodes[i].typeName == "Node")
-            selectedNodes[i].SetEnabled(!selectedNodes[i].enabled, true);
+        {
+            bool oldEnabled = selectedNodes[i].enabled;
+            selectedNodes[i].SetEnabled(!oldEnabled, true);
+
+            // Create undo action
+            ToggleNodeEnabledAction action;
+            action.Define(selectedNodes[i], oldEnabled);
+            group.actions.Push(action);
+        }
     }
     }
     for (uint i = 0; i < selectedComponents.length; ++i)
     for (uint i = 0; i < selectedComponents.length; ++i)
     {
     {
         // Some components purposefully do not expose the Enabled attribute, and it does not affect them in any way
         // Some components purposefully do not expose the Enabled attribute, and it does not affect them in any way
         // (Octree, PhysicsWorld). Check that the first attribute is in fact called "Is Enabled"
         // (Octree, PhysicsWorld). Check that the first attribute is in fact called "Is Enabled"
         if (selectedComponents[i].numAttributes > 0 && selectedComponents[i].attributeInfos[0].name == "Is Enabled")
         if (selectedComponents[i].numAttributes > 0 && selectedComponents[i].attributeInfos[0].name == "Is Enabled")
-            selectedComponents[i].enabled = !selectedComponents[i].enabled;
+        {
+            bool oldEnabled = selectedComponents[i].enabled;
+            selectedComponents[i].enabled = !oldEnabled;
+            
+            // Create undo action
+            EditAttributeAction action;
+            action.Define(selectedComponents[i], 0, Variant(oldEnabled));
+            group.actions.Push(action);
+        }
     }
     }
 
 
+    SaveEditActionGroup(group);
+
     return true;
     return true;
 }
 }
 
 
 bool SceneChangeParent(Node@ sourceNode, Node@ targetNode, bool createUndoAction = true)
 bool SceneChangeParent(Node@ sourceNode, Node@ targetNode, bool createUndoAction = true)
 {
 {
-    // Perform the reparenting
+    // Create undo action if requested
     if (createUndoAction)
     if (createUndoAction)
     {
     {
         ReparentNodeAction action;
         ReparentNodeAction action;
         action.Define(sourceNode, targetNode);
         action.Define(sourceNode, targetNode);
         SaveEditAction(action);
         SaveEditAction(action);
     }
     }
+
     sourceNode.parent = targetNode;
     sourceNode.parent = targetNode;
 
 
     // Return true if success
     // Return true if success
-    return sourceNode.parent is targetNode;
+    if (sourceNode.parent is targetNode)
+    {
+        UpdateNodeAttributes(); // Parent change may have changed local transform
+        return true;
+    }
+    else
+        return false;
 }
 }
 
 
 bool SceneResetPosition()
 bool SceneResetPosition()
 {
 {
     if (editNode !is null)
     if (editNode !is null)
     {
     {
+        Transform oldTransform;
+        oldTransform.Define(editNode);
+
         editNode.position = Vector3(0.0, 0.0, 0.0);
         editNode.position = Vector3(0.0, 0.0, 0.0);
+        
+        // Create undo action
+        EditNodeTransformAction action;
+        action.Define(editNode, oldTransform);
+        SaveEditAction(action);
+
         UpdateNodeAttributes();
         UpdateNodeAttributes();
         return true;
         return true;
     }
     }
@@ -562,7 +598,16 @@ bool SceneResetRotation()
 {
 {
     if (editNode !is null)
     if (editNode !is null)
     {
     {
+        Transform oldTransform;
+        oldTransform.Define(editNode);
+
         editNode.rotation = Quaternion();
         editNode.rotation = Quaternion();
+        
+        // Create undo action
+        EditNodeTransformAction action;
+        action.Define(editNode, oldTransform);
+        SaveEditAction(action);
+
         UpdateNodeAttributes();
         UpdateNodeAttributes();
         return true;
         return true;
     }
     }
@@ -574,7 +619,16 @@ bool SceneResetScale()
 {
 {
     if (editNode !is null)
     if (editNode !is null)
     {
     {
+        Transform oldTransform;
+        oldTransform.Define(editNode);
+
         editNode.scale = Vector3(1.0, 1.0, 1.0);
         editNode.scale = Vector3(1.0, 1.0, 1.0);
+        
+        // Create undo action
+        EditNodeTransformAction action;
+        action.Define(editNode, oldTransform);
+        SaveEditAction(action);
+
         UpdateNodeAttributes();
         UpdateNodeAttributes();
         return true;
         return true;
     }
     }

+ 2 - 2
Bin/Data/Scripts/Editor/EditorUI.as

@@ -885,6 +885,6 @@ void SetIconEnabledColor(UIElement@ element, bool enabled, bool partial = false)
 void UpdateDirtyUI()
 void UpdateDirtyUI()
 {
 {
     // Perform some event-triggered updates latently in case a large hierarchy was changed
     // Perform some event-triggered updates latently in case a large hierarchy was changed
-    if (attributesDirty)
-        UpdateAttributeInspector(false);
+    if (attributesFullDirty || attributesDirty)
+        UpdateAttributeInspector(attributesFullDirty);
 }
 }