Explorar el Código

EditorWindow maximize/restore works

BearishSun hace 10 años
padre
commit
05bcc32e89

+ 15 - 0
BansheeEditor/Include/BsDockManager.h

@@ -169,6 +169,11 @@ namespace BansheeEngine
 			 * @brief	Triggered whenever the user closes or undocks a widget belonging to this container.
 			 */
 			void widgetRemoved();
+
+			/**
+			 * @brief	Triggered when the maximize button in the container's title bar is clicked.
+			 */
+			void maximizeClicked();
 		};
 
 		/**
@@ -262,6 +267,12 @@ namespace BansheeEngine
 		 */
 		void updateClippedBounds() override;
 
+		/**
+		 * @brief	Maximizes or restored the specified container. If any container is previously
+		 * 			maximized it needs to be toggled back to restored state before maximizing another.
+		 */
+		void toggleMaximize(DockContainer* container);
+
 		/**
 		 * @copydoc GUIElementBase::_mouseEvent
 		 */
@@ -276,6 +287,10 @@ namespace BansheeEngine
 		HMesh mDropOverlayMesh;
 		Rect2I mLastOverlayBounds;
 
+		bool mIsMaximized;
+		DockContainer* mMaximizedContainer;
+		DockManagerLayoutPtr mRestoredLayout;
+
 		DockContainer* mMouseOverContainer;
 		DockLocation mHighlightedDropLoc;
 		bool mShowOverlay;

+ 26 - 0
BansheeEditor/Include/BsDockManagerLayout.h

@@ -56,6 +56,7 @@ namespace BansheeEngine
 		};
 
 	public:
+		DockManagerLayout();
 		~DockManagerLayout();
 
 		/**
@@ -63,14 +64,39 @@ namespace BansheeEngine
 		 */
 		Entry& getRootEntry() { return mRootEntry; }
 
+		/**
+		 * @brief	Signals whether there is a maximized dock container in the layout.
+		 *
+		 * @param	maximized 	True if maximized.
+		 * @param	widgetName	Name of the widgets on the maximized container.
+		 */
+		void setIsMaximized(bool maximized, const Vector<String>& widgetNames);
+
+		/**
+		 * @brief	Check if the layout has a maximized container.
+		 */
+		bool isMaximized() const { return mIsMaximized; }
+
+		/**
+		 * @brief	Returns widget names that are in the maximized container, if there is one.
+		 */
+		const Vector<String>& getMaximizedWidgetNames() const { return mMaximizedWidgetNames; }
+
 		/**
 		 * @brief	Removes widgets that can no longer be found (their names no longer reference a widget),
 		 *			and removes containers with no widgets.
 		 */
 		void pruneInvalidLeaves();
 
+		/**
+		 * @brief	Makes a deep copy of this object.
+		 */
+		DockManagerLayoutPtr clone();
+
 	private:
 		Entry mRootEntry;
+		bool mIsMaximized;
+		Vector<String> mMaximizedWidgetNames;
 
 		/************************************************************************/
 		/* 								RTTI		                     		*/

+ 8 - 0
BansheeEditor/Include/BsDockManagerLayoutRTTI.h

@@ -12,10 +12,18 @@ namespace BansheeEngine
 		DockManagerLayout::Entry& getRootEntry(DockManagerLayout* obj) { return obj->mRootEntry; }
 		void setRootEntry(DockManagerLayout* obj, DockManagerLayout::Entry& val) { obj->mRootEntry = val; } 
 
+		bool& getIsMaximized(DockManagerLayout* obj) { return obj->mIsMaximized; }
+		void setIsMaximized(DockManagerLayout* obj, bool& val) { obj->mIsMaximized = val; }
+
+		Vector<String>& getMaximizedWidgetNames(DockManagerLayout* obj) { return obj->mMaximizedWidgetNames; }
+		void setMaximizedWidgetNames(DockManagerLayout* obj, Vector<String>& val) { obj->mMaximizedWidgetNames = val; }
+
 	public:
 		DockManagerLayoutRTTI()
 		{
 			addPlainField("mRootEntry", 0, &DockManagerLayoutRTTI::getRootEntry, &DockManagerLayoutRTTI::setRootEntry);
+			addPlainField("mIsMaximized", 1, &DockManagerLayoutRTTI::getIsMaximized, &DockManagerLayoutRTTI::setIsMaximized);
+			addPlainField("mMaximizedWidgetNames", 2, &DockManagerLayoutRTTI::getMaximizedWidgetNames, &DockManagerLayoutRTTI::setMaximizedWidgetNames);
 		}
 
 		void onDeserializationEnded(IReflectable* obj) override

+ 8 - 0
BansheeEditor/Include/BsEditorWidgetContainer.h

@@ -111,6 +111,7 @@ namespace BansheeEngine
 
 		Event<void()> onWidgetAdded; /**< Triggered whenever a new widget is added to this container. */
 		Event<void()> onWidgetClosed; /**< Triggered whenever a widget docked in this container is closed. */
+		Event<void()> onMaximized; /**< Triggered when the maximize button is clicked. */
 	private:
 		EditorWindowBase* mParentWindow;
 		GUITabbedTitleBar* mTitleBar;
@@ -150,6 +151,13 @@ namespace BansheeEngine
 		 */
 		void tabClosed(UINT32 idx);
 
+		/**
+		 * @brief	Triggered when a user clicks the maximize button on the title bar.
+		 *
+		 * @param	idx		Unique widget index (not sequential) of the tab that was closed.
+		 */
+		void tabMaximized(UINT32 idx);
+
 		/**
 		 * @brief	Triggered when a user drags a tab off the tabbed title bar.
 		 *

+ 5 - 0
BansheeEditor/Include/BsEditorWindow.h

@@ -57,6 +57,11 @@ namespace BansheeEngine
 		 */
 		void widgetRemoved();
 
+		/**
+		 * @brief	Triggered when the maximize button on the title bar is clicked.
+		 */
+		void maximizeClicked();
+
 		/**
 		 * @brief	A callback that triggers when a drag and drop operation originated from
 		 *			this window ends. 

+ 36 - 25
BansheeEditor/Include/BsGUITabbedTitleBar.h

@@ -109,6 +109,12 @@ namespace BansheeEngine
 		 */
 		Event<void(UINT32)> onTabClosed;
 
+		/**
+		 * @brief	Triggered when a tab maximize button is clicked. Provided parameter is the
+		 *			unique index of the maximized/restored tab.
+		 */
+		Event<void(UINT32)> onTabMaximized;
+
 		/**
 		 * @brief	Triggered when a tab gets dragged off the title bar. Provided 
 		 *			parameter is the unique index of the activated tab.
@@ -122,31 +128,6 @@ namespace BansheeEngine
 		Event<void(UINT32)> onTabDraggedOn;
 
 	protected:
-		static const UINT32 TAB_SPACING;
-		static const UINT32 OPTION_BTN_SPACING;
-
-		Vector<GUITabButton*> mTabButtons;
-
-		UINT32 mUniqueTabIdx;
-		UINT32 mActiveTabIdx;
-		GUITexture* mBackgroundImage;
-		GUIButton* mMinBtn;
-		GUIButton* mCloseBtn;
-		GUIToggleGroupPtr mTabToggleGroup;
-
-		EditorWidgetBase* mTempDraggedWidget;
-		UINT32 mTempDraggedTabIdx;
-
-		bool mDragInProgress;
-		GUITabButton* mDraggedBtn;
-		INT32 mDragBtnOffset;
-		INT32 mInitialDragOffset;
-
-		String mBackgroundStyle;
-		String mCloseBtnStyle;
-		String mMaximizeBtnStyle;
-		String mTabBtnStyle;
-
 		GUITabbedTitleBar(const String& backgroundStyle, const String& tabBtnStyle, 
 			const String& minBtnStyle, const String& closeBtnStyle, const GUIDimensions& dimensions);
 
@@ -199,6 +180,11 @@ namespace BansheeEngine
 		 */
 		void tabClosed();
 
+		/**
+		 * @brief	Triggered when the maximize button is pressed.
+		 */
+		void tabMaximize();
+
 		/**
 		 * @brief	Triggered every frame while a tab button is being dragged.
 		 *
@@ -220,5 +206,30 @@ namespace BansheeEngine
 		 *			tab's position in the title bar.
 		 */
 		INT32 uniqueIdxToSeqIdx(UINT32 uniqueIdx) const;
+
+		static const UINT32 TAB_SPACING;
+		static const UINT32 OPTION_BTN_SPACING;
+
+		Vector<GUITabButton*> mTabButtons;
+
+		UINT32 mUniqueTabIdx;
+		UINT32 mActiveTabIdx;
+		GUITexture* mBackgroundImage;
+		GUIButton* mMaxBtn;
+		GUIButton* mCloseBtn;
+		GUIToggleGroupPtr mTabToggleGroup;
+
+		EditorWidgetBase* mTempDraggedWidget;
+		UINT32 mTempDraggedTabIdx;
+
+		bool mDragInProgress;
+		GUITabButton* mDraggedBtn;
+		INT32 mDragBtnOffset;
+		INT32 mInitialDragOffset;
+
+		String mBackgroundStyle;
+		String mCloseBtnStyle;
+		String mMaximizeBtnStyle;
+		String mTabBtnStyle;
 	};
 }

+ 174 - 88
BansheeEditor/Source/BsDockManager.cpp

@@ -141,6 +141,7 @@ namespace BansheeEngine
 		mWidgets = bs_new<EditorWidgetContainer>(guiWidget.get(), parentWindow);
 
 		mWidgets->onWidgetClosed.connect(std::bind(&DockManager::DockContainer::widgetRemoved, this));
+		mWidgets->onMaximized.connect(std::bind(&DockManager::DockContainer::maximizeClicked, this));
 
 		if(mSlider != nullptr)
 		{
@@ -159,6 +160,7 @@ namespace BansheeEngine
 		mGUIWidgetSO = guiWidgetSO;
 
 		mWidgets->onWidgetClosed.connect(std::bind(&DockManager::DockContainer::widgetRemoved, this));
+		mWidgets->onMaximized.connect(std::bind(&DockManager::DockContainer::maximizeClicked, this));
 
 		if(mSlider != nullptr)
 		{
@@ -213,6 +215,7 @@ namespace BansheeEngine
 		children[idxB] = bs_new<DockContainer>(mManager, this);
 
 		mWidgets->onWidgetClosed.clear();
+		mWidgets->onMaximized.clear();
 		
 		children[idxA]->makeLeaf(mManager->mParentWindow);
 		children[idxB]->makeLeaf(mGUIWidgetSO, mWidgets);
@@ -300,6 +303,12 @@ namespace BansheeEngine
 		{
 			if(mParent == nullptr) // We're root so we just reset ourselves, can't delete root
 			{
+				if (mManager->mIsMaximized)
+				{
+					mManager->mMaximizedContainer = this;
+					mManager->mIsMaximized = false;
+				}
+
 				bs_delete(mWidgets);
 				mWidgets = nullptr;
 
@@ -322,6 +331,7 @@ namespace BansheeEngine
 				if (sibling->mIsLeaf)
 				{
 					sibling->mWidgets->onWidgetClosed.clear();
+					sibling->mWidgets->onMaximized.clear();
 
 					mParent->makeLeaf(sibling->mGUIWidgetSO, sibling->mWidgets);
 					sibling->mWidgets = nullptr;
@@ -344,6 +354,11 @@ namespace BansheeEngine
 		}
 	}
 
+	void DockManager::DockContainer::maximizeClicked()
+	{
+		mManager->toggleMaximize(this);
+	}
+
 	DockManager::DockContainer* DockManager::DockContainer::find(EditorWidgetContainer* widgetContainer)
 	{
 		if(mIsLeaf)
@@ -433,7 +448,7 @@ namespace BansheeEngine
 
 	DockManager::DockManager(EditorWindowBase* parentWindow, const GUIDimensions& dimensions)
 		:GUIElementContainer(dimensions), mMouseOverContainer(nullptr), mHighlightedDropLoc(DockLocation::None),
-		mShowOverlay(false), mRootContainer(this), mParentWindow(parentWindow)
+		mShowOverlay(false), mRootContainer(this), mParentWindow(parentWindow), mIsMaximized(false), mMaximizedContainer(nullptr)
 	{
 		mTopDropPolygon = bs_newN<Vector2>(4);
 		mBotDropPolygon = bs_newN<Vector2>(4);
@@ -567,57 +582,72 @@ namespace BansheeEngine
 			return widgetNames;
 		};
 
-		DockManagerLayoutPtr layout = bs_shared_ptr_new<DockManagerLayout>();
-		DockManagerLayout::Entry* rootEntry = &layout->getRootEntry();
-
-		if(mRootContainer.mIsLeaf)
+		if (mIsMaximized)
 		{
-			rootEntry->isLeaf = true;
-			rootEntry->widgetNames = GetWidgetNamesInContainer(&mRootContainer);
+			DockManagerLayoutPtr layout;
+
+			if (mRestoredLayout != nullptr)
+				layout = mRestoredLayout->clone();
+			else
+				layout = bs_shared_ptr_new<DockManagerLayout>();
+
+			layout->setIsMaximized(true, GetWidgetNamesInContainer(mMaximizedContainer));
+			return layout;
 		}
 		else
 		{
-			rootEntry->isLeaf = false;
-			rootEntry->horizontalSplit = mRootContainer.mIsHorizontal;
-			rootEntry->splitPosition = mRootContainer.mSplitPosition;
-			rootEntry->parent = nullptr;
-		}
+			DockManagerLayoutPtr layout = bs_shared_ptr_new<DockManagerLayout>();
+			DockManagerLayout::Entry* rootEntry = &layout->getRootEntry();
 
-		Stack<StackElem> todo;
-		todo.push(StackElem(rootEntry, &mRootContainer));
+			if (mRootContainer.mIsLeaf)
+			{
+				rootEntry->isLeaf = true;
+				rootEntry->widgetNames = GetWidgetNamesInContainer(&mRootContainer);
+			}
+			else
+			{
+				rootEntry->isLeaf = false;
+				rootEntry->horizontalSplit = mRootContainer.mIsHorizontal;
+				rootEntry->splitPosition = mRootContainer.mSplitPosition;
+				rootEntry->parent = nullptr;
+			}
 
-		while(!todo.empty())
-		{
-			StackElem currentElem = todo.top();
-			todo.pop();
+			Stack<StackElem> todo;
+			todo.push(StackElem(rootEntry, &mRootContainer));
 
-			if(!currentElem.container->mIsLeaf)
+			while (!todo.empty())
 			{
-				for(UINT32 i = 0; i < 2; i++)
-				{
-					if(currentElem.container->mChildren[i] == nullptr)
-						continue;
+				StackElem currentElem = todo.top();
+				todo.pop();
 
-					if(currentElem.container->mChildren[i]->mIsLeaf)
-					{
-						Vector<String> widgetNames = GetWidgetNamesInContainer(currentElem.container->mChildren[i]);
-						currentElem.layoutEntry->children[i] = 
-							DockManagerLayout::Entry::createLeaf(currentElem.layoutEntry, i, widgetNames);
-					}
-					else
+				if (!currentElem.container->mIsLeaf)
+				{
+					for (UINT32 i = 0; i < 2; i++)
 					{
-						currentElem.layoutEntry->children[i] = 
-							DockManagerLayout::Entry::createContainer(currentElem.layoutEntry, i, 
-							currentElem.container->mChildren[i]->mSplitPosition, 
-							currentElem.container->mChildren[i]->mIsHorizontal);
-
-						todo.push(StackElem(currentElem.layoutEntry->children[i], currentElem.container->mChildren[i]));
+						if (currentElem.container->mChildren[i] == nullptr)
+							continue;
+
+						if (currentElem.container->mChildren[i]->mIsLeaf)
+						{
+							Vector<String> widgetNames = GetWidgetNamesInContainer(currentElem.container->mChildren[i]);
+							currentElem.layoutEntry->children[i] =
+								DockManagerLayout::Entry::createLeaf(currentElem.layoutEntry, i, widgetNames);
+						}
+						else
+						{
+							currentElem.layoutEntry->children[i] =
+								DockManagerLayout::Entry::createContainer(currentElem.layoutEntry, i,
+								currentElem.container->mChildren[i]->mSplitPosition,
+								currentElem.container->mChildren[i]->mIsHorizontal);
+
+							todo.push(StackElem(currentElem.layoutEntry->children[i], currentElem.container->mChildren[i]));
+						}
 					}
 				}
 			}
-		}
 
-		return layout;
+			return layout;
+		}
 	}
 
 	void DockManager::setLayout(const DockManagerLayoutPtr& layout)
@@ -625,34 +655,36 @@ namespace BansheeEngine
 		// Undock all currently docked widgets
 		Vector<EditorWidgetBase*> undockedWidgets;
 
-		Stack<DockContainer*> todo;
-		todo.push(&mRootContainer);
-
-		while(!todo.empty())
+		std::function<void(DockContainer*)> undockWidgets = [&](DockContainer* container)
 		{
-			DockContainer* current = todo.top();
-			todo.pop();
+			if (!container->mIsLeaf)
+			{
+				// Due to the way undocking works a container can be transfromed from non-leaf to leaf
+				// if its child container is deleted, so we need to check to that specially
+				undockWidgets(container->mChildren[0]);
 
-			if(current->mIsLeaf)
+				if (!container->mIsLeaf)
+					undockWidgets(container->mChildren[1]);
+			}
+
+			if (container->mIsLeaf)
 			{
-				if(current->mWidgets != nullptr)
+				if (container->mWidgets != nullptr)
 				{
-					while(current->mWidgets->getNumWidgets() > 0)
+					UINT32 numWidgets = container->mWidgets->getNumWidgets();
+
+					for (UINT32 i = 0; i < numWidgets; i++)
 					{
-						EditorWidgetBase* curWidget = current->mWidgets->getWidget(0);
-						current->mWidgets->remove(*curWidget);
+						EditorWidgetBase* curWidget = container->mWidgets->getWidget(0);
+						container->mWidgets->remove(*curWidget);
 
 						undockedWidgets.push_back(curWidget);
 					}
 				}
 			}
-			else
-			{
-				todo.push(current->mChildren[0]);
-				todo.push(current->mChildren[1]);
-			}
-		}
-
+		};
+		
+		undockWidgets(&mRootContainer);
 		mRootContainer = DockContainer(this);
 
 		// Load layout
@@ -690,55 +722,70 @@ namespace BansheeEngine
 		// Prune layout by removing invalid leafs (ones with no widgets, or widgets that no longer exist)
 		layout->pruneInvalidLeaves();
 
-		// Dock elements
-		const DockManagerLayout::Entry* rootEntry = &layout->getRootEntry();
-		const DockManagerLayout::Entry* leafEntry = GetLeafEntry(rootEntry, 0);
+		if (layout->isMaximized())
+		{
+			mRestoredLayout = layout->clone();
+			mRestoredLayout->setIsMaximized(false, Vector<String>());
 
-		if(leafEntry->widgetNames.size() > 0) // If zero, entire layout is empty
+			const Vector<String>& maximizedWidgets = mRestoredLayout->getMaximizedWidgetNames();
+			if (maximizedWidgets.size() > 0) // If zero, entire layout is empty
+			{
+				mRootContainer.makeLeaf(mParentWindow);
+				OpenWidgets(&mRootContainer, maximizedWidgets);
+			}
+		}
+		else
 		{
-			mRootContainer.makeLeaf(mParentWindow);
-			OpenWidgets(&mRootContainer, leafEntry->widgetNames);
+			// Dock elements
+			const DockManagerLayout::Entry* rootEntry = &layout->getRootEntry();
+			const DockManagerLayout::Entry* leafEntry = GetLeafEntry(rootEntry, 0);
 
-			if(!rootEntry->isLeaf)
+			if (leafEntry->widgetNames.size() > 0) // If zero, entire layout is empty
 			{
-				Stack<StackEntry> layoutTodo;
-				layoutTodo.push(StackEntry(rootEntry, &mRootContainer));
+				mRootContainer.makeLeaf(mParentWindow);
+				OpenWidgets(&mRootContainer, leafEntry->widgetNames);
 
-				while(!layoutTodo.empty())
+				if (!rootEntry->isLeaf)
 				{
-					StackEntry curEntry = layoutTodo.top();
-					layoutTodo.pop();
+					Stack<StackEntry> layoutTodo;
+					layoutTodo.push(StackEntry(rootEntry, &mRootContainer));
 
-					leafEntry = GetLeafEntry(curEntry.layoutEntry->children[1], 0);
+					while (!layoutTodo.empty())
+					{
+						StackEntry curEntry = layoutTodo.top();
+						layoutTodo.pop();
 
-					curEntry.container->splitContainer(curEntry.layoutEntry->horizontalSplit, false, curEntry.layoutEntry->splitPosition);
+						leafEntry = GetLeafEntry(curEntry.layoutEntry->children[1], 0);
 
-					DockContainer* otherChild = curEntry.container->mChildren[1];
-					OpenWidgets(otherChild, leafEntry->widgetNames);
+						curEntry.container->splitContainer(curEntry.layoutEntry->horizontalSplit, false, curEntry.layoutEntry->splitPosition);
 
-					if(!curEntry.layoutEntry->children[0]->isLeaf)
-						layoutTodo.push(StackEntry(curEntry.layoutEntry->children[0], curEntry.container->mChildren[0]));
+						DockContainer* otherChild = curEntry.container->mChildren[1];
+						OpenWidgets(otherChild, leafEntry->widgetNames);
 
-					if(!curEntry.layoutEntry->children[1]->isLeaf)
-						layoutTodo.push(StackEntry(curEntry.layoutEntry->children[1], curEntry.container->mChildren[1]));
+						if (!curEntry.layoutEntry->children[0]->isLeaf)
+							layoutTodo.push(StackEntry(curEntry.layoutEntry->children[0], curEntry.container->mChildren[0]));
+
+						if (!curEntry.layoutEntry->children[1]->isLeaf)
+							layoutTodo.push(StackEntry(curEntry.layoutEntry->children[1], curEntry.container->mChildren[1]));
+					}
 				}
 			}
-		}
 
-		// Set container sizes
-		{
-			Stack<StackEntry> layoutTodo;
-			layoutTodo.push(StackEntry(rootEntry, &mRootContainer));
-
-			while(!layoutTodo.empty())
+			// Set container sizes
 			{
-				StackEntry curEntry = layoutTodo.top();
-				layoutTodo.pop();
+				Stack<StackEntry> layoutTodo;
+				layoutTodo.push(StackEntry(rootEntry, &mRootContainer));
 
-				if(!curEntry.layoutEntry->isLeaf)
+				while (!layoutTodo.empty())
 				{
-					layoutTodo.push(StackEntry(curEntry.layoutEntry->children[0], curEntry.container->mChildren[0]));
-					layoutTodo.push(StackEntry(curEntry.layoutEntry->children[1], curEntry.container->mChildren[1]));
+					StackEntry curEntry = layoutTodo.top();
+					layoutTodo.pop();
+
+					if (!curEntry.layoutEntry->isLeaf)
+					{
+						layoutTodo.push(StackEntry(curEntry.layoutEntry->children[0], curEntry.container->mChildren[0]));
+						layoutTodo.push(StackEntry(curEntry.layoutEntry->children[1], curEntry.container->mChildren[1]));
+					}
 				}
 			}
 		}
@@ -753,6 +800,45 @@ namespace BansheeEngine
 		setArea(mArea.x, mArea.y, mArea.width, mArea.height);
 	}
 
+	void DockManager::toggleMaximize(DockContainer* container)
+	{
+		if (mIsMaximized)
+		{
+			if (mRestoredLayout != nullptr)
+				setLayout(mRestoredLayout);
+
+			mRestoredLayout = nullptr;
+			mMaximizedContainer = nullptr;
+			mIsMaximized = false;
+		}
+		else
+		{
+			mRestoredLayout = getLayout();
+			mMaximizedContainer = container;
+
+			Vector<String> maximizedWidgetNames;
+			if (container->mWidgets != nullptr)
+			{
+				UINT32 numWidgets = container->mWidgets->getNumWidgets();
+
+				for (UINT32 i = 0; i < numWidgets; i++)
+				{
+					EditorWidgetBase* widget = container->mWidgets->getWidget(i);
+					maximizedWidgetNames.push_back(widget->getName());
+				}
+			}
+
+			DockManagerLayoutPtr maxLayout = bs_shared_ptr_new<DockManagerLayout>();
+			DockManagerLayout::Entry& rootEntry = maxLayout->getRootEntry();
+
+			rootEntry.isLeaf = true;
+			rootEntry.widgetNames = maximizedWidgetNames;
+
+			setLayout(maxLayout);
+			mIsMaximized = true;
+		}
+	}
+
 	void DockManager::updateClippedBounds()
 	{
 		// TODO - Clipping not actually accounted for but shouldn't matter as right now DockManager is only used in one specific situation

+ 43 - 0
BansheeEditor/Source/BsDockManagerLayout.cpp

@@ -46,6 +46,10 @@ namespace BansheeEngine
 		return newEntry;
 	}
 
+	DockManagerLayout::DockManagerLayout()
+		:mIsMaximized(false)
+	{ }
+
 	DockManagerLayout::~DockManagerLayout()
 	{
 		Stack<Entry*> todo;
@@ -70,6 +74,12 @@ namespace BansheeEngine
 		}
 	}
 
+	void DockManagerLayout::setIsMaximized(bool maximized, const Vector<String>& widgetNames)
+	{
+		mIsMaximized = maximized;
+		mMaximizedWidgetNames = widgetNames;
+	}
+
 	void DockManagerLayout::pruneInvalidLeaves()
 	{
 		Stack<Entry*> layoutTodo;
@@ -120,6 +130,39 @@ namespace BansheeEngine
 				}
 			}
 		}
+
+		for (INT32 i = 0; i < (INT32)mMaximizedWidgetNames.size(); i++)
+		{
+			if (!EditorWidgetManager::instance().isValidWidget(mMaximizedWidgetNames[i]))
+			{
+				mMaximizedWidgetNames.erase(mMaximizedWidgetNames.begin() + i);
+				i--;
+			}
+		}
+	}
+
+	DockManagerLayoutPtr DockManagerLayout::clone()
+	{
+		std::function<void(Entry*, Entry*, Entry*)> cloneEntry = [&](Entry* toClone, Entry* parent, Entry* clone)
+		{
+			*clone = *toClone;
+
+			clone->parent = parent;
+
+			if (!toClone->isLeaf)
+			{
+				clone->children[0] = bs_new<Entry>();
+				clone->children[1] = bs_new<Entry>();
+
+				cloneEntry(toClone->children[0], clone, clone->children[0]);
+				cloneEntry(toClone->children[1], clone, clone->children[1]);
+			}
+		};
+
+		DockManagerLayoutPtr copy = bs_shared_ptr_new<DockManagerLayout>();
+		cloneEntry(&mRootEntry, nullptr, &copy->mRootEntry);
+
+		return copy;
 	}
 
 	/************************************************************************/

+ 6 - 0
BansheeEditor/Source/BsEditorWidgetContainer.cpp

@@ -27,6 +27,7 @@ namespace BansheeEngine
 		mTitleBar->onTabClosed.connect(std::bind(&EditorWidgetContainer::tabClosed, this, _1));
 		mTitleBar->onTabDraggedOff.connect(std::bind(&EditorWidgetContainer::tabDraggedOff, this, _1));
 		mTitleBar->onTabDraggedOn.connect(std::bind(&EditorWidgetContainer::tabDraggedOn, this, _1));
+		mTitleBar->onTabMaximized.connect(std::bind(&EditorWidgetContainer::tabMaximized, this, _1));
 
 		GUILayout* titleBarLayout = mTitleBarPanel->addNewElement<GUILayoutX>();
 		titleBarLayout->addElement(mTitleBar);
@@ -225,6 +226,11 @@ namespace BansheeEngine
 		widget->close();
 	}
 
+	void EditorWidgetContainer::tabMaximized(UINT32 uniqueIdx)
+	{
+		onMaximized();
+	}
+
 	void EditorWidgetContainer::tabDraggedOff(UINT32 uniqueIdx)
 	{
 		EditorWidgetBase* widget = mWidgets[uniqueIdx];

+ 10 - 0
BansheeEditor/Source/BsEditorWindow.cpp

@@ -3,6 +3,7 @@
 #include "BsEditorWindowManager.h"
 #include "BsDragAndDropManager.h"
 #include "BsRenderWindow.h"
+#include "BsCoreThread.h"
 
 namespace BansheeEngine
 {
@@ -13,6 +14,7 @@ namespace BansheeEngine
 		
 		mWidgets->onWidgetAdded.connect(std::bind(&EditorWindow::widgetAdded, this));
 		mWidgets->onWidgetClosed.connect(std::bind(&EditorWindow::widgetRemoved, this));
+		mWidgets->onMaximized.connect(std::bind(&EditorWindow::maximizeClicked, this));
 	}
 
 	EditorWindow::~EditorWindow()
@@ -69,6 +71,14 @@ namespace BansheeEngine
 		}
 	}
 
+	void EditorWindow::maximizeClicked()
+	{
+		if (mRenderWindow->getProperties().isMaximized())
+			mRenderWindow->restore(gCoreAccessor());
+		else
+			mRenderWindow->maximize(gCoreAccessor());
+	}
+
 	void EditorWindow::closeWindowDelayed()
 	{
 		close();

+ 16 - 9
BansheeEditor/Source/BsGUITabbedTitleBar.cpp

@@ -22,7 +22,7 @@ namespace BansheeEngine
 
 	GUITabbedTitleBar::GUITabbedTitleBar(const String& backgroundStyle, const String& tabBtnStyle, 
 		const String& maxBtnStyle, const String& closeBtnStyle, const GUIDimensions& dimensions)
-		:GUIElementContainer(dimensions), mMinBtn(nullptr), 
+		:GUIElementContainer(dimensions), mMaxBtn(nullptr), 
 		mCloseBtn(nullptr), mBackgroundImage(nullptr), mUniqueTabIdx(0), mActiveTabIdx(0),
 		mDragInProgress(false), mDraggedBtn(nullptr), mDragBtnOffset(0), mInitialDragOffset(0), mBackgroundStyle(backgroundStyle),
 		mTabBtnStyle(tabBtnStyle), mMaximizeBtnStyle(maxBtnStyle), mCloseBtnStyle(closeBtnStyle), mTempDraggedWidget(nullptr),
@@ -40,19 +40,20 @@ namespace BansheeEngine
 		if(mTabBtnStyle == StringUtil::BLANK)
 			mTabBtnStyle = "TabbedBarBtn";
 
-		mMinBtn = GUIButton::create(HString(L""), mMaximizeBtnStyle);
-		mMinBtn->_setElementDepth(1);
-		_registerChildElement(mMinBtn);
+		mMaxBtn = GUIButton::create(HString(L""), mMaximizeBtnStyle);
+		mMaxBtn->_setElementDepth(1);
+		_registerChildElement(mMaxBtn);
 
 		mCloseBtn = GUIButton::create(HString(L""), mCloseBtnStyle);
 		mCloseBtn->_setElementDepth(1);
 		_registerChildElement(mCloseBtn);
 
 		mBackgroundImage = GUITexture::create(mBackgroundStyle);
-		mBackgroundImage->_setElementDepth(mMinBtn->_getRenderElementDepthRange() + 1);
+		mBackgroundImage->_setElementDepth(mMaxBtn->_getRenderElementDepthRange() + 1);
 		_registerChildElement(mBackgroundImage);
 
 		mCloseBtn->onClick.connect(std::bind(&GUITabbedTitleBar::tabClosed, this));
+		mMaxBtn->onClick.connect(std::bind(&GUITabbedTitleBar::tabMaximize, this));
 
 		mTabToggleGroup = GUIToggle::createToggleGroup();
 	}
@@ -244,7 +245,7 @@ namespace BansheeEngine
 
 	Vector2I GUITabbedTitleBar::_getOptimalSize() const
 	{
-		Vector2I optimalSize = mMinBtn->_getOptimalSize();
+		Vector2I optimalSize = mMaxBtn->_getOptimalSize();
 		optimalSize.x += OPTION_BTN_SPACING + 1;
 
 		Vector2I closeBtnOptimalSize = mCloseBtn->_getOptimalSize();
@@ -265,7 +266,7 @@ namespace BansheeEngine
 
 	void GUITabbedTitleBar::_updateLayoutInternal(const GUILayoutData& data)
 	{
-		Vector2I minBtnOptimalSize = mMinBtn->_getOptimalSize();
+		Vector2I minBtnOptimalSize = mMaxBtn->_getOptimalSize();
 		Vector2I closeBtnOptimalSize = mCloseBtn->_getOptimalSize();
 
 		UINT32 endButtonWidth = minBtnOptimalSize.x + closeBtnOptimalSize.x + OPTION_BTN_SPACING;
@@ -331,7 +332,7 @@ namespace BansheeEngine
 			childData.area.width = minBtnOptimalSize.x;
 			childData.area.height = minBtnOptimalSize.y;
 
-			mMinBtn->_setLayoutData(childData);
+			mMaxBtn->_setLayoutData(childData);
 		}
 
 		optionBtnXPos += minBtnOptimalSize.x + OPTION_BTN_SPACING;
@@ -366,7 +367,7 @@ namespace BansheeEngine
 			curX += TAB_SPACING + optimalSize.x;
 		}
 
-		Vector2I minBtnOptimalSize = mMinBtn->_getOptimalSize();
+		Vector2I minBtnOptimalSize = mMaxBtn->_getOptimalSize();
 		Vector2I closeBtnOptimalSize = mCloseBtn->_getOptimalSize();
 
 		UINT32 endButtonWidth = minBtnOptimalSize.x + closeBtnOptimalSize.x + OPTION_BTN_SPACING;
@@ -400,6 +401,12 @@ namespace BansheeEngine
 			mActiveTabIdx = mTabButtons[0]->getIndex();
 	}
 
+	void GUITabbedTitleBar::tabMaximize()
+	{
+		if (!onTabMaximized.empty())
+			onTabMaximized(mActiveTabIdx);
+	}
+
 	void GUITabbedTitleBar::startDrag(UINT32 seqIdx, const Vector2I& startDragPos)
 	{
 		if(!mDragInProgress)

+ 16 - 20
TODO.txt

@@ -52,39 +52,30 @@ Code quality improvements:
 ----------------------------------------------------------------------
 Polish
 
-Open a project window and closing it, then attempting shutdown causes a crash when destroying a ScriptGUILayout
- - Likely due to clearScene() deleting all GUIWidgets and their related elements before managed elements were destroyed
-
 Ribek use:
  - Hook up color picker to guicolor field
- - When starting drag from hierarchy tree view it tends to select another object (can't repro)
  - UseCustomInspector isn't implemented
  - Camera, Renderable, Material, Texture inspector
  - Test release mode
  - Ability to create assets in Project view (At least Material)
  - (Optionally, needed for GUI editing) GUISkin resource inspector & a way to inspect and save the default editor skin
 
-First screenshot:
- - Additional menu bar items: 
+Other polish:
+ - C# inspectors for Point/Spot/Directional light
+ - C# interface for Font
+ - Import option inspectors for Texture, Mesh, Font
+ - Add menu items:
   - Edit: Undo/Redo, Cut/Copy/Paste/Duplicate/Delete(need to make sure it works in Hierarchy, with shortcuts), Frame Selected, Preferences, Play/Pause/Step, View/Move/rotate/scale
   - Assets (also add to context): Create(Folder, Material, Shader, Script, Prefab, GUI Skin), Show in explorer
   - Game Object (also add to context): Create(Empty, Empty Child, Camera, Renderable, Point/Spot/Directional Light), Apply prefab, Break prefab, Revert prefab
    - Possibly create helper objects: Cube, Sphere, Plane, Quad, Capsule, Cylinder
   - Help - About, API Reference (link to site)
- - (Optionally) New UI look (tabs, component/array containers, better buttons)
-   - Foldout in scene tree view is hard to click, make it bigger
- - (Optionally) Console window
-
-Other polish:
- - C# inspectors for Point/Spot/Directional light
- - C# interface for Font
- - Handle seems to lag behind the selected mesh
+ - New UI look
  - Replace "minimize" button in tabbed title bar with maximize and make sure it works (in both docked and floating mode)
  - When I expand inspector elements and them come back to that object it should remember the previous state
    - Add a chaching mechanism to inspector (likely based on instance ID & property names)
    - This has to work not only when I come back to the object, but whenever inspector rebuilds (e.g. after removing element from array)
    - Consider saving this information with the serialized object
- - Import option inspectors for Texture, Mesh, Font
 
 Stage 2 polish:
  - Inject an icon into an .exe (Win32 specific)
@@ -97,14 +88,14 @@ Stage 2 polish:
  - When managed exception happens log an error and continue execution
  - Game publishing (Build window, collect resources, output exe, default viewport) (described below)
 
-Finalizing:
- - Add copyright notices in all files & change license to GPL
+Optional:
+ - When starting drag from hierarchy tree view it tends to select another object (can't repro)
+ - Handle seems to lag behind the selected mesh
  - Move all the code files into subfolders so their hierarchy is similar to VS filters
  - Splash screen
  - Settings/Preferences window
- - Documentation
- - Need to generate a proper merge of dev and preview branches
- - (Optionally) GUI tabbing to switch between elements
+ - Console window
+ - GUI tabbing to switch between elements
  - Undo/Redo
   - CmdRecordSO records an SO and all its children but it should only record a single SO
   - CmdRecordSO should instead of recording the entire object record a diff
@@ -119,6 +110,11 @@ Finalizing:
    - Likely use a user-provided callback to trigger when populating the menus
  - Need to list all script components in the Components menu
 
+Finalizing:
+ - Add copyright notices in all files & change license to GPL
+ - Documentation
+ - Need to generate a proper merge of dev and preview branches
+
 ----------------------------------------------------------------------
 Build system
  - Test Resources (if loading works and if they're properly packaged in build)