Browse Source

Work on GUIMenuBar
GUIDropDownBox should now properly handle different placement options

Marko Pintera 12 years ago
parent
commit
47682e394e

+ 2 - 0
BansheeEngine/BansheeEngine.vcxproj

@@ -250,6 +250,7 @@
     <ClInclude Include="Include\BsGUILayout.h" />
     <ClInclude Include="Include\BsGUILayoutY.h" />
     <ClInclude Include="Include\BsGUIMenu.h" />
+    <ClInclude Include="Include\BsGUIMenuBar.h" />
     <ClInclude Include="Include\BsGUIScrollBar.h" />
     <ClInclude Include="Include\BsGUIScrollBarHandle.h" />
     <ClInclude Include="Include\BsGUIScrollBarHorz.h" />
@@ -311,6 +312,7 @@
     <ClCompile Include="Source\BsGUILayoutY.cpp" />
     <ClCompile Include="Source\BsGUIManager.cpp" />
     <ClCompile Include="Source\BsGUIMaterialManager.cpp" />
+    <ClCompile Include="Source\BsGUIMenuBar.cpp" />
     <ClCompile Include="Source\BsGUIMouseEvent.cpp" />
     <ClCompile Include="Source\BsGUIScrollBar.cpp" />
     <ClCompile Include="Source\BsGUIScrollBarHandle.cpp" />

+ 6 - 0
BansheeEngine/BansheeEngine.vcxproj.filters

@@ -216,6 +216,9 @@
     <ClInclude Include="Source\BsGUIContextMenu.cpp">
       <Filter>Source Files\GUI</Filter>
     </ClInclude>
+    <ClInclude Include="Include\BsGUIMenuBar.h">
+      <Filter>Header Files\GUI</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="Source\BsGUIElement.cpp">
@@ -371,5 +374,8 @@
     <ClCompile Include="Source\BsGUIMenu.cpp">
       <Filter>Source Files\GUI</Filter>
     </ClCompile>
+    <ClCompile Include="Source\BsGUIMenuBar.cpp">
+      <Filter>Source Files\GUI</Filter>
+    </ClCompile>
   </ItemGroup>
 </Project>

+ 1 - 1
BansheeEngine/Include/BsGUIContextMenu.h

@@ -7,6 +7,6 @@ namespace BansheeEngine
 {
 	class BS_EXPORT GUIContextMenu : public GUIMenu
 	{
-
+		
 	};
 }

+ 45 - 0
BansheeEngine/Include/BsGUIMenuBar.h

@@ -0,0 +1,45 @@
+#pragma once
+
+#include "BsPrerequisites.h"
+
+namespace BansheeEngine
+{
+	class BS_EXPORT GUIMenuBar
+	{
+		struct GUIMenuBarData
+		{
+			CM::WString name;
+			GUIMenu* menu;
+			GUIButton* button;
+		};
+
+	public:
+		GUIMenuBar(BS::GUIWidget* parent);
+		virtual ~GUIMenuBar();
+
+		void setSize(CM::UINT32 width, CM::UINT32 height);
+		void setPosition(CM::INT32 x, CM::INT32 y);
+
+		const GUIMenuItem* addMenuItem(const CM::WString& path, std::function<void()> callback);
+		const GUIMenuItem* addSeparator(const CM::WString& path);
+		const GUIMenuItem* getMenuItem(const CM::WString& path) const;
+		void removeMenuItem(const CM::WString& path);
+	private:
+		GUIWidget* mParentWidget;
+		GUIArea* mMainArea;
+		GUIArea* mBackgroundArea;
+
+		CM::Vector<GUIMenuBarData>::type mChildMenus;
+
+		const GUIMenuBarData* getSubMenu(const CM::WString& name) const;
+
+		/**
+		 * @brief	Attempts to remove the first element from the specified path. First element
+		 * 			returned in specified in "pathRoot", and original "path" parameter is modified so
+		 * 			it no longer includes the first element.
+		 *
+		 * @return	False if first element doesn't exist, true otherwise.
+		 */
+		bool stripPath(CM::WString& path, CM::WString& pathRoot) const;
+	};
+}

+ 2 - 0
BansheeEngine/Include/BsPrerequisites.h

@@ -56,6 +56,8 @@ namespace BansheeEngine
 	class GUIListBox;
 	class GUIDropDownBox;
 	class DragAndDropManager;
+	class GUIMenu;
+	class GUIMenuItem;
 	class GUIContent;
 	class GUIContextMenu;
 

+ 36 - 12
BansheeEngine/Source/BsGUIDropDownBox.cpp

@@ -122,20 +122,44 @@ namespace BansheeEngine
 		setSkin(skin);
 
 		Rect dropDownListBounds = placement.getBounds();
+		int potentialLeftStart = 0;
+		int potentialRightStart = 0;
+		int potentialTopStart = 0;
+		int potentialBottomStart = 0;
+
+		switch(placement.getType())
+		{
+		case GUIDropDownAreaPlacement::Type::Position: 
+			potentialLeftStart = potentialRightStart = placement.getPosition().x;
+			potentialTopStart = potentialBottomStart = placement.getPosition().y;
+			break;
+		case GUIDropDownAreaPlacement::Type::BoundsHorz:
+			potentialRightStart = placement.getBounds().x;
+			potentialLeftStart = placement.getBounds().x + placement.getBounds().width;
+			potentialBottomStart = placement.getBounds().y + placement.getBounds().height;
+			potentialTopStart = placement.getBounds().y;
+			break;
+		case GUIDropDownAreaPlacement::Type::BoundsVert:
+			potentialRightStart = placement.getBounds().x + placement.getBounds().width;
+			potentialLeftStart = placement.getBounds().x;
+			potentialBottomStart = placement.getBounds().y;
+			potentialTopStart = placement.getBounds().y + placement.getBounds().height;
+			break;
+		}
 
 		// Determine x position and whether to align to left or right side of the drop down list
-		UINT32 availableRightwardWidth = (UINT32)std::max(0, (target->getLeft() + target->getWidth()) - dropDownListBounds.x);
-		UINT32 availableLeftwardWidth = (UINT32)std::max(0, (dropDownListBounds.x + dropDownListBounds.width) - target->getLeft());
+		UINT32 availableRightwardWidth = (UINT32)std::max(0, (target->getLeft() + target->getWidth()) - potentialRightStart);
+		UINT32 availableLeftwardWidth = (UINT32)std::max(0, potentialLeftStart - target->getLeft());
 
 		//// Prefer right if possible
 		if(DROP_DOWN_BOX_WIDTH <= availableRightwardWidth)
-			x = dropDownListBounds.x;
+			x = potentialRightStart;
 		else
 		{
 			if(availableRightwardWidth >= availableLeftwardWidth)
-				x = dropDownListBounds.x;
+				x = potentialRightStart;
 			else
-				x = dropDownListBounds.x - std::min(DROP_DOWN_BOX_WIDTH, availableLeftwardWidth);
+				x = potentialLeftStart - std::min(DROP_DOWN_BOX_WIDTH, availableLeftwardWidth);
 		}
 
 		// Determine maximum width
@@ -143,6 +167,10 @@ namespace BansheeEngine
 		width = std::min(DROP_DOWN_BOX_WIDTH, maxPossibleWidth);
 
 		// Determine y position and whether to open upward or downward
+		UINT32 availableDownwardHeight = (UINT32)std::max(0, (target->getTop() + target->getHeight()) - potentialBottomStart);
+		UINT32 availableUpwardHeight = (UINT32)std::max(0, potentialTopStart - target->getTop());
+
+		//// Prefer down if possible
 		UINT32 helperElementHeight = mScrollUpStyle->height + mScrollDownStyle->height + mBackgroundStyle->margins.top + mBackgroundStyle->margins.bottom;
 
 		UINT32 maxNeededHeight = helperElementHeight;
@@ -150,26 +178,22 @@ namespace BansheeEngine
 		for(UINT32 i = 0; i < numElements; i++)
 			maxNeededHeight += getElementHeight(i);
 
-		UINT32 availableDownwardHeight = (UINT32)std::max(0, (target->getTop() + target->getHeight()) - (dropDownListBounds.y + dropDownListBounds.height));
-		UINT32 availableUpwardHeight = (UINT32)std::max(0, dropDownListBounds.y - target->getTop());
-
-		//// Prefer down if possible
 		height = 0;
 		if(maxNeededHeight <= availableDownwardHeight)
 		{
-			y = dropDownListBounds.y + dropDownListBounds.height;
+			y = potentialBottomStart;
 			height = availableDownwardHeight;
 		}
 		else
 		{
 			if(availableDownwardHeight >= availableUpwardHeight)
 			{
-				y = dropDownListBounds.y + dropDownListBounds.height;
+				y = potentialBottomStart;
 				height = availableDownwardHeight;
 			}
 			else
 			{
-				y = dropDownListBounds.y - std::min(maxNeededHeight, availableUpwardHeight);
+				y = potentialTopStart - std::min(maxNeededHeight, availableUpwardHeight);
 				height = availableUpwardHeight;
 			}
 		}

+ 158 - 0
BansheeEngine/Source/BsGUIMenuBar.cpp

@@ -0,0 +1,158 @@
+#include "BsGUIMenuBar.h"
+#include "BsGUIArea.h"
+#include "BsGUIElement.h"
+#include "BsGUIButton.h"
+#include "BsGUILayout.h"
+#include "BsGUIMenu.h"
+
+using namespace CamelotFramework;
+
+namespace BansheeEngine
+{
+	GUIMenuBar::GUIMenuBar(BS::GUIWidget* parent)
+		:mParentWidget(parent), mMainArea(nullptr), mBackgroundArea(nullptr)
+	{
+		mBackgroundArea = GUIArea::create(*parent, 0, 0, 1, 13, 9900);
+		mMainArea = GUIArea::create(*parent, 0, 0, 1, 13, 9899);
+	}
+
+	GUIMenuBar::~GUIMenuBar()
+	{
+		for(auto& menu : mChildMenus)
+		{
+			cm_delete<PoolAlloc>(menu.menu);
+			GUIElement::destroy(menu.button);
+		}
+
+		GUIArea::destroy(mMainArea);
+		GUIArea::destroy(mBackgroundArea);
+	}
+
+	void GUIMenuBar::setPosition(CM::INT32 x, CM::INT32 y)
+	{
+		mMainArea->setPosition(x, y);
+		mBackgroundArea->setPosition(x, y);
+	}
+
+	void GUIMenuBar::setSize(CM::UINT32 width, CM::UINT32 height)
+	{
+		mMainArea->setSize(width, height);
+		mBackgroundArea->setSize(width, height);
+	}
+
+	const GUIMenuItem* GUIMenuBar::addMenuItem(const CM::WString& path, std::function<void()> callback)
+	{
+		WString strippedPath = path;
+		WString rootName;
+
+		if(!stripPath(strippedPath, rootName))
+			return nullptr;
+
+		const GUIMenuBarData* subMenu = getSubMenu(rootName);
+		if(subMenu == nullptr)
+		{
+			mChildMenus.push_back(GUIMenuBarData());
+
+			GUIMenuBarData& newSubMenu = mChildMenus.back();
+			newSubMenu.name = rootName;
+			newSubMenu.menu = cm_new<GUIMenu>();
+
+			GUIButton* newButton = GUIButton::create(*mParentWidget, rootName);
+			mMainArea->getLayout().addElement(newButton);
+
+			newSubMenu.button = newButton;
+
+			subMenu = &newSubMenu;
+		}
+
+		return subMenu->menu->addMenuItem(strippedPath, callback);
+	}
+
+	const GUIMenuItem* GUIMenuBar::addSeparator(const CM::WString& path)
+	{
+		WString strippedPath = path;
+		WString rootName;
+
+		if(!stripPath(strippedPath, rootName))
+			return nullptr;
+
+		const GUIMenuBarData* subMenu = getSubMenu(rootName);
+		if(subMenu == nullptr)
+		{
+			mChildMenus.push_back(GUIMenuBarData());
+
+			GUIMenuBarData& newSubMenu = mChildMenus.back();
+			newSubMenu.name = rootName;
+			newSubMenu.menu = cm_new<GUIMenu>();
+
+			GUIButton* newButton = GUIButton::create(*mParentWidget, rootName);
+			mMainArea->getLayout().addElement(newButton);
+
+			newSubMenu.button = newButton;
+
+			subMenu = &newSubMenu;
+		}
+
+		return subMenu->menu->addSeparator(strippedPath);
+	}
+
+	const GUIMenuItem* GUIMenuBar::getMenuItem(const CM::WString& path) const
+	{
+		WString strippedPath = path;
+		WString rootName;
+
+		if(!stripPath(strippedPath, rootName))
+			return nullptr;
+
+		const GUIMenuBarData* subMenu = getSubMenu(rootName);
+		if(subMenu == nullptr)
+			return nullptr;
+
+		return subMenu->menu->getMenuItem(strippedPath);
+	}
+
+	void GUIMenuBar::removeMenuItem(const CM::WString& path)
+	{
+		WString strippedPath = path;
+		WString rootName;
+
+		if(!stripPath(strippedPath, rootName))
+			return;
+
+		const GUIMenuBarData* subMenu = getSubMenu(rootName);
+		if(subMenu == nullptr)
+			return;
+
+		const GUIMenuItem* item = subMenu->menu->getMenuItem(strippedPath);
+		if(item == nullptr)
+			return;
+
+		subMenu->menu->removeMenuItem(item);
+	}
+
+	const GUIMenuBar::GUIMenuBarData* GUIMenuBar::getSubMenu(const CM::WString& name) const
+	{
+		for(auto& subMenu : mChildMenus)
+		{
+			if(subMenu.name == name)
+				return &subMenu;
+		}
+
+		return nullptr;
+	}
+
+	bool GUIMenuBar::stripPath(CM::WString& path, CM::WString& pathRoot) const
+	{
+		Vector<WString>::type pathElements = StringUtil::split(path, L"/");
+		if(pathElements.size() == 0)
+			return false;
+
+		pathRoot = pathElements[0];
+		path.erase(0, pathRoot.size());
+
+		if(path.size() > 0)
+			path.erase(0, 1); // Forward slash
+
+		return true;
+	}
+}

+ 2 - 0
TODODoc.txt

@@ -18,6 +18,8 @@
    some out, even if they contain no data. This would create incomplete RTTI class hierarchy which can cause various issues.
  - If you are manually constructing a class and it has an "initialize()" method, then you need to call it before using the class. Most object creation is wrapped
    in methods which do this automatically though. ("create()" methods)
+ - Unlike most other objects you don't have to worry about cleaning up GUI elements. You can destroy them manually if you want to get rid of a 
+   specific element, but normally destruction will be handled by the owning GUIWidget.
  - Notation rules:
     - Method prefixed with _ (i.e. _getDevice()) are internal methods used by the engine systems and normal user should not need to call them unless 
 	  he really knows what is he doing. Those methods might also be marked with "Internal method" notation in their documentation (without the _ prefix).