Browse Source

Support for creating custom undo/redo stacks per editor window

BearishSun 9 years ago
parent
commit
ee343f5182

+ 1 - 0
Source/MBansheeEditor/MBansheeEditor.csproj

@@ -223,6 +223,7 @@
     <Compile Include="Utility\UndoRedo.cs" />
     <Compile Include="Tests\UnitTests.cs" />
     <Compile Include="Tests\UnitTestTypes.cs" />
+    <Compile Include="Window\UndoRedoLocal.cs" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\MBansheeEngine\MBansheeEngine.csproj">

+ 27 - 0
Source/MBansheeEditor/Window/EditorWindow.cs

@@ -16,6 +16,10 @@ namespace BansheeEditor
     /// </summary>
     public class EditorWindow : ScriptObject
     {
+#pragma warning disable 649 // Value assigned by the runtime
+        private UndoRedo undoRedo;
+#pragma warning restore 649
+
         /// <summary>
         /// Width of the window in pixels.
         /// </summary>
@@ -55,6 +59,15 @@ namespace BansheeEditor
             set { Internal_SetFocus(mCachedPtr, value); }
         }
 
+        /// <summary>
+        /// Returns the local undo/redo stack specific to this editor window. Only windows marked with [UndoRedoLocal]
+        /// attribute will return a non-null value.
+        /// </summary>
+        public UndoRedo UndoRedo
+        {
+            get { return undoRedo; }
+        }
+
         /// <summary>
         /// Determines is the mouse pointer currently hovering over the editor window.
         /// </summary>
@@ -66,8 +79,19 @@ namespace BansheeEditor
         /// </summary>
         public bool Active { get { return Internal_IsActive(mCachedPtr); } }
 
+        /// <summary>
+        /// GUI panel that you may use for adding GUI elements to the window.
+        /// </summary>
         public GUIPanel GUI;
 
+        /// <summary>
+        /// Returns a list of all currently open editor windows.
+        /// </summary>
+        public static EditorWindow[] AllWindows
+        {
+            get { return Internal_GetAllWindows(); }
+        }
+
         /// <summary>
         /// Opens an editor window. If window is already open it returns the existing instance.
         /// </summary>
@@ -146,6 +170,9 @@ namespace BansheeEditor
         [MethodImpl(MethodImplOptions.InternalCall)]
         private static extern EditorWindow Internal_GetInstance(string ns, string typeName);
 
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern EditorWindow[] Internal_GetAllWindows();
+
         [MethodImpl(MethodImplOptions.InternalCall)]
         private static extern int Internal_GetWidth(IntPtr nativeInstance);
 

+ 28 - 0
Source/MBansheeEditor/Window/MenuItems.cs

@@ -722,6 +722,20 @@ namespace BansheeEditor
         [ToolbarItem("Undo", ToolbarIcon.Undo, "Undo (Ctrl + Z)", 1900, true)]
         public static void Undo()
         {
+            EditorWindow[] allWindows = EditorWindow.AllWindows;
+            foreach (var window in allWindows)
+            {
+                if (!window.HasFocus)
+                    continue;
+
+                UndoRedo localStack = window.UndoRedo;
+                if (localStack == null)
+                    continue;
+
+                localStack.Undo();
+                return;
+            }
+
             UndoRedo.Global.Undo();
         }
 
@@ -732,6 +746,20 @@ namespace BansheeEditor
         [ToolbarItem("Redo", ToolbarIcon.Redo, "Redo (Ctrl + Y)", 1899)]
         public static void Redo()
         {
+            EditorWindow[] allWindows = EditorWindow.AllWindows;
+            foreach (var window in allWindows)
+            {
+                if (!window.HasFocus)
+                    continue;
+
+                UndoRedo localStack = window.UndoRedo;
+                if (localStack == null)
+                    continue;
+
+                localStack.Redo();
+                return;
+            }
+
             UndoRedo.Global.Redo();
         }
 

+ 23 - 0
Source/MBansheeEditor/Window/UndoRedoLocal.cs

@@ -0,0 +1,23 @@
+//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
+//**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
+using System;
+
+namespace BansheeEditor
+{
+    /** @addtogroup Window
+     *  @{
+     */
+
+    /// <summary>
+    /// Can be placed on <see cref="EditorWindow"/> class to ensure the editor window uses a local undo/redo stack instead
+    /// of the global one.
+    /// </summary>
+    [AttributeUsage(AttributeTargets.Class)]
+    public sealed class UndoRedoLocal : Attribute
+    {
+        public UndoRedoLocal()
+        { }
+    }
+
+    /** @} */
+}

+ 1 - 1
Source/MBansheeEditor/Windows/AnimationWindow.cs

@@ -16,7 +16,7 @@ namespace BansheeEditor
     /// Displays animation curve editor window. Allows the user to manipulate keyframes of animation curves, add/remove
     /// curves from an animation clip, and manipulate animation events.
     /// </summary>
-    [DefaultSize(900, 500)]
+    [DefaultSize(900, 500), UndoRedoLocal]
     internal class AnimationWindow : EditorWindow
     {
         private const int FIELD_DISPLAY_WIDTH = 300;

+ 6 - 2
Source/SBansheeEditor/Include/BsScriptEditorWindow.h

@@ -83,6 +83,7 @@ namespace BansheeEngine
 		static MonoMethod* onResizedMethod;
 		static MonoMethod* onFocusChangedMethod;
 		static MonoField* guiPanelField;
+		static MonoField* undoRedoField;
 
 		// Global editor window management methods
 
@@ -100,7 +101,7 @@ namespace BansheeEngine
 
 		/**	Callback that is triggered when user requests a widget to be opened. */
 		static EditorWidgetBase* openEditorWidgetCallback(String ns, String type, UINT32 width, UINT32 height, 
-			EditorWidgetContainer& parentContainer);
+			bool localUndoRedo, EditorWidgetContainer& parentContainer);
 
 		static UnorderedMap<String, EditorWindowHandle> OpenScriptEditorWindows;
 		static Vector<String> AvailableWindowTypes;
@@ -110,6 +111,7 @@ namespace BansheeEngine
 		/************************************************************************/
 		static MonoObject* internal_createOrGetInstance(MonoString* ns, MonoString* typeName);
 		static MonoObject* internal_getInstance(MonoString* ns, MonoString* typeName);
+		static MonoArray* internal_getAllWindows();
 
 		static bool internal_hasFocus(ScriptEditorWindow* thisPtr);
 		static void internal_setFocus(ScriptEditorWindow* thisPtr, bool focus);
@@ -141,10 +143,11 @@ namespace BansheeEngine
 		 * @param[in]	type			Name of the widget type.
 		 * @param[in]	defaultWidth	Default width of the widget when initially created.
 		 * @param[in]	defaultHeight	Default height of the widget when initially created.
+		 * @param[in]	localUndoRedo	Determines should the window use a local undo/redo stack instead of the global one.
 		 * @param[in]	parentContainer	Container to initially dock the widget in.
 		 */
 		ScriptEditorWidget(const String& ns, const String& type, UINT32 defaultWidth, 
-			UINT32 defaultHeight, EditorWidgetContainer& parentContainer);
+			UINT32 defaultHeight, bool localUndoRedo, EditorWidgetContainer& parentContainer);
 		~ScriptEditorWidget();
 
 		/**
@@ -200,6 +203,7 @@ namespace BansheeEngine
 		ScriptEditorWindow* mScriptOwner;
 		ScriptGUILayout* mContentsPanel;
 		bool mIsInitialized;
+		bool mHasLocalUndoRedo;
 	};
 
 	/** @} */

+ 3 - 0
Source/SBansheeEditor/Include/BsScriptUndoRedo.h

@@ -20,6 +20,9 @@ namespace BansheeEngine
 	public:
 		SCRIPT_OBJ(EDITOR_ASSEMBLY, "BansheeEditor", "UndoRedo");
 
+		/** Creates a new managed UndoRedo stack. */
+		static MonoObject* create();
+
 	private:
 		ScriptUndoRedo(MonoObject* instance, const SPtr<UndoRedo>& undoRedo);
 

+ 35 - 6
Source/SBansheeEditor/Source/BsScriptEditorWindow.cpp

@@ -17,6 +17,7 @@
 #include "BsScriptHString.h"
 #include "BsPlatform.h"
 #include "BsInput.h"
+#include "BsScriptUndoRedo.h"
 
 using namespace std::placeholders;
 
@@ -27,6 +28,7 @@ namespace BansheeEngine
 	MonoMethod* ScriptEditorWindow::onResizedMethod = nullptr;
 	MonoMethod* ScriptEditorWindow::onFocusChangedMethod = nullptr;
 	MonoField* ScriptEditorWindow::guiPanelField = nullptr;
+	MonoField* ScriptEditorWindow::undoRedoField = nullptr;
 
 	ScriptEditorWindow::ScriptEditorWindow(ScriptEditorWidget* editorWidget)
 		:ScriptObject(editorWidget->getManagedInstance()), mName(editorWidget->getName()), mEditorWidget(editorWidget), 
@@ -51,6 +53,8 @@ namespace BansheeEngine
 	{
 		metaData.scriptClass->addInternalCall("Internal_CreateOrGetInstance", &ScriptEditorWindow::internal_createOrGetInstance);
 		metaData.scriptClass->addInternalCall("Internal_GetInstance", &ScriptEditorWindow::internal_getInstance);
+		metaData.scriptClass->addInternalCall("Internal_GetAllWindows", &ScriptEditorWindow::internal_getAllWindows);
+
 		metaData.scriptClass->addInternalCall("Internal_GetWidth", &ScriptEditorWindow::internal_getWidth);
 		metaData.scriptClass->addInternalCall("Internal_GetHeight", &ScriptEditorWindow::internal_getHeight);
 		metaData.scriptClass->addInternalCall("Internal_GetBounds", &ScriptEditorWindow::internal_getBounds);
@@ -65,6 +69,7 @@ namespace BansheeEngine
 		onFocusChangedMethod = metaData.scriptClass->getMethod("FocusChanged", 1);
 
 		guiPanelField = metaData.scriptClass->getField("GUI");
+		undoRedoField = metaData.scriptClass->getField("undoRedo");
 	}
 
 	MonoObject* ScriptEditorWindow::internal_createOrGetInstance(MonoString* ns, MonoString* typeName)
@@ -99,6 +104,18 @@ namespace BansheeEngine
 		return nullptr;
 	}
 
+	MonoArray* ScriptEditorWindow::internal_getAllWindows()
+	{
+		UINT32 numWindows = (UINT32)OpenScriptEditorWindows.size();
+		ScriptArray output = ScriptArray::create<ScriptEditorWindow>(numWindows);
+
+		UINT32 idx = 0;
+		for (auto& entry : OpenScriptEditorWindows)
+			output.set(idx++, entry.second.nativeObj->getManagedInstance());
+
+		return output.getInternal();
+	}
+
 	EditorWidgetBase* ScriptEditorWindow::getEditorWidget() const 
 	{ 
 		return mEditorWidget; 
@@ -294,11 +311,15 @@ namespace BansheeEngine
 		{
 			MonoClass* defaultSizeAttrib = assembly->getClass("BansheeEditor", "DefaultSize");
 			if (defaultSizeAttrib == nullptr)
-				BS_EXCEPT(InternalErrorException, "Cannot find DefaultSize managed class.");
+				BS_EXCEPT(InternalErrorException, "Cannot find DefaultSize managed attribute.");
 
 			MonoField* defaultWidthField = defaultSizeAttrib->getField("width");
 			MonoField* defaultHeightField = defaultSizeAttrib->getField("height");
 
+			MonoClass* undoRedoLocalAttrib = assembly->getClass("BansheeEditor", "UndoRedoLocal");
+			if (undoRedoLocalAttrib == nullptr)
+				BS_EXCEPT(InternalErrorException, "Cannot find UndoRedoLocal managed attribute.");
+
 			MonoClass* editorWindowClass = assembly->getClass("BansheeEditor", "EditorWindow");
 
 			const Vector<MonoClass*>& allClasses = assembly->getAllClasses();
@@ -316,10 +337,12 @@ namespace BansheeEngine
 						defaultHeightField->getValue(defaultSize, &height);
 					}
 
+					bool hasLocalUndoRedo = curClass->getAttribute(undoRedoLocalAttrib) != nullptr;
+
 					const String& className = curClass->getFullName();
 					EditorWidgetManager::instance().registerWidget(className, 
 						std::bind(&ScriptEditorWindow::openEditorWidgetCallback, curClass->getNamespace(), 
-						curClass->getTypeName(), width, height, _1));
+						curClass->getTypeName(), width, height, hasLocalUndoRedo, _1));
 					AvailableWindowTypes.push_back(className);
 				}
 			}
@@ -337,9 +360,9 @@ namespace BansheeEngine
 	}
 
 	EditorWidgetBase* ScriptEditorWindow::openEditorWidgetCallback(String ns, String type, UINT32 width, 
-		UINT32 height, EditorWidgetContainer& parentContainer)
+		UINT32 height, bool localUndoRedo, EditorWidgetContainer& parentContainer)
 	{
-		ScriptEditorWidget* editorWidget = bs_new<ScriptEditorWidget>(ns, type, width, height, parentContainer);
+		ScriptEditorWidget* editorWidget = bs_new<ScriptEditorWidget>(ns, type, width, height, localUndoRedo, parentContainer);
 		ScriptEditorWindow* nativeInstance = new (bs_alloc<ScriptEditorWindow>()) ScriptEditorWindow(editorWidget);
 
 		ScriptEditorWindow::registerScriptEditorWindow(nativeInstance);
@@ -379,11 +402,11 @@ namespace BansheeEngine
 	}
 
 	ScriptEditorWidget::ScriptEditorWidget(const String& ns, const String& type, UINT32 defaultWidth, 
-		UINT32 defaultHeight, EditorWidgetContainer& parentContainer)
+		UINT32 defaultHeight, bool localUndoRedo, EditorWidgetContainer& parentContainer)
 		: EditorWidgetBase(HString(toWString(type)), ns + "." + type, defaultWidth, defaultHeight, parentContainer)
 		, mNamespace(ns), mTypename(type), mOnInitializeThunk(nullptr), mOnDestroyThunk(nullptr), mUpdateThunk(nullptr)
 		, mManagedInstance(nullptr), mGetDisplayName(nullptr), mScriptOwner(nullptr), mContentsPanel(nullptr)
-		, mIsInitialized(false)
+		, mIsInitialized(false), mHasLocalUndoRedo(localUndoRedo)
 	{
 		if(createManagedInstance())
 		{
@@ -434,6 +457,12 @@ namespace BansheeEngine
 					mContentsPanel = ScriptGUILayout::toNative(guiPanel);
 					ScriptEditorWindow::guiPanelField->setValue(mManagedInstance, guiPanel);
 
+					if(mHasLocalUndoRedo)
+					{
+						MonoObject* undoRedo = ScriptUndoRedo::create();
+						ScriptEditorWindow::undoRedoField->setValue(mManagedInstance, undoRedo);
+					}
+
 					reloadMonoTypes(editorWindowClass);
 					return true;
 				}

+ 13 - 0
Source/SBansheeEditor/Source/BsScriptUndoRedo.cpp

@@ -49,6 +49,19 @@ namespace BansheeEngine
 		metaData.scriptClass->addInternalCall("Internal_BreakPrefab", &ScriptUndoRedo::internal_BreakPrefab);
 	}
 
+	MonoObject* ScriptUndoRedo::create()
+	{
+		bool dummy = false;
+		void* params[1] = { &dummy };
+
+		MonoObject* instance = metaData.scriptClass->createInstance("bool", params);
+
+		SPtr<UndoRedo> undoRedo = bs_shared_ptr_new<UndoRedo>();
+		new (bs_alloc<ScriptUndoRedo>()) ScriptUndoRedo(instance, undoRedo);
+
+		return instance;
+	}
+
 	void ScriptUndoRedo::internal_CreateInstance(MonoObject* instance)
 	{
 		SPtr<UndoRedo> undoRedo = bs_shared_ptr_new<UndoRedo>();