Procházet zdrojové kódy

Added DropDownWindow (WIP)

Marko Pintera před 10 roky
rodič
revize
64b31cf1e5
34 změnil soubory, kde provedl 1669 přidání a 947 odebrání
  1. 4 0
      BansheeEditor/BansheeEditor.vcxproj
  2. 12 0
      BansheeEditor/BansheeEditor.vcxproj.filters
  3. 82 0
      BansheeEditor/Include/BsDropDownWindow.h
  4. 30 0
      BansheeEditor/Include/BsDropDownWindowManager.h
  5. 1 0
      BansheeEditor/Include/BsEditorPrerequisites.h
  6. 113 0
      BansheeEditor/Source/BsDropDownWindow.cpp
  7. 29 0
      BansheeEditor/Source/BsDropDownWindowManager.cpp
  8. 4 0
      BansheeEditor/Source/BsEditorApplication.cpp
  9. 2 2
      BansheeEditor/Source/BsGUIMenuBar.cpp
  10. 4 2
      BansheeEngine/BansheeEngine.vcxproj
  11. 12 6
      BansheeEngine/BansheeEngine.vcxproj.filters
  12. 105 0
      BansheeEngine/Include/BsDropDownAreaPlacement.h
  13. 3 3
      BansheeEngine/Include/BsGUIDropDownBoxManager.h
  14. 5 5
      BansheeEngine/Include/BsGUIDropDownContent.h
  15. 1 1
      BansheeEngine/Include/BsGUIDropDownHitBox.h
  16. 285 351
      BansheeEngine/Include/BsGUIDropDownMenu.h
  17. 1 1
      BansheeEngine/Include/BsGUIMenu.h
  18. 1 1
      BansheeEngine/Include/BsPrerequisites.h
  19. 117 0
      BansheeEngine/Source/BsDropDownAreaPlacement.cpp
  20. 2 2
      BansheeEngine/Source/BsGUIContextMenu.cpp
  21. 2 2
      BansheeEngine/Source/BsGUIDropDownBoxManager.cpp
  22. 3 3
      BansheeEngine/Source/BsGUIDropDownContent.cpp
  23. 464 563
      BansheeEngine/Source/BsGUIDropDownMenu.cpp
  24. 2 2
      BansheeEngine/Source/BsGUIListBox.cpp
  25. 1 1
      BansheeEngine/Source/BsGUIManager.cpp
  26. 1 1
      BansheeEngine/Source/BsGUIMenu.cpp
  27. 87 0
      MBansheeEditor/DropDownWindow.cs
  28. 1 0
      MBansheeEditor/MBansheeEditor.csproj
  29. 1 1
      MBansheeEngine/ContextMenu.cs
  30. 74 0
      SBansheeEditor/Include/BsScriptDropDownWindow.h
  31. 2 0
      SBansheeEditor/SBansheeEditor.vcxproj
  32. 6 0
      SBansheeEditor/SBansheeEditor.vcxproj.filters
  33. 210 0
      SBansheeEditor/Source/BsScriptDropDownWindow.cpp
  34. 2 0
      SBansheeEngine/Source/BsScriptContextMenu.cpp

+ 4 - 0
BansheeEditor/BansheeEditor.vcxproj

@@ -279,6 +279,7 @@
     <ClInclude Include="Include\BsDockManager.h" />
     <ClInclude Include="Include\BsDockManager.h" />
     <ClInclude Include="Include\BsDockManagerLayout.h" />
     <ClInclude Include="Include\BsDockManagerLayout.h" />
     <ClInclude Include="Include\BsDockManagerLayoutRTTI.h" />
     <ClInclude Include="Include\BsDockManagerLayoutRTTI.h" />
+    <ClInclude Include="Include\BsDropDownWindowManager.h" />
     <ClInclude Include="Include\BsEditorApplication.h" />
     <ClInclude Include="Include\BsEditorApplication.h" />
     <ClInclude Include="Include\BsEditorCommand.h" />
     <ClInclude Include="Include\BsEditorCommand.h" />
     <ClInclude Include="Include\BsBuiltinEditorResources.h" />
     <ClInclude Include="Include\BsBuiltinEditorResources.h" />
@@ -291,6 +292,7 @@
     <ClInclude Include="Include\BsGizmoManager.h" />
     <ClInclude Include="Include\BsGizmoManager.h" />
     <ClInclude Include="Include\BsGUIColor.h" />
     <ClInclude Include="Include\BsGUIColor.h" />
     <ClInclude Include="Include\BsGUIColorField.h" />
     <ClInclude Include="Include\BsGUIColorField.h" />
+    <ClInclude Include="Include\BsDropDownWindow.h" />
     <ClInclude Include="Include\BsGUIFieldBase.h" />
     <ClInclude Include="Include\BsGUIFieldBase.h" />
     <ClInclude Include="Include\BsGUIFloatField.h" />
     <ClInclude Include="Include\BsGUIFloatField.h" />
     <ClInclude Include="Include\BsGUIComponentFoldout.h" />
     <ClInclude Include="Include\BsGUIComponentFoldout.h" />
@@ -356,6 +358,8 @@
     <ClCompile Include="Source\BsDbgTestGameObjectRef.cpp" />
     <ClCompile Include="Source\BsDbgTestGameObjectRef.cpp" />
     <ClCompile Include="Source\BsDockManager.cpp" />
     <ClCompile Include="Source\BsDockManager.cpp" />
     <ClCompile Include="Source\BsDockManagerLayout.cpp" />
     <ClCompile Include="Source\BsDockManagerLayout.cpp" />
+    <ClCompile Include="Source\BsDropDownWindow.cpp" />
+    <ClCompile Include="Source\BsDropDownWindowManager.cpp" />
     <ClCompile Include="Source\BsEditorCommand.cpp" />
     <ClCompile Include="Source\BsEditorCommand.cpp" />
     <ClCompile Include="Source\BsBuiltinEditorResources.cpp" />
     <ClCompile Include="Source\BsBuiltinEditorResources.cpp" />
     <ClCompile Include="Source\BsEditorTestSuite.cpp" />
     <ClCompile Include="Source\BsEditorTestSuite.cpp" />

+ 12 - 0
BansheeEditor/BansheeEditor.vcxproj.filters

@@ -279,6 +279,12 @@
     <ClInclude Include="Include\BsShaderIncludeHandler.h">
     <ClInclude Include="Include\BsShaderIncludeHandler.h">
       <Filter>Header Files\Editor</Filter>
       <Filter>Header Files\Editor</Filter>
     </ClInclude>
     </ClInclude>
+    <ClInclude Include="Include\BsDropDownWindow.h">
+      <Filter>Header Files\Editor</Filter>
+    </ClInclude>
+    <ClInclude Include="Include\BsDropDownWindowManager.h">
+      <Filter>Header Files\Editor</Filter>
+    </ClInclude>
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
     <ClCompile Include="Source\BsEditorWidgetContainer.cpp">
     <ClCompile Include="Source\BsEditorWidgetContainer.cpp">
@@ -491,5 +497,11 @@
     <ClCompile Include="Source\BsShaderIncludeHandler.cpp">
     <ClCompile Include="Source\BsShaderIncludeHandler.cpp">
       <Filter>Source Files\Editor</Filter>
       <Filter>Source Files\Editor</Filter>
     </ClCompile>
     </ClCompile>
+    <ClCompile Include="Source\BsDropDownWindowManager.cpp">
+      <Filter>Source Files\Editor</Filter>
+    </ClCompile>
+    <ClCompile Include="Source\BsDropDownWindow.cpp">
+      <Filter>Source Files\Editor</Filter>
+    </ClCompile>
   </ItemGroup>
   </ItemGroup>
 </Project>
 </Project>

+ 82 - 0
BansheeEditor/Include/BsDropDownWindow.h

@@ -0,0 +1,82 @@
+#pragma once
+
+#include "BsEditorPrerequisites.h"
+#include "BsVector2I.h"
+
+namespace BansheeEngine
+{
+	/**
+	 * @brief	This is a generic GUI drop down window class that 
+	 *			can be used for displaying custom drop down content.
+	 */
+	class BS_ED_EXPORT DropDownWindow
+	{
+	public:
+		virtual ~DropDownWindow();
+
+		/**
+		 * @brief	Returns width of the window in pixels.
+		 */
+		UINT32 getWidth() const { return mWidth; }
+		
+		/**
+		 * @brief	Returns height of the window in pixels.
+		 */
+		UINT32 getHeight() const { return mHeight; }
+
+		/**
+		 * @brief	Converts screen pointer coordinates into coordinates relative to the window contents GUI panel.
+		 */
+		Vector2I screenToWindowPos(const Vector2I& screenPos) const;
+
+		/**
+		 * @brief	Converts pointer coordinates relative to the window contents GUI panel into screen coordinates.
+		 */
+		Vector2I windowToScreenPos(const Vector2I& windowPos) const;
+
+		/**
+		 * @brief	Returns the GUI widget used for displaying all GUI contents in the window.
+		 */
+		HGUIWidget getGUIWidget() const { return mGUI; }
+
+		/**
+		 * @brief	Changes the size of the drop down window area.
+		 *			
+		 * @note	This might reposition the window if the new size doesn't fit at the current position.
+		 */
+		void setSize(UINT32 width, UINT32 height);
+
+		/**
+		 * @brief	Closes the drop down window.
+		 */
+		void close();
+
+		/**
+		 * @brief	Called once every frame. Internal method.
+		 */
+		virtual void update() { }
+
+	protected:
+		DropDownWindow(const RenderWindowPtr& parent, Viewport* target,
+			const Vector2I& position, UINT32 width = 200, UINT32 height = 200);
+
+		GUIPanel* mContents;
+
+	private:
+		friend class DropDropWindowManager;
+
+		/**
+		 * @brief	Triggered when the user clicks outside of the drop down area.
+		 */
+		void dropDownFocusLost();
+
+		RenderWindowPtr mRenderWindow;
+		HSceneObject mSceneObject;
+		HGUIWidget mGUI;
+
+		GUIPanel* mRootPanel;
+		Vector2I mPosition;
+		UINT32 mWidth;
+		UINT32 mHeight;
+	};
+}

+ 30 - 0
BansheeEditor/Include/BsDropDownWindowManager.h

@@ -0,0 +1,30 @@
+#pragma once
+
+#include "BsEditorPrerequisites.h"
+#include "BsModule.h"
+
+namespace BansheeEngine
+{
+	class BS_ED_EXPORT DropDownWindowManager : public Module <DropDownWindowManager>
+	{
+	public:
+		DropDownWindowManager();
+		~DropDownWindowManager();
+
+		template<class T, class... Args>
+		T* open(const RenderWindowPtr& parent, Viewport* target,
+			const Vector2I& position, Args &&...args)
+		{
+			close();
+
+			mOpenWindow = bs_new<T>(parent, target, position, std::forward<Args>(args)...);
+			return static_cast<T*>(mOpenWindow);
+		}
+
+		void close();
+
+		void update();
+	protected:
+		DropDownWindow* mOpenWindow;
+	};
+}

+ 1 - 0
BansheeEditor/Include/BsEditorPrerequisites.h

@@ -67,6 +67,7 @@ namespace BansheeEngine
 	class EditorSettings;
 	class EditorSettings;
 	class SceneViewHandler;
 	class SceneViewHandler;
 	class SelectionRenderer;
 	class SelectionRenderer;
+	class DropDownWindow;
 
 
 	typedef std::shared_ptr<ProjectResourceMeta> ProjectResourceMetaPtr;
 	typedef std::shared_ptr<ProjectResourceMeta> ProjectResourceMetaPtr;
 	typedef std::shared_ptr<DockManagerLayout> DockManagerLayoutPtr;
 	typedef std::shared_ptr<DockManagerLayout> DockManagerLayoutPtr;

+ 113 - 0
BansheeEditor/Source/BsDropDownWindow.cpp

@@ -0,0 +1,113 @@
+#include "BsDropDownWindow.h"
+#include "BsSceneObject.h"
+#include "BsGUIWidget.h"
+#include "BsGUIPanel.h"
+#include "BsGUITexture.h"
+#include "BsGUIWindowFrame.h"
+#include "BsDropDownAreaPlacement.h"
+#include "BsBuiltinEditorResources.h"
+#include "BsGUIDropDownHitBox.h"
+#include "BsDropDownWindowManager.h"
+
+namespace BansheeEngine
+{
+	DropDownWindow::DropDownWindow(const RenderWindowPtr& parent, Viewport* target, 
+		const Vector2I& position, UINT32 width, UINT32 height)
+		:mRootPanel(nullptr), mPosition(position), mWidth(width), mHeight(height), 
+		mRenderWindow(parent)
+	{
+		mSceneObject = SceneObject::create("EditorWindow");
+
+		mGUI = mSceneObject->addComponent<GUIWidget>(target);
+
+		mGUI->setDepth(0); // Needs to be in front of everything
+		mGUI->setSkin(BuiltinEditorResources::instance().getSkin());
+
+		mRootPanel = mGUI->getPanel()->addNewElement<GUIPanel>();
+		
+		setSize(width, height);
+
+		GUIPanel* backgroundPanel = mRootPanel->addNewElement<GUIPanel>(500);
+		backgroundPanel->addElement(GUITexture::create(GUIImageScaleMode::RepeatToFit,
+			GUIOptions(GUIOption::flexibleWidth(), GUIOption::flexibleHeight()), "WindowBackground"));
+
+		GUIPanel* windowFramePanel = mRootPanel->addNewElement<GUIPanel>(499);
+
+		GUIWindowFrame* windowFrame = GUIWindowFrame::create("WindowFrame");
+		windowFramePanel->addElement(windowFrame);
+
+		mContents = mRootPanel->addNewElement<GUIPanel>();
+		mContents->setPosition(1, 1);
+		mContents->setWidth(width - 2);
+		mContents->setHeight(height - 2);
+
+
+		GUIPanel* hitBoxPanel = mRootPanel->addNewElement<GUIPanel>(std::numeric_limits<INT16>::min());
+
+		GUIDropDownHitBox* hitBox = GUIDropDownHitBox::create(false);
+		hitBox->onFocusLost.connect(std::bind(&DropDownWindow::dropDownFocusLost, this));
+		hitBox->setFocus(true);
+		hitBoxPanel->addElement(hitBox);
+
+		GUIPanel* captureHitBoxPanel = mGUI->getPanel()->addNewElement<GUIPanel>(std::numeric_limits<INT16>::max());
+
+		GUIDropDownHitBox* captureHitBox = GUIDropDownHitBox::create(true);
+		captureHitBoxPanel->addElement(captureHitBox);
+	}
+
+	DropDownWindow::~DropDownWindow()
+	{
+		mSceneObject->destroy();
+	}
+
+	Vector2I DropDownWindow::screenToWindowPos(const Vector2I& screenPos) const
+	{
+		Vector2I renderWindowPos = mRenderWindow->screenToWindowPos(screenPos);
+
+		Vector2I contentsPos = renderWindowPos;
+		Rect2I contentArea = mContents->getGlobalBounds();
+
+		contentsPos.x -= contentArea.x;
+		contentsPos.y -= contentArea.y;
+
+		return contentsPos;
+	}
+
+	Vector2I DropDownWindow::windowToScreenPos(const Vector2I& windowPos) const
+	{
+		Vector2I contentsPos = windowPos;
+		Rect2I contentArea = mContents->getGlobalBounds();
+
+		contentsPos.x += contentArea.x;
+		contentsPos.y += contentArea.y;
+
+		return mRenderWindow->windowToScreenPos(contentsPos);
+	}
+
+	void DropDownWindow::setSize(UINT32 width, UINT32 height)
+	{
+		Rect2I availableBounds(0, 0, mGUI->getTarget()->getWidth(), mGUI->getTarget()->getHeight());
+		DropDownAreaPlacement dropDownPlacement = DropDownAreaPlacement::aroundPosition(mPosition);
+
+		DropDownAreaPlacement::HorzDir horzDir;
+		DropDownAreaPlacement::VertDir vertDir;
+		Rect2I placementBounds = dropDownPlacement.getOptimalBounds(width, height, availableBounds, horzDir, vertDir);
+
+		mRootPanel->setPosition(placementBounds.x, placementBounds.y);
+		mRootPanel->setWidth(width);
+		mRootPanel->setHeight(height);
+
+		mWidth = width;
+		mHeight = height;
+	}
+
+	void DropDownWindow::close()
+	{
+		DropDownWindowManager::instance().close();
+	}
+
+	void DropDownWindow::dropDownFocusLost()
+	{
+		close();
+	}
+}

+ 29 - 0
BansheeEditor/Source/BsDropDownWindowManager.cpp

@@ -0,0 +1,29 @@
+#include "BsDropDownWindowManager.h"
+#include "BsDropDownWindow.h"
+
+namespace BansheeEngine
+{
+	DropDownWindowManager::DropDownWindowManager()
+		:mOpenWindow(nullptr)
+	{ }
+
+	DropDownWindowManager::~DropDownWindowManager()
+	{
+		close();
+	}
+
+	void DropDownWindowManager::close()
+	{
+		if (mOpenWindow != nullptr)
+		{
+			bs_delete(mOpenWindow);
+			mOpenWindow = nullptr;
+		}
+	}
+
+	void DropDownWindowManager::update()
+	{
+		if (mOpenWindow != nullptr)
+			mOpenWindow->update();
+	}
+}

+ 4 - 0
BansheeEditor/Source/BsEditorApplication.cpp

@@ -16,6 +16,7 @@
 #include "BsBuildManager.h"
 #include "BsBuildManager.h"
 #include "BsScriptCodeImporter.h"
 #include "BsScriptCodeImporter.h"
 #include "BsShaderIncludeHandler.h"
 #include "BsShaderIncludeHandler.h"
+#include "BsDropDownWindowManager.h"
 
 
 // DEBUG ONLY
 // DEBUG ONLY
 #include "DbgEditorWidget1.h"
 #include "DbgEditorWidget1.h"
@@ -80,6 +81,7 @@ namespace BansheeEngine
 		saveWidgetLayout(EditorWidgetManager::instance().getLayout());
 		saveWidgetLayout(EditorWidgetManager::instance().getLayout());
 		// TODO - Save project settings
 		// TODO - Save project settings
 
 
+		DropDownWindowManager::shutDown();
 		EditorWidgetManager::shutDown();
 		EditorWidgetManager::shutDown();
 		EditorWindowManager::shutDown();
 		EditorWindowManager::shutDown();
 		UndoRedo::shutDown();
 		UndoRedo::shutDown();
@@ -143,6 +145,7 @@ namespace BansheeEngine
 		UndoRedo::startUp();
 		UndoRedo::startUp();
 		EditorWindowManager::startUp();
 		EditorWindowManager::startUp();
 		EditorWidgetManager::startUp();
 		EditorWidgetManager::startUp();
+		DropDownWindowManager::startUp();
 
 
 		ScenePicking::startUp();
 		ScenePicking::startUp();
 		Selection::startUp();
 		Selection::startUp();
@@ -249,6 +252,7 @@ namespace BansheeEngine
 		Application::preUpdate();
 		Application::preUpdate();
 
 
 		EditorWidgetManager::instance().update();
 		EditorWidgetManager::instance().update();
+		DropDownWindowManager::instance().update();
 	}
 	}
 
 
 	void EditorApplication::postUpdate()
 	void EditorApplication::postUpdate()

+ 2 - 2
BansheeEditor/Source/BsGUIMenuBar.cpp

@@ -253,9 +253,9 @@ namespace BansheeEngine
 		GUIDropDownData dropDownData = subMenu->menu->getDropDownData();
 		GUIDropDownData dropDownData = subMenu->menu->getDropDownData();
 		GUIWidget* widget = subMenu->button->_getParentWidget();
 		GUIWidget* widget = subMenu->button->_getParentWidget();
 
 
-		GUIDropDownAreaPlacement placement = GUIDropDownAreaPlacement::aroundBoundsHorz(subMenu->button->_getLayoutData().area);
+		DropDownAreaPlacement placement = DropDownAreaPlacement::aroundBoundsHorz(subMenu->button->_getLayoutData().area);
 
 
-		GameObjectHandle<GUIDropDownBox> dropDownBox = GUIDropDownBoxManager::instance().openDropDownBox(widget->getTarget(), 
+		GameObjectHandle<GUIDropDownMenu> dropDownBox = GUIDropDownBoxManager::instance().openDropDownBox(widget->getTarget(), 
 			placement, dropDownData, widget->getSkinResource(), GUIDropDownType::MenuBar, std::bind(&GUIMenuBar::onSubMenuClosed, this));
 			placement, dropDownData, widget->getSkinResource(), GUIDropDownType::MenuBar, std::bind(&GUIMenuBar::onSubMenuClosed, this));
 
 
 		subMenu->button->_setOn(true);
 		subMenu->button->_setOn(true);

+ 4 - 2
BansheeEngine/BansheeEngine.vcxproj

@@ -236,6 +236,7 @@
     <ClCompile Include="Source\BsCursor.cpp" />
     <ClCompile Include="Source\BsCursor.cpp" />
     <ClCompile Include="Source\BsDefaultMeshData.cpp" />
     <ClCompile Include="Source\BsDefaultMeshData.cpp" />
     <ClCompile Include="Source\BsDrawHelper.cpp" />
     <ClCompile Include="Source\BsDrawHelper.cpp" />
+    <ClCompile Include="Source\BsDropDownAreaPlacement.cpp" />
     <ClCompile Include="Source\BsGUIDropDownContent.cpp" />
     <ClCompile Include="Source\BsGUIDropDownContent.cpp" />
     <ClCompile Include="Source\BsGUIElementStyle.cpp" />
     <ClCompile Include="Source\BsGUIElementStyle.cpp" />
     <ClCompile Include="Source\BsGUIPanel.cpp" />
     <ClCompile Include="Source\BsGUIPanel.cpp" />
@@ -263,6 +264,7 @@
     <ClInclude Include="Include\BsCursor.h" />
     <ClInclude Include="Include\BsCursor.h" />
     <ClInclude Include="Include\BsDefaultMeshData.h" />
     <ClInclude Include="Include\BsDefaultMeshData.h" />
     <ClInclude Include="Include\BsDrawHelper.h" />
     <ClInclude Include="Include\BsDrawHelper.h" />
+    <ClInclude Include="Include\BsDropDownAreaPlacement.h" />
     <ClInclude Include="Include\BsGUIDropDownContent.h" />
     <ClInclude Include="Include\BsGUIDropDownContent.h" />
     <ClInclude Include="Include\BsGUIElementStyleRTTI.h" />
     <ClInclude Include="Include\BsGUIElementStyleRTTI.h" />
     <ClInclude Include="Include\BsGUILayoutData.h" />
     <ClInclude Include="Include\BsGUILayoutData.h" />
@@ -292,7 +294,7 @@
     <ClInclude Include="Include\BsGUICommandEvent.h" />
     <ClInclude Include="Include\BsGUICommandEvent.h" />
     <ClInclude Include="Include\BsGUIContent.h" />
     <ClInclude Include="Include\BsGUIContent.h" />
     <ClInclude Include="Include\BsGUIContextMenu.h" />
     <ClInclude Include="Include\BsGUIContextMenu.h" />
-    <ClInclude Include="Include\BsGUIDropDownBox.h" />
+    <ClInclude Include="Include\BsGUIDropDownMenu.h" />
     <ClInclude Include="Include\BsGUIDropDownBoxManager.h" />
     <ClInclude Include="Include\BsGUIDropDownBoxManager.h" />
     <ClInclude Include="Include\BsGUIDropDownHitBox.h" />
     <ClInclude Include="Include\BsGUIDropDownHitBox.h" />
     <ClInclude Include="Include\BsGUIElementContainer.h" />
     <ClInclude Include="Include\BsGUIElementContainer.h" />
@@ -366,7 +368,7 @@
     <ClCompile Include="Source\BsBuiltinResources.cpp" />
     <ClCompile Include="Source\BsBuiltinResources.cpp" />
     <ClCompile Include="Source\BsGUIButton.cpp" />
     <ClCompile Include="Source\BsGUIButton.cpp" />
     <ClCompile Include="Source\BsGUIContent.cpp" />
     <ClCompile Include="Source\BsGUIContent.cpp" />
-    <ClCompile Include="Source\BsGUIDropDownBox.cpp" />
+    <ClCompile Include="Source\BsGUIDropDownMenu.cpp" />
     <ClCompile Include="Source\BsGUIDropDownBoxManager.cpp" />
     <ClCompile Include="Source\BsGUIDropDownBoxManager.cpp" />
     <ClCompile Include="Source\BsGUIDropDownHitBox.cpp" />
     <ClCompile Include="Source\BsGUIDropDownHitBox.cpp" />
     <ClCompile Include="Source\BsGUIElementContainer.cpp" />
     <ClCompile Include="Source\BsGUIElementContainer.cpp" />

+ 12 - 6
BansheeEngine/BansheeEngine.vcxproj.filters

@@ -161,9 +161,6 @@
     <ClInclude Include="Include\BsGUIViewport.h">
     <ClInclude Include="Include\BsGUIViewport.h">
       <Filter>Header Files\GUI</Filter>
       <Filter>Header Files\GUI</Filter>
     </ClInclude>
     </ClInclude>
-    <ClInclude Include="Include\BsGUIDropDownBox.h">
-      <Filter>Header Files\GUI</Filter>
-    </ClInclude>
     <ClInclude Include="Include\BsGUIContent.h">
     <ClInclude Include="Include\BsGUIContent.h">
       <Filter>Header Files\GUI</Filter>
       <Filter>Header Files\GUI</Filter>
     </ClInclude>
     </ClInclude>
@@ -335,6 +332,12 @@
     <ClInclude Include="Include\BsDefaultMeshData.h">
     <ClInclude Include="Include\BsDefaultMeshData.h">
       <Filter>Header Files</Filter>
       <Filter>Header Files</Filter>
     </ClInclude>
     </ClInclude>
+    <ClInclude Include="Include\BsGUIDropDownMenu.h">
+      <Filter>Header Files\GUI</Filter>
+    </ClInclude>
+    <ClInclude Include="Include\BsDropDownAreaPlacement.h">
+      <Filter>Header Files\GUI</Filter>
+    </ClInclude>
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
     <ClCompile Include="Source\BsGUIElement.cpp">
     <ClCompile Include="Source\BsGUIElement.cpp">
@@ -445,9 +448,6 @@
     <ClCompile Include="Source\BsGUIViewport.cpp">
     <ClCompile Include="Source\BsGUIViewport.cpp">
       <Filter>Source Files\GUI</Filter>
       <Filter>Source Files\GUI</Filter>
     </ClCompile>
     </ClCompile>
-    <ClCompile Include="Source\BsGUIDropDownBox.cpp">
-      <Filter>Source Files\GUI</Filter>
-    </ClCompile>
     <ClCompile Include="Source\BsGUIContent.cpp">
     <ClCompile Include="Source\BsGUIContent.cpp">
       <Filter>Source Files\GUI</Filter>
       <Filter>Source Files\GUI</Filter>
     </ClCompile>
     </ClCompile>
@@ -580,5 +580,11 @@
     <ClCompile Include="Source\BsDefaultMeshData.cpp">
     <ClCompile Include="Source\BsDefaultMeshData.cpp">
       <Filter>Source Files</Filter>
       <Filter>Source Files</Filter>
     </ClCompile>
     </ClCompile>
+    <ClCompile Include="Source\BsGUIDropDownMenu.cpp">
+      <Filter>Source Files\GUI</Filter>
+    </ClCompile>
+    <ClCompile Include="Source\BsDropDownAreaPlacement.cpp">
+      <Filter>Source Files\GUI</Filter>
+    </ClCompile>
   </ItemGroup>
   </ItemGroup>
 </Project>
 </Project>

+ 105 - 0
BansheeEngine/Include/BsDropDownAreaPlacement.h

@@ -0,0 +1,105 @@
+#pragma once
+
+#include "BsPrerequisites.h"
+#include "BsVector2I.h"
+#include "BsRect2I.h"
+
+namespace BansheeEngine
+{
+	/**
+	 * @brief	Determines how will the drop down box be positioned. Usually the system will attempt to position
+	 * 			the drop box in a way so all elements can fit, and this class allows you to specify some limitations
+	 * 			on how that works. 
+	 * 			
+	 * @note	For example, list boxes usually want drop down boxes to be placed above or below them, while
+	 * 			context menus may want to have them placed around a single point in any direction.
+	 */
+	class BS_EXPORT DropDownAreaPlacement
+	{
+	public:
+		/**
+		 * @brief	Determines how will the drop down box be positioned.
+		 */
+		enum class Type
+		{
+			Position,
+			BoundsVert,
+			BoundsHorz
+		};
+
+		/**
+		 * @brief	Preferred horizontal direction of the placement bounds, either
+		 *			to the left or to the right of the wanted position/bounds.
+		 */
+		enum class HorzDir
+		{
+			Left, Right
+		};
+
+		/**
+		 * @brief	Preferred horizontal direction of the placement bounds, either
+		 *			upward or downward of the wanted position/bounds.
+		 */
+		enum class VertDir
+		{
+			Up, Down
+		};
+
+		/**
+		 * @brief	Drop down box will be placed at the specified position. By default the system
+		 * 			prefers the top left corner of the box to correspond to the position, but if
+		 * 			other corners offer more space for the contents, those will be used instead.
+		 */
+		static DropDownAreaPlacement aroundPosition(const Vector2I& position);
+
+		/**
+		 * @brief	Drop down box will be placed at the specified bounds. Box will be horizontally aligned to the left
+		 * 			of the provided bounds. Vertically system prefers placing the box at the bottom of the bounds, but may choose
+		 * 			to align it with the top of the bounds if it offers more space for the contents.
+		 */
+		static DropDownAreaPlacement aroundBoundsVert(const Rect2I& bounds);
+		
+		/**
+		 * @brief	Drop down box will be placed at the specified bounds. Box will be vertically aligned to the top
+		 * 			of the provided bounds. Horizontally system prefers placing the box at the right of the bounds, but may choose
+		 * 			to align it with the left of the bounds if it offers more space for the contents.
+		 */
+		static DropDownAreaPlacement aroundBoundsHorz(const Rect2I& bounds);
+
+		/**
+		 * @brief	Returns drop down box positioning type.
+		 */
+		Type getType() const { return mType; }
+
+		/**
+		 * @brief	Returns bounds around which to position the drop down box
+		 *			if one of the bounds positioning types is used.
+		 */
+		const Rect2I& getBounds() const { return mBounds; }
+
+		/**
+		 * @brief	Returns position around which to position the drop down box
+		 *			if position positioning type is used.
+		 */
+		const Vector2I& getPosition() const { return mPosition; }
+
+		/**
+		 * @brief	Calculates the optimal bounds to place an element of the specified size, within
+		 *			the available area using the internal data as a guide.
+		 *
+		 * @param	width	Width of the element to try to position, in pixels.
+		 * @param	height	Height of the element to try to position, in pixels.
+		 * @param	Rect2I	Available area to try to position the element in, in pixels.
+		 * @param	horzDir	Output parameter that signals the preferred horizontal direction of the bounds (left or right).
+		 * @param	vertDir	Output parameter that signals the preferred vertical direction of the bounds (up or down).
+		 */
+		Rect2I getOptimalBounds(UINT32 width, UINT32 height, const Rect2I& availableArea, HorzDir& horzDir, VertDir& vertDir) const;
+
+	private:
+		DropDownAreaPlacement() { }
+
+		Type mType;
+		Rect2I mBounds;
+		Vector2I mPosition;
+	};
+}

+ 3 - 3
BansheeEngine/Include/BsGUIDropDownBoxManager.h

@@ -1,7 +1,7 @@
 #pragma once
 #pragma once
 
 
 #include "BsPrerequisites.h"
 #include "BsPrerequisites.h"
-#include "BsGUIDropDownBox.h"
+#include "BsGUIDropDownMenu.h"
 #include "BsModule.h"
 #include "BsModule.h"
 
 
 namespace BansheeEngine
 namespace BansheeEngine
@@ -25,7 +25,7 @@ namespace BansheeEngine
 		 * @param	type				Specific type of drop down box to display.
 		 * @param	type				Specific type of drop down box to display.
 		 * @param	onClosedCallback	Callback triggered when drop down box is closed.
 		 * @param	onClosedCallback	Callback triggered when drop down box is closed.
 		 */
 		 */
-		GameObjectHandle<GUIDropDownBox> openDropDownBox(Viewport* target, const GUIDropDownAreaPlacement& placement,
+		GameObjectHandle<GUIDropDownMenu> openDropDownBox(Viewport* target, const DropDownAreaPlacement& placement,
 			const GUIDropDownData& dropDownData, const HGUISkin& skin, GUIDropDownType type, std::function<void()> onClosedCallback);
 			const GUIDropDownData& dropDownData, const HGUISkin& skin, GUIDropDownType type, std::function<void()> onClosedCallback);
 
 
 		/**
 		/**
@@ -35,7 +35,7 @@ namespace BansheeEngine
 
 
 	private:
 	private:
 		HSceneObject mDropDownSO;
 		HSceneObject mDropDownSO;
-		GameObjectHandle<GUIDropDownBox> mDropDownBox;
+		GameObjectHandle<GUIDropDownMenu> mDropDownBox;
 		std::function<void()> mOnClosedCallback;
 		std::function<void()> mOnClosedCallback;
 	};
 	};
 }
 }

+ 5 - 5
BansheeEngine/Include/BsGUIDropDownContent.h

@@ -2,7 +2,7 @@
 
 
 #include "BsPrerequisites.h"
 #include "BsPrerequisites.h"
 #include "BsGUIElementContainer.h"
 #include "BsGUIElementContainer.h"
-#include "BsGUIDropDownBox.h"
+#include "BsGUIDropDownMenu.h"
 
 
 namespace BansheeEngine
 namespace BansheeEngine
 {
 {
@@ -37,7 +37,7 @@ namespace BansheeEngine
 		 *							from GUISkin of the GUIWidget the element is used on. If not specified
 		 *							from GUISkin of the GUIWidget the element is used on. If not specified
 		 *							default button style is used.
 		 *							default button style is used.
 		 */
 		 */
-		static GUIDropDownContent* create(GUIDropDownBox::DropDownSubMenu* parent, const GUIDropDownData& dropDownData,
+		static GUIDropDownContent* create(GUIDropDownMenu::DropDownSubMenu* parent, const GUIDropDownData& dropDownData,
 			const String& style = StringUtil::BLANK);
 			const String& style = StringUtil::BLANK);
 
 
 		/**
 		/**
@@ -51,7 +51,7 @@ namespace BansheeEngine
 		 *							from GUISkin of the GUIWidget the element is used on. If not specified
 		 *							from GUISkin of the GUIWidget the element is used on. If not specified
 		 *							default button style is used.
 		 *							default button style is used.
 		 */
 		 */
-		static GUIDropDownContent* create(GUIDropDownBox::DropDownSubMenu* parent, const GUIDropDownData& dropDownData, 
+		static GUIDropDownContent* create(GUIDropDownMenu::DropDownSubMenu* parent, const GUIDropDownData& dropDownData, 
 			const GUIOptions& options, const String& style = StringUtil::BLANK);
 			const GUIOptions& options, const String& style = StringUtil::BLANK);
 
 
 		/**
 		/**
@@ -76,7 +76,7 @@ namespace BansheeEngine
 		static const String ENTRY_EXP_STYLE_TYPE;
 		static const String ENTRY_EXP_STYLE_TYPE;
 		static const String SEPARATOR_STYLE_TYPE;
 		static const String SEPARATOR_STYLE_TYPE;
 	protected:
 	protected:
-		GUIDropDownContent(GUIDropDownBox::DropDownSubMenu* parent, const GUIDropDownData& dropDownData, 
+		GUIDropDownContent(GUIDropDownMenu::DropDownSubMenu* parent, const GUIDropDownData& dropDownData, 
 			const String& style, const GUIDimensions& dimensions);
 			const String& style, const GUIDimensions& dimensions);
 		~GUIDropDownContent() override;
 		~GUIDropDownContent() override;
 
 
@@ -129,7 +129,7 @@ namespace BansheeEngine
 		Vector<VisibleElement> mVisibleElements;
 		Vector<VisibleElement> mVisibleElements;
 		UINT32 mSelectedIdx;
 		UINT32 mSelectedIdx;
 		UINT32 mRangeStart, mRangeEnd;
 		UINT32 mRangeStart, mRangeEnd;
-		GUIDropDownBox::DropDownSubMenu* mParent;
+		GUIDropDownMenu::DropDownSubMenu* mParent;
 		bool mKeyboardFocus;
 		bool mKeyboardFocus;
 	};
 	};
 }
 }

+ 1 - 1
BansheeEngine/Include/BsGUIDropDownHitBox.h

@@ -9,7 +9,7 @@ namespace BansheeEngine
 	 * Helper class used for detecting when a certain area is in focus,
 	 * Helper class used for detecting when a certain area is in focus,
 	 * and getting notified when that state changes.
 	 * and getting notified when that state changes.
 	 */
 	 */
-	class GUIDropDownHitBox : public GUIElementContainer
+	class BS_EXPORT GUIDropDownHitBox : public GUIElementContainer
 	{
 	{
 	public:
 	public:
 		/**
 		/**

+ 285 - 351
BansheeEngine/Include/BsGUIDropDownBox.h → BansheeEngine/Include/BsGUIDropDownMenu.h

@@ -1,352 +1,286 @@
-#pragma once
-
-#include "BsPrerequisites.h"
-#include "BsGUIWidget.h"
-#include "BsVector2I.h"
-#include "BsEvent.h"
-
-namespace BansheeEngine
-{
-	/**
-	 * @brief	Contains items used for initializing one level in a drop down box hierarchy.
-	 */
-	struct BS_EXPORT GUIDropDownData
-	{
-		Vector<GUIDropDownDataEntry> entries;
-		UnorderedMap<WString, HString> localizedNames;
-	};
-
-	/**
-	 * @brief	Represents a single entry in a drop down box.
-	 */
-	class BS_EXPORT GUIDropDownDataEntry
-	{
-		enum class Type
-		{
-			Separator,
-			Entry,
-			SubMenu
-		};
-
-	public:
-		/**
-		 * @brief	Creates a new separator entry.
-		 */
-		static GUIDropDownDataEntry separator();
-
-		/**
-		 * @brief	Creates a new button entry with the specified callback that is triggered
-		 *			when button is selected.
-		 */
-		static GUIDropDownDataEntry button(const WString& label, std::function<void()> callback, const WString& shortcutTag = StringUtil::WBLANK);
-
-		/**
-		 * @brief	Creates a new sub-menu entry that will open the provided drop down data
-		 *			sub-menu when activated.
-		 */
-		static GUIDropDownDataEntry subMenu(const WString& label, const GUIDropDownData& data);
-
-		/**
-		 * @brief	Check is the entry a separator.
-		 */
-		bool isSeparator() const { return mType == Type::Separator; }
-
-		/**
-		 * @brief	Check is the entry a sub menu.
-		 */
-		bool isSubMenu() const { return mType == Type::SubMenu; }
-
-		/**
-		 * @brief	Returns display label of the entry (if an entry is a button or a sub-menu).
-		 */
-		const WString& getLabel() const { return mLabel; }
-
-		/**
-		 * @brief	Returns the shortcut key combination string that is to be displayed along the entry label.
-		 */
-		const WString& getShortcutTag() const { return mShortcutTag; }
-
-		/**
-		 * @brief	Returns a button callback if the entry (if an entry is a button).
-		 */
-		std::function<void()> getCallback() const { return mCallback; }
-
-		/**
-		 * @brief	Returns sub-menu data that is used for creating a sub-menu (if an entry is a sub-menu).
-		 */
-		const GUIDropDownData& getSubMenuData() const { return mChildData; }
-	private:
-		GUIDropDownDataEntry() { }
-
-		std::function<void()> mCallback;
-		GUIDropDownData mChildData;
-		WString mLabel;
-		WString mShortcutTag;
-		Type mType; 
-	};
-
-	/**
-	 * @brief	Determines how will the drop down box be positioned. Usually the system will attempt to position
-	 * 			the drop box in a way so all elements can fit, and this class allows you to specify some limitations
-	 * 			on how that works. 
-	 * 			
-	 * @note	For example, list boxes usually want drop down boxes to be placed above or below them, while
-	 * 			context menus may want to have them placed around a single point in any direction.
-	 */
-	class BS_EXPORT GUIDropDownAreaPlacement
-	{
-	public:
-		/**
-		 * @brief	Determines how will the drop down box be positioned.
-		 */
-		enum class Type
-		{
-			Position,
-			BoundsVert,
-			BoundsHorz
-		};
-
-		/**
-		 * @brief	Drop down box will be placed at the specified position. By default the system
-		 * 			prefers the top left corner of the box to correspond to the position, but if
-		 * 			other corners offer more space for the contents, those will be used instead.
-		 */
-		static GUIDropDownAreaPlacement aroundPosition(const Vector2I& position);
-
-		/**
-		 * @brief	Drop down box will be placed at the specified bounds. Box will be horizontally aligned to the left
-		 * 			of the provided bounds. Vertically system prefers placing the box at the bottom of the bounds, but may choose
-		 * 			to align it with the top of the bounds if it offers more space for the contents.
-		 */
-		static GUIDropDownAreaPlacement aroundBoundsVert(const Rect2I& bounds);
-		
-		/**
-		 * @brief	Drop down box will be placed at the specified bounds. Box will be vertically aligned to the top
-		 * 			of the provided bounds. Horizontally system prefers placing the box at the right of the bounds, but may choose
-		 * 			to align it with the left of the bounds if it offers more space for the contents.
-		 */
-		static GUIDropDownAreaPlacement aroundBoundsHorz(const Rect2I& bounds);
-
-		/**
-		 * @brief	Returns drop down box positioning type.
-		 */
-		Type getType() const { return mType; }
-
-		/**
-		 * @brief	Returns bounds around which to position the drop down box
-		 *			if one of the bounds positioning types is used.
-		 */
-		const Rect2I& getBounds() const { return mBounds; }
-
-		/**
-		 * @brief	Returns position around which to position the drop down box
-		 *			if position positioning type is used.
-		 */
-		const Vector2I& getPosition() const { return mPosition; }
-
-	private:
-		GUIDropDownAreaPlacement() { }
-
-		Type mType;
-		Rect2I mBounds;
-		Vector2I mPosition;
-	};
-
-	/**
-	 * @brief	Type of drop down box types.
-	 */
-	enum class GUIDropDownType
-	{
-		ListBox,
-		ContextMenu,
-		MenuBar
-	};
-
-	/**
-	 * @brief	This is a generic GUI drop down box class that can be used for:
-	 * 			list boxes, menu bars or context menus.
-	 */
-	class BS_EXPORT GUIDropDownBox : public GUIWidget
-	{
-	public:
-		/**
-		 * @brief	Creates a new drop down box widget.
-		 *
-		 * @param	parent			Parent scene object to attach the drop down box to.
-		 * @param	target			Viewport on which to open the drop down box.
-		 * @param	placement		Determines how is the drop down box positioned in the visible area.
-		 * @param	dropDownData	Data to use for initializing menu items of the drop down box.
-		 * @param	skin			Skin to use for drop down box GUI elements.
-		 * @param	type			Specific type of drop down box to display.
-		 */
-		GUIDropDownBox(const HSceneObject& parent, Viewport* target, const GUIDropDownAreaPlacement& placement,
-			const GUIDropDownData& dropDownData, const HGUISkin& skin, GUIDropDownType type);
-		~GUIDropDownBox();
-
-	private:
-		/**
-		 * @brief	Contains data about a single drop down box sub-menu
-		 */
-		struct DropDownSubMenu
-		{
-			/**
-			 * @brief	Represents a single sub-menu page.
-			 */
-			struct PageInfo
-			{
-				UINT32 idx;
-				UINT32 start;
-				UINT32 end;
-				UINT32 height;
-			};
-
-		public:
-			/**
-			 * @brief	Creates a new drop down box sub-menu
-			 *
-			 * @param	owner			Owner drop down box this sub menu belongs to.
-			 * @param	parent			Parent sub-menu. Can be null.
-			 * @param	placement		Determines how is the sub-menu positioned in the visible area.
-			 * @param	availableBounds	Available bounds (in pixels) in which the sub-menu may be opened.
-			 * @param	dropDownData	Data to use for initializing menu items of the sub-menu.
-			 * @param	skin			Skin to use for sub-menu GUI elements.
-			 * @param	depthOffset		How much to offset the sub-menu depth. We want deeper levels of the
-			 *							sub-menu hierarchy to be in front of lower levels, so you should
-			 *							increase this value for each level of the sub-menu hierarchy.
-			 */
-			DropDownSubMenu(GUIDropDownBox* owner, DropDownSubMenu* parent, const GUIDropDownAreaPlacement& placement, 
-				const Rect2I& availableBounds, const GUIDropDownData& dropDownData, GUIDropDownType type, UINT32 depthOffset);
-			~DropDownSubMenu();
-
-			/**
-			 * @brief	Recreates all internal GUI elements for the entries of the current sub-menu page.
-			 */
-			void updateGUIElements();
-
-			/**
-			 * @brief	Moves the sub-menu to the previous page and displays its elements, if available.
-			 */
-			void scrollDown();
-
-			/**
-			 * @brief	Moves the sub-menu to the next page and displays its elements, if available.
-			 */
-			void scrollUp();
-
-			/**
-			 * @brief	Moves the sub-menu to the first page and displays its elements.
-			 */
-			void scrollToTop();
-
-			/**
-			 * @brief	Moves the sub-menu to the last page and displays its elements.
-			 */
-			void scrollToBottom();
-
-			/**
-			 * @brief	Calculates ranges for all the pages of the sub-menu.
-			 */
-			Vector<PageInfo> getPageInfos() const;
-
-			/**
-			 * @brief	Called when the user activates an element with the specified index.
-			 *
-			 * @param	bounds	Bounds of the GUI element that is used as a visual representation
-			 *					of this drop down element.
-			 */
-			void elementActivated(UINT32 idx, const Rect2I& bounds);
-
-			/**
-			 * @brief	Called when the user selects an element with the specified index.
-			 * 
-			 * @param	idx		Index of the element that was selected.
-			 */
-			void elementSelected(UINT32 idx);
-
-			/**
-			 * @brief	Called when the user wants to close the currently open sub-menu.
-			 */
-			void closeSubMenu();
-
-			/**
-			 * @brief	Closes this sub-menu.
-			 */
-			void close();
-
-			/**
-			 * @brief	Returns actual visible bounds of the sub-menu.
-			 */
-			Rect2I getVisibleBounds() const { return mVisibleBounds; }
-
-			/**
-			 * @brief	Returns the drop box object that owns this sub-menu.
-			 */
-			GUIDropDownBox* getOwner() const { return mOwner; }
-
-		public:
-			GUIDropDownBox* mOwner;
-
-			GUIDropDownType mType;
-			GUIDropDownData mData;
-			UINT32 mPage;
-			INT32 x, y;
-			UINT32 width, height;
-			Rect2I mVisibleBounds;
-			Rect2I mAvailableBounds;
-			UINT32 mDepthOffset;
-			bool mOpenedUpward;
-
-			GUIButton* mScrollUpBtn;
-			GUIButton* mScrollDownBtn;
-			GUIDropDownContent* mContent;
-			GUITexture* mBackgroundFrame;
-
-			GUIPanel* mBackgroundPanel;
-			GUIPanel* mContentPanel;
-			GUILayout* mContentLayout;
-
-			DropDownSubMenu* mParent;
-			DropDownSubMenu* mSubMenu;
-		};
-
-	private:
-		friend class GUIDropDownContent;
-
-		/**
-		 * @brief	Called when the specified sub-menu is opened.
-		 */
-		void notifySubMenuOpened(DropDownSubMenu* subMenu);
-
-		/**
-		 * @brief	Called when the specified sub-menu is opened.
-		 */
-		void notifySubMenuClosed(DropDownSubMenu* subMenu);
-
-		/**
-		 * @brief	Called when the drop down box loses focus (and should be closed).
-		 */
-		void dropDownFocusLost();
-
-		/**
-		 * @copydoc	GUIWidget::onDestroyed
-		 */
-		void onDestroyed() override;
-
-	private:
-		static const UINT32 DROP_DOWN_BOX_WIDTH;
-
-		String mScrollUpStyle;
-		String mScrollDownStyle;
-		String mBackgroundStyle;
-		String mContentStyle;
-		HSpriteTexture mScrollUpBtnArrow;
-		HSpriteTexture mScrollDownBtnArrow;
-
-		DropDownSubMenu* mRootMenu;
-		GUIDropDownHitBox* mHitBox;
-		// Captures mouse clicks so that we don't trigger elements outside the drop down box when we just want to close it.
-		// (Particular example is clicking on the button that opened the drop down box in the first place. Clicking will cause
-		// the drop down to lose focus and close, but if the button still processes the mouse click it will be immediately opened again)
-		GUIDropDownHitBox* mCaptureHitBox; 
-	};
+#pragma once
+
+#include "BsPrerequisites.h"
+#include "BsGUIWidget.h"
+#include "BsVector2I.h"
+#include "BsEvent.h"
+#include "BsDropDownAreaPlacement.h"
+
+namespace BansheeEngine
+{
+	/**
+	 * @brief	Contains items used for initializing one level in a drop down box hierarchy.
+	 */
+	struct BS_EXPORT GUIDropDownData
+	{
+		Vector<GUIDropDownDataEntry> entries;
+		UnorderedMap<WString, HString> localizedNames;
+	};
+
+	/**
+	 * @brief	Represents a single entry in a drop down box.
+	 */
+	class BS_EXPORT GUIDropDownDataEntry
+	{
+		enum class Type
+		{
+			Separator,
+			Entry,
+			SubMenu
+		};
+
+	public:
+		/**
+		 * @brief	Creates a new separator entry.
+		 */
+		static GUIDropDownDataEntry separator();
+
+		/**
+		 * @brief	Creates a new button entry with the specified callback that is triggered
+		 *			when button is selected.
+		 */
+		static GUIDropDownDataEntry button(const WString& label, std::function<void()> callback, const WString& shortcutTag = StringUtil::WBLANK);
+
+		/**
+		 * @brief	Creates a new sub-menu entry that will open the provided drop down data
+		 *			sub-menu when activated.
+		 */
+		static GUIDropDownDataEntry subMenu(const WString& label, const GUIDropDownData& data);
+
+		/**
+		 * @brief	Check is the entry a separator.
+		 */
+		bool isSeparator() const { return mType == Type::Separator; }
+
+		/**
+		 * @brief	Check is the entry a sub menu.
+		 */
+		bool isSubMenu() const { return mType == Type::SubMenu; }
+
+		/**
+		 * @brief	Returns display label of the entry (if an entry is a button or a sub-menu).
+		 */
+		const WString& getLabel() const { return mLabel; }
+
+		/**
+		 * @brief	Returns the shortcut key combination string that is to be displayed along the entry label.
+		 */
+		const WString& getShortcutTag() const { return mShortcutTag; }
+
+		/**
+		 * @brief	Returns a button callback if the entry (if an entry is a button).
+		 */
+		std::function<void()> getCallback() const { return mCallback; }
+
+		/**
+		 * @brief	Returns sub-menu data that is used for creating a sub-menu (if an entry is a sub-menu).
+		 */
+		const GUIDropDownData& getSubMenuData() const { return mChildData; }
+	private:
+		GUIDropDownDataEntry() { }
+
+		std::function<void()> mCallback;
+		GUIDropDownData mChildData;
+		WString mLabel;
+		WString mShortcutTag;
+		Type mType; 
+	};
+
+	/**
+	 * @brief	Type of drop down box types.
+	 */
+	enum class GUIDropDownType
+	{
+		ListBox,
+		ContextMenu,
+		MenuBar
+	};
+
+	/**
+	 * @brief	This is a generic GUI drop down box class that can be used for:
+	 * 			list boxes, menu bars or context menus.
+	 */
+	class BS_EXPORT GUIDropDownMenu : public GUIWidget
+	{
+	public:
+		/**
+		 * @brief	Creates a new drop down box widget.
+		 *
+		 * @param	parent			Parent scene object to attach the drop down box to.
+		 * @param	target			Viewport on which to open the drop down box.
+		 * @param	placement		Determines how is the drop down box positioned in the visible area.
+		 * @param	dropDownData	Data to use for initializing menu items of the drop down box.
+		 * @param	skin			Skin to use for drop down box GUI elements.
+		 * @param	type			Specific type of drop down box to display.
+		 */
+		GUIDropDownMenu(const HSceneObject& parent, Viewport* target, const DropDownAreaPlacement& placement,
+			const GUIDropDownData& dropDownData, const HGUISkin& skin, GUIDropDownType type);
+		~GUIDropDownMenu();
+
+	private:
+		/**
+		 * @brief	Contains data about a single drop down box sub-menu
+		 */
+		struct DropDownSubMenu
+		{
+			/**
+			 * @brief	Represents a single sub-menu page.
+			 */
+			struct PageInfo
+			{
+				UINT32 idx;
+				UINT32 start;
+				UINT32 end;
+				UINT32 height;
+			};
+
+		public:
+			/**
+			 * @brief	Creates a new drop down box sub-menu
+			 *
+			 * @param	owner			Owner drop down box this sub menu belongs to.
+			 * @param	parent			Parent sub-menu. Can be null.
+			 * @param	placement		Determines how is the sub-menu positioned in the visible area.
+			 * @param	availableBounds	Available bounds (in pixels) in which the sub-menu may be opened.
+			 * @param	dropDownData	Data to use for initializing menu items of the sub-menu.
+			 * @param	skin			Skin to use for sub-menu GUI elements.
+			 * @param	depthOffset		How much to offset the sub-menu depth. We want deeper levels of the
+			 *							sub-menu hierarchy to be in front of lower levels, so you should
+			 *							increase this value for each level of the sub-menu hierarchy.
+			 */
+			DropDownSubMenu(GUIDropDownMenu* owner, DropDownSubMenu* parent, const DropDownAreaPlacement& placement, 
+				const Rect2I& availableBounds, const GUIDropDownData& dropDownData, GUIDropDownType type, UINT32 depthOffset);
+			~DropDownSubMenu();
+
+			/**
+			 * @brief	Recreates all internal GUI elements for the entries of the current sub-menu page.
+			 */
+			void updateGUIElements();
+
+			/**
+			 * @brief	Moves the sub-menu to the previous page and displays its elements, if available.
+			 */
+			void scrollDown();
+
+			/**
+			 * @brief	Moves the sub-menu to the next page and displays its elements, if available.
+			 */
+			void scrollUp();
+
+			/**
+			 * @brief	Moves the sub-menu to the first page and displays its elements.
+			 */
+			void scrollToTop();
+
+			/**
+			 * @brief	Moves the sub-menu to the last page and displays its elements.
+			 */
+			void scrollToBottom();
+
+			/**
+			 * @brief	Calculates ranges for all the pages of the sub-menu.
+			 */
+			Vector<PageInfo> getPageInfos() const;
+
+			/**
+			 * @brief	Called when the user activates an element with the specified index.
+			 *
+			 * @param	bounds	Bounds of the GUI element that is used as a visual representation
+			 *					of this drop down element.
+			 */
+			void elementActivated(UINT32 idx, const Rect2I& bounds);
+
+			/**
+			 * @brief	Called when the user selects an element with the specified index.
+			 * 
+			 * @param	idx		Index of the element that was selected.
+			 */
+			void elementSelected(UINT32 idx);
+
+			/**
+			 * @brief	Called when the user wants to close the currently open sub-menu.
+			 */
+			void closeSubMenu();
+
+			/**
+			 * @brief	Closes this sub-menu.
+			 */
+			void close();
+
+			/**
+			 * @brief	Returns actual visible bounds of the sub-menu.
+			 */
+			Rect2I getVisibleBounds() const { return mVisibleBounds; }
+
+			/**
+			 * @brief	Returns the drop box object that owns this sub-menu.
+			 */
+			GUIDropDownMenu* getOwner() const { return mOwner; }
+
+		public:
+			GUIDropDownMenu* mOwner;
+
+			GUIDropDownType mType;
+			GUIDropDownData mData;
+			UINT32 mPage;
+			INT32 x, y;
+			UINT32 width, height;
+			Rect2I mVisibleBounds;
+			Rect2I mAvailableBounds;
+			UINT32 mDepthOffset;
+			bool mOpenedUpward;
+
+			GUIButton* mScrollUpBtn;
+			GUIButton* mScrollDownBtn;
+			GUIDropDownContent* mContent;
+			GUITexture* mBackgroundFrame;
+
+			GUIPanel* mBackgroundPanel;
+			GUIPanel* mContentPanel;
+			GUILayout* mContentLayout;
+
+			DropDownSubMenu* mParent;
+			DropDownSubMenu* mSubMenu;
+		};
+
+	private:
+		friend class GUIDropDownContent;
+
+		/**
+		 * @brief	Called when the specified sub-menu is opened.
+		 */
+		void notifySubMenuOpened(DropDownSubMenu* subMenu);
+
+		/**
+		 * @brief	Called when the specified sub-menu is opened.
+		 */
+		void notifySubMenuClosed(DropDownSubMenu* subMenu);
+
+		/**
+		 * @brief	Called when the drop down box loses focus (and should be closed).
+		 */
+		void dropDownFocusLost();
+
+		/**
+		 * @copydoc	GUIWidget::onDestroyed
+		 */
+		void onDestroyed() override;
+
+	private:
+		static const UINT32 DROP_DOWN_BOX_WIDTH;
+
+		String mScrollUpStyle;
+		String mScrollDownStyle;
+		String mBackgroundStyle;
+		String mContentStyle;
+		HSpriteTexture mScrollUpBtnArrow;
+		HSpriteTexture mScrollDownBtnArrow;
+
+		DropDownSubMenu* mRootMenu;
+		GUIDropDownHitBox* mHitBox;
+		// Captures mouse clicks so that we don't trigger elements outside the drop down box when we just want to close it.
+		// (Particular example is clicking on the button that opened the drop down box in the first place. Clicking will cause
+		// the drop down to lose focus and close, but if the button still processes the mouse click it will be immediately opened again)
+		GUIDropDownHitBox* mCaptureHitBox; 
+	};
 }
 }

+ 1 - 1
BansheeEngine/Include/BsGUIMenu.h

@@ -1,7 +1,7 @@
 #pragma once
 #pragma once
 
 
 #include "BsPrerequisites.h"
 #include "BsPrerequisites.h"
-#include "BsGUIDropDownBox.h"
+#include "BsGUIDropDownMenu.h"
 #include "BsShortcutKey.h"
 #include "BsShortcutKey.h"
 
 
 namespace BansheeEngine
 namespace BansheeEngine

+ 1 - 1
BansheeEngine/Include/BsPrerequisites.h

@@ -68,7 +68,7 @@ namespace BansheeEngine
 	class GUIToggleGroup;
 	class GUIToggleGroup;
 	class GUIListBox;
 	class GUIListBox;
 	class GUIDropDownDataEntry;
 	class GUIDropDownDataEntry;
-	class GUIDropDownBox;
+	class GUIDropDownMenu;
 	class DragAndDropManager;
 	class DragAndDropManager;
 	class GUIMenu;
 	class GUIMenu;
 	class GUIMenuItem;
 	class GUIMenuItem;

+ 117 - 0
BansheeEngine/Source/BsDropDownAreaPlacement.cpp

@@ -0,0 +1,117 @@
+#include "BsDropDownAreaPlacement.h"
+
+namespace BansheeEngine
+{
+	DropDownAreaPlacement DropDownAreaPlacement::aroundPosition(const Vector2I& position)
+	{
+		DropDownAreaPlacement instance;
+		instance.mType = Type::Position;
+		instance.mPosition = position;
+
+		return instance;
+	}
+
+	DropDownAreaPlacement DropDownAreaPlacement::aroundBoundsVert(const Rect2I& bounds)
+	{
+		DropDownAreaPlacement instance;
+		instance.mType = Type::BoundsVert;
+		instance.mBounds = bounds;
+
+		return instance;
+	}
+
+	DropDownAreaPlacement DropDownAreaPlacement::aroundBoundsHorz(const Rect2I& bounds)
+	{
+		DropDownAreaPlacement instance;
+		instance.mType = Type::BoundsHorz;
+		instance.mBounds = bounds;
+
+		return instance;
+	}
+
+	Rect2I DropDownAreaPlacement::getOptimalBounds(UINT32 width, UINT32 height, const Rect2I& availableArea, HorzDir& horzDir, VertDir& vertDir) const
+	{
+		Rect2I output;
+
+		int potentialLeftStart = 0;
+		int potentialRightStart = 0;
+		int potentialTopStart = 0;
+		int potentialBottomStart = 0;
+
+		switch (getType())
+		{
+		case DropDownAreaPlacement::Type::Position:
+			potentialLeftStart = potentialRightStart = getPosition().x;
+			potentialTopStart = potentialBottomStart = getPosition().y;
+			break;
+		case DropDownAreaPlacement::Type::BoundsHorz:
+			potentialRightStart = getBounds().x;
+			potentialLeftStart = getBounds().x + getBounds().width;
+			potentialBottomStart = getBounds().y + getBounds().height;
+			potentialTopStart = getBounds().y;
+			break;
+		case DropDownAreaPlacement::Type::BoundsVert:
+			potentialRightStart = getBounds().x + getBounds().width;
+			potentialLeftStart = getBounds().x;
+			potentialBottomStart = getBounds().y;
+			potentialTopStart = getBounds().y + 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, (availableArea.x + availableArea.width) - potentialRightStart);
+		UINT32 availableLeftwardWidth = (UINT32)std::max(0, potentialLeftStart - availableArea.x);
+
+		//// Prefer right if possible
+		if (width <= availableRightwardWidth)
+		{
+			output.x = potentialRightStart;
+			output.width = width;
+			horzDir = HorzDir::Right;
+		}
+		else
+		{
+			if (availableRightwardWidth >= availableLeftwardWidth)
+			{
+				output.x = potentialRightStart;
+				output.width = std::min(width, availableRightwardWidth);
+				horzDir = HorzDir::Right;
+			}
+			else
+			{
+				output.x = potentialLeftStart - std::min(width, availableLeftwardWidth);
+				output.width = std::min(width, availableLeftwardWidth);
+				horzDir = HorzDir::Left;
+			}
+		}
+
+		// Determine y position and whether to open upward or downward
+		UINT32 availableDownwardHeight = (UINT32)std::max(0, (availableArea.y + availableArea.height) - potentialBottomStart);
+		UINT32 availableUpwardHeight = (UINT32)std::max(0, potentialTopStart - availableArea.y);
+
+		//// Prefer down if possible
+		if (height <= availableDownwardHeight)
+		{
+			output.y = potentialBottomStart;
+			output.height = availableDownwardHeight;
+			vertDir = VertDir::Down;
+		}
+		else
+		{
+			if (availableDownwardHeight >= availableUpwardHeight)
+			{
+				output.y = potentialBottomStart;
+				output.height = availableDownwardHeight;
+				vertDir = VertDir::Down;
+			}
+			else
+			{
+				output.y = potentialTopStart - (INT32)std::min(height, availableUpwardHeight);
+				output.height = availableUpwardHeight;
+				vertDir = VertDir::Up;
+			}
+		}
+
+		return output;
+	}
+}

+ 2 - 2
BansheeEngine/Source/BsGUIContextMenu.cpp

@@ -17,9 +17,9 @@ namespace BansheeEngine
 
 
 	void GUIContextMenu::open(const Vector2I& position, GUIWidget& widget)
 	void GUIContextMenu::open(const Vector2I& position, GUIWidget& widget)
 	{
 	{
-		GUIDropDownAreaPlacement placement = GUIDropDownAreaPlacement::aroundPosition(position);
+		DropDownAreaPlacement placement = DropDownAreaPlacement::aroundPosition(position);
 
 
-		GameObjectHandle<GUIDropDownBox> dropDownBox = GUIDropDownBoxManager::instance().openDropDownBox(widget.getTarget(), 
+		GameObjectHandle<GUIDropDownMenu> dropDownBox = GUIDropDownBoxManager::instance().openDropDownBox(widget.getTarget(), 
 			placement, getDropDownData(), widget.getSkinResource(), GUIDropDownType::ContextMenu, std::bind(&GUIContextMenu::onMenuClosed, this));
 			placement, getDropDownData(), widget.getSkinResource(), GUIDropDownType::ContextMenu, std::bind(&GUIContextMenu::onMenuClosed, this));
 
 
 		mContextMenuOpen = true;
 		mContextMenuOpen = true;

+ 2 - 2
BansheeEngine/Source/BsGUIDropDownBoxManager.cpp

@@ -8,13 +8,13 @@ namespace BansheeEngine
 		closeDropDownBox();
 		closeDropDownBox();
 	}
 	}
 
 
-	GameObjectHandle<GUIDropDownBox> GUIDropDownBoxManager::openDropDownBox(Viewport* target, const GUIDropDownAreaPlacement& placement,
+	GameObjectHandle<GUIDropDownMenu> GUIDropDownBoxManager::openDropDownBox(Viewport* target, const DropDownAreaPlacement& placement,
 		const GUIDropDownData& dropDownData, const HGUISkin& skin, GUIDropDownType type, std::function<void()> onClosedCallback)
 		const GUIDropDownData& dropDownData, const HGUISkin& skin, GUIDropDownType type, std::function<void()> onClosedCallback)
 	{
 	{
 		closeDropDownBox();
 		closeDropDownBox();
 
 
 		mDropDownSO = SceneObject::create("DropDownBox");
 		mDropDownSO = SceneObject::create("DropDownBox");
-		mDropDownBox = mDropDownSO->addComponent<GUIDropDownBox>(target, placement, dropDownData, skin, type);
+		mDropDownBox = mDropDownSO->addComponent<GUIDropDownMenu>(target, placement, dropDownData, skin, type);
 		mOnClosedCallback = onClosedCallback;
 		mOnClosedCallback = onClosedCallback;
 
 
 		return mDropDownBox;
 		return mDropDownBox;

+ 3 - 3
BansheeEngine/Source/BsGUIDropDownContent.cpp

@@ -18,7 +18,7 @@ namespace BansheeEngine
 	const String GUIDropDownContent::ENTRY_EXP_STYLE_TYPE = "DropDownEntryExpBtn";
 	const String GUIDropDownContent::ENTRY_EXP_STYLE_TYPE = "DropDownEntryExpBtn";
 	const String GUIDropDownContent::SEPARATOR_STYLE_TYPE = "DropDownSeparator";
 	const String GUIDropDownContent::SEPARATOR_STYLE_TYPE = "DropDownSeparator";
 
 
-	GUIDropDownContent::GUIDropDownContent(GUIDropDownBox::DropDownSubMenu* parent, const GUIDropDownData& dropDownData, 
+	GUIDropDownContent::GUIDropDownContent(GUIDropDownMenu::DropDownSubMenu* parent, const GUIDropDownData& dropDownData, 
 		const String& style, const GUIDimensions& dimensions)
 		const String& style, const GUIDimensions& dimensions)
 		:GUIElementContainer(dimensions, style), mDropDownData(dropDownData),
 		:GUIElementContainer(dimensions, style), mDropDownData(dropDownData),
 		mSelectedIdx(UINT_MAX), mRangeStart(0), mRangeEnd(0), mParent(parent)
 		mSelectedIdx(UINT_MAX), mRangeStart(0), mRangeEnd(0), mParent(parent)
@@ -31,7 +31,7 @@ namespace BansheeEngine
 
 
 	}
 	}
 
 
-	GUIDropDownContent* GUIDropDownContent::create(GUIDropDownBox::DropDownSubMenu* parent, 
+	GUIDropDownContent* GUIDropDownContent::create(GUIDropDownMenu::DropDownSubMenu* parent, 
 		const GUIDropDownData& dropDownData, const String& style)
 		const GUIDropDownData& dropDownData, const String& style)
 	{
 	{
 		const String* curStyle = &style;
 		const String* curStyle = &style;
@@ -41,7 +41,7 @@ namespace BansheeEngine
 		return new (bs_alloc<GUIDropDownContent>()) GUIDropDownContent(parent, dropDownData, *curStyle, GUIDimensions::create());
 		return new (bs_alloc<GUIDropDownContent>()) GUIDropDownContent(parent, dropDownData, *curStyle, GUIDimensions::create());
 	}
 	}
 
 
-	GUIDropDownContent* GUIDropDownContent::create(GUIDropDownBox::DropDownSubMenu* parent, 
+	GUIDropDownContent* GUIDropDownContent::create(GUIDropDownMenu::DropDownSubMenu* parent, 
 		const GUIDropDownData& dropDownData, const GUIOptions& options,
 		const GUIDropDownData& dropDownData, const GUIOptions& options,
 		const String& style)
 		const String& style)
 	{
 	{

+ 464 - 563
BansheeEngine/Source/BsGUIDropDownBox.cpp → BansheeEngine/Source/BsGUIDropDownMenu.cpp

@@ -1,564 +1,465 @@
-#include "BsGUIDropDownBox.h"
-#include "BsGUIPanel.h"
-#include "BsGUILayoutY.h"
-#include "BsGUILayoutX.h"
-#include "BsGUITexture.h"
-#include "BsGUILabel.h"
-#include "BsGUIButton.h"
-#include "BsGUISpace.h"
-#include "BsGUIContent.h"
-#include "BsGUISkin.h"
-#include "BsViewport.h"
-#include "BsGUIListBox.h"
-#include "BsGUIDropDownBoxManager.h"
-#include "BsSceneObject.h"
-#include "BsGUIDropDownHitBox.h"
-#include "BsGUIDropDownContent.h"
-
-using namespace std::placeholders;
-
-namespace BansheeEngine
-{
-	const UINT32 GUIDropDownBox::DROP_DOWN_BOX_WIDTH = 250;
-
-	GUIDropDownDataEntry GUIDropDownDataEntry::separator()
-	{
-		GUIDropDownDataEntry data;
-		data.mType = Type::Separator;
-		data.mCallback = nullptr;
-
-		return data;
-	}
-
-	GUIDropDownDataEntry GUIDropDownDataEntry::button(const WString& label, std::function<void()> callback, const WString& shortcutTag)
-	{
-		GUIDropDownDataEntry data;
-		data.mLabel = label;
-		data.mType = Type::Entry;
-		data.mCallback = callback;
-		data.mShortcutTag = shortcutTag;
-
-		return data;
-	}
-
-	GUIDropDownDataEntry GUIDropDownDataEntry::subMenu(const WString& label, const GUIDropDownData& data)
-	{
-		GUIDropDownDataEntry dataEntry;
-		dataEntry.mLabel = label;
-		dataEntry.mType = Type::SubMenu;
-		dataEntry.mChildData = data;
-
-		return dataEntry;
-	}
-
-	GUIDropDownAreaPlacement GUIDropDownAreaPlacement::aroundPosition(const Vector2I& position)
-	{
-		GUIDropDownAreaPlacement instance;
-		instance.mType = Type::Position;
-		instance.mPosition = position;
-
-		return instance;
-	}
-
-	GUIDropDownAreaPlacement GUIDropDownAreaPlacement::aroundBoundsVert(const Rect2I& bounds)
-	{
-		GUIDropDownAreaPlacement instance;
-		instance.mType = Type::BoundsVert;
-		instance.mBounds = bounds;
-
-		return instance;
-	}
-		
-	GUIDropDownAreaPlacement GUIDropDownAreaPlacement::aroundBoundsHorz(const Rect2I& bounds)
-	{
-		GUIDropDownAreaPlacement instance;
-		instance.mType = Type::BoundsHorz;
-		instance.mBounds = bounds;
-
-		return instance;
-	}
-
-	GUIDropDownBox::GUIDropDownBox(const HSceneObject& parent, Viewport* target, const GUIDropDownAreaPlacement& placement,
-		const GUIDropDownData& dropDownData, const HGUISkin& skin, GUIDropDownType type)
-		:GUIWidget(parent, target), mRootMenu(nullptr), mHitBox(nullptr), mCaptureHitBox(nullptr)
-	{
-		String stylePrefix = "";
-		switch(type)
-		{
-		case GUIDropDownType::ContextMenu:
-			stylePrefix = "ContextMenu";
-			break;
-		case GUIDropDownType::ListBox:
-			stylePrefix = "ListBox";
-			break;
-		case GUIDropDownType::MenuBar:
-			stylePrefix = "MenuBar";
-			break;
-		}
-
-		mScrollUpStyle = stylePrefix + "ScrollUpBtn";
-		mScrollDownStyle = stylePrefix + "ScrollDownBtn";
-		mBackgroundStyle = stylePrefix + "Frame";
-		mContentStyle = stylePrefix + "Content";
-
-		mScrollUpBtnArrow = skin->getStyle(stylePrefix + "ScrollUpBtnArrow")->normal.texture;
-		mScrollDownBtnArrow = skin->getStyle(stylePrefix + "ScrollDownBtnArrow")->normal.texture;
-
-		setDepth(0); // Needs to be in front of everything
-		setSkin(skin);
-
-		mHitBox = GUIDropDownHitBox::create(false);
-		mHitBox->onFocusLost.connect(std::bind(&GUIDropDownBox::dropDownFocusLost, this));
-		mHitBox->setFocus(true);
-		GUILayoutData hitboxLayoutData = mHitBox->_getLayoutData();
-		hitboxLayoutData.setWidgetDepth(0);
-		hitboxLayoutData.setPanelDepth(0);
-		mHitBox->_setLayoutData(hitboxLayoutData);
-		mHitBox->_changeParentWidget(this);
-		mHitBox->_markContentAsDirty();
-
-		mCaptureHitBox = GUIDropDownHitBox::create(true);
-		mCaptureHitBox->setBounds(Rect2I(0, 0, target->getWidth(), target->getHeight()));
-		GUILayoutData captureHitboxLayoutData = mCaptureHitBox->_getLayoutData();
-		captureHitboxLayoutData.setWidgetDepth(0);
-		captureHitboxLayoutData.setPanelDepth(200);
-		mCaptureHitBox->_setLayoutData(captureHitboxLayoutData);
-		mCaptureHitBox->_changeParentWidget(this);
-		mCaptureHitBox->_markContentAsDirty();
-
-		Rect2I availableBounds(target->getX(), target->getY(), target->getWidth(), target->getHeight());
-		mRootMenu = bs_new<DropDownSubMenu>(this, nullptr, placement, availableBounds, dropDownData, type, 0);
-	}
-
-	GUIDropDownBox::~GUIDropDownBox()
-	{
-
-	}
-
-	void GUIDropDownBox::onDestroyed()
-	{
-		GUIElement::destroy(mHitBox);
-		GUIElement::destroy(mCaptureHitBox);
-		bs_delete(mRootMenu);
-
-		GUIWidget::onDestroyed();
-	}
-
-	void GUIDropDownBox::dropDownFocusLost()
-	{
-		mRootMenu->closeSubMenu();
-		GUIDropDownBoxManager::instance().closeDropDownBox();
-	}
-
-	void GUIDropDownBox::notifySubMenuOpened(DropDownSubMenu* subMenu)
-	{
-		Vector<Rect2I> bounds;
-
-		while(subMenu != nullptr)
-		{
-			bounds.push_back(subMenu->getVisibleBounds());
-
-			subMenu = subMenu->mSubMenu;
-		}
-
-		mHitBox->setBounds(bounds);
-	}
-
-	void GUIDropDownBox::notifySubMenuClosed(DropDownSubMenu* subMenu)
-	{
-		Vector<Rect2I> bounds;
-
-		while(subMenu != nullptr)
-		{
-			bounds.push_back(subMenu->getVisibleBounds());
-
-			subMenu = subMenu->mSubMenu;
-		}
-
-		mHitBox->setBounds(bounds);
-	}
-
-	GUIDropDownBox::DropDownSubMenu::DropDownSubMenu(GUIDropDownBox* owner, DropDownSubMenu* parent, const GUIDropDownAreaPlacement& placement,
-		const Rect2I& availableBounds, const GUIDropDownData& dropDownData, GUIDropDownType type, UINT32 depthOffset)
-		:mOwner(owner), mParent(parent), mPage(0), mBackgroundFrame(nullptr), mBackgroundPanel(nullptr), mContentPanel(nullptr),
-		mContentLayout(nullptr), mScrollUpBtn(nullptr), mScrollDownBtn(nullptr), x(0), y(0), width(0), height(0), 
-		mType(type), mSubMenu(nullptr), mData(dropDownData), mOpenedUpward(false), mDepthOffset(depthOffset), mContent(nullptr)
-	{
-		mAvailableBounds = availableBounds;
-
-		const GUIElementStyle* scrollUpStyle = mOwner->getSkin().getStyle(mOwner->mScrollUpStyle);
-		const GUIElementStyle* scrollDownStyle = mOwner->getSkin().getStyle(mOwner->mScrollDownStyle);
-		const GUIElementStyle* backgroundStyle = mOwner->getSkin().getStyle(mOwner->mBackgroundStyle);
-
-		Rect2I 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;
-		}
-
-		// Create content GUI element
-		mContent = GUIDropDownContent::create(this, dropDownData, mOwner->mContentStyle);
-		mContent->setKeyboardFocus(true);
-
-		// Content area
-		mContentPanel = mOwner->getPanel()->addNewElement<GUIPanel>();
-		mContentPanel->setWidth(width);
-		mContentPanel->setHeight(height);
-		mContentPanel->setDepthRange(100 - depthOffset * 2 - 1);
-
-		mContentLayout = mContentPanel->addNewElement<GUILayoutY>();
-
-		// Background frame
-		mBackgroundPanel = mOwner->getPanel()->addNewElement<GUIPanel>();
-		mBackgroundPanel->setWidth(width);
-		mBackgroundPanel->setHeight(height);
-		mBackgroundPanel->setDepthRange(100 - depthOffset * 2);
-
-		GUILayout* backgroundLayout = mBackgroundPanel->addNewElement<GUILayoutX>();
-
-		mBackgroundFrame = GUITexture::create(GUIImageScaleMode::StretchToFit, mOwner->mBackgroundStyle);
-		backgroundLayout->addElement(mBackgroundFrame);
-
-		mContentLayout->addElement(mContent); // Note: It's important this is added to the layout before we 
-											  // use it for size calculations, in order for its skin to be assigned
-
-		// Determine x position and whether to align to left or right side of the drop down list
-		UINT32 availableRightwardWidth = (UINT32)std::max(0, (availableBounds.x + availableBounds.width) - potentialRightStart);
-		UINT32 availableLeftwardWidth = (UINT32)std::max(0, potentialLeftStart - availableBounds.x);
-
-		//// Prefer right if possible
-		if(DROP_DOWN_BOX_WIDTH <= availableRightwardWidth)
-		{
-			x = potentialRightStart;
-			width = DROP_DOWN_BOX_WIDTH;
-		}
-		else
-		{
-			if(availableRightwardWidth >= availableLeftwardWidth)
-			{
-				x = potentialRightStart;
-				width = std::min(DROP_DOWN_BOX_WIDTH, availableRightwardWidth);
-			}
-			else
-			{
-				x = potentialLeftStart - std::min(DROP_DOWN_BOX_WIDTH, availableLeftwardWidth);
-				width = std::min(DROP_DOWN_BOX_WIDTH, availableLeftwardWidth);
-			}
-		}
-
-		// Determine y position and whether to open upward or downward
-		UINT32 availableDownwardHeight = (UINT32)std::max(0, (availableBounds.y + availableBounds.height) - potentialBottomStart);
-		UINT32 availableUpwardHeight = (UINT32)std::max(0, potentialTopStart - availableBounds.y);
-
-		//// Prefer down if possible
-		UINT32 helperElementHeight = scrollUpStyle->height + scrollDownStyle->height + 
-			backgroundStyle->margins.top + backgroundStyle->margins.bottom;
-
-		UINT32 maxNeededHeight = helperElementHeight;
-		UINT32 numElements = (UINT32)dropDownData.entries.size();
-		for(UINT32 i = 0; i < numElements; i++)
-			maxNeededHeight += mContent->getElementHeight(i);
-
-		height = 0;
-		if(maxNeededHeight <= availableDownwardHeight)
-		{
-			y = potentialBottomStart;
-			height = availableDownwardHeight;
-			mOpenedUpward = false;
-		}
-		else
-		{
-			if(availableDownwardHeight >= availableUpwardHeight)
-			{
-				y = potentialBottomStart;
-				height = availableDownwardHeight;
-				mOpenedUpward = false;
-			}
-			else
-			{
-				y = potentialTopStart;
-				height = availableUpwardHeight;
-				mOpenedUpward = true;
-			}
-		}
-
-		INT32 actualY = y;
-			
-		if(mOpenedUpward)	
-			actualY -= (INT32)std::min(maxNeededHeight, availableUpwardHeight);
-
-		mContentPanel->setPosition(x, actualY);
-		mBackgroundPanel->setPosition(x, actualY);
-
-		updateGUIElements();
-
-		mOwner->notifySubMenuOpened(this);
-	}
-
-	GUIDropDownBox::DropDownSubMenu::~DropDownSubMenu()
-	{
-		closeSubMenu();
-
-		mOwner->notifySubMenuClosed(this);
-
-		GUIElement::destroy(mContent);
-
-		if (mScrollUpBtn != nullptr)
-			GUIElement::destroy(mScrollUpBtn);
-
-		if (mScrollDownBtn != nullptr)
-			GUIElement::destroy(mScrollDownBtn);
-
-		GUIElement::destroy(mBackgroundFrame);
-
-		GUILayout::destroy(mBackgroundPanel);
-		GUILayout::destroy(mContentPanel);
-	}
-
-	Vector<GUIDropDownBox::DropDownSubMenu::PageInfo> GUIDropDownBox::DropDownSubMenu::getPageInfos() const
-	{
-		const GUIElementStyle* scrollUpStyle = mOwner->getSkin().getStyle(mOwner->mScrollUpStyle);
-		const GUIElementStyle* scrollDownStyle = mOwner->getSkin().getStyle(mOwner->mScrollDownStyle);
-		const GUIElementStyle* backgroundStyle = mOwner->getSkin().getStyle(mOwner->mBackgroundStyle);
-
-		INT32 numElements = (INT32)mData.entries.size();
-
-		PageInfo curPageInfo;
-		curPageInfo.start = 0;
-		curPageInfo.end = 0;
-		curPageInfo.idx = 0;
-		curPageInfo.height = backgroundStyle->margins.top + backgroundStyle->margins.bottom;
-		
-		Vector<PageInfo> pageInfos;
-		for (INT32 i = 0; i < numElements; i++)
-		{
-			curPageInfo.height += mContent->getElementHeight((UINT32)i);
-			curPageInfo.end++;
-
-			if (curPageInfo.height > height)
-			{
-				curPageInfo.height += scrollDownStyle->height;
-
-				// Remove last few elements until we fit again
-				while (curPageInfo.height > height && i >= 0)
-				{
-					curPageInfo.height -= mContent->getElementHeight((UINT32)i);
-					curPageInfo.end--;
-
-					i--;
-				}
-
-				// Nothing fits, break out of infinite loop
-				if (curPageInfo.start >= curPageInfo.end)
-					break;
-
-				pageInfos.push_back(curPageInfo);
-
-				curPageInfo.start = curPageInfo.end;
-				curPageInfo.height = backgroundStyle->margins.top + backgroundStyle->margins.bottom;
-				curPageInfo.height += scrollUpStyle->height;
-
-				curPageInfo.idx++;
-			}
-		}
-
-		if (curPageInfo.start < curPageInfo.end)
-			pageInfos.push_back(curPageInfo);
-
-		return pageInfos;
-	}
-
-	void GUIDropDownBox::DropDownSubMenu::updateGUIElements()
-	{
-		// Remove all elements from content layout
-		while(mContentLayout->getNumChildren() > 0)
-			mContentLayout->removeElementAt(mContentLayout->getNumChildren() - 1);
-
-		mContentLayout->addElement(mContent); // Note: Needs to be added first so that size calculations have proper skin to work with
-
-		const GUIElementStyle* scrollUpStyle = mOwner->getSkin().getStyle(mOwner->mScrollUpStyle);
-		const GUIElementStyle* scrollDownStyle = mOwner->getSkin().getStyle(mOwner->mScrollDownStyle);
-		const GUIElementStyle* backgroundStyle = mOwner->getSkin().getStyle(mOwner->mBackgroundStyle);
-
-		// Determine if we need scroll up and/or down buttons, number of visible elements and actual height
-		bool needsScrollUp = mPage > 0;
-
-		UINT32 pageStart = 0, pageEnd = 0;
-		UINT32 pageHeight = 0;
-		Vector<PageInfo> pageInfos = getPageInfos();
-
-		if (pageInfos.size() > mPage)
-		{
-			pageStart = pageInfos[mPage].start;
-			pageEnd = pageInfos[mPage].end;
-			pageHeight = pageInfos[mPage].height;
-		}
-
-		UINT32 numElements = (UINT32)mData.entries.size();
-		bool needsScrollDown = pageEnd != numElements;
-
-		// Add scroll up button
-		if(needsScrollUp)
-		{
-			if(mScrollUpBtn == nullptr)
-			{
-				mScrollUpBtn = GUIButton::create(GUIContent(HString(L""), mOwner->mScrollUpBtnArrow), mOwner->mScrollUpStyle);
-				mScrollUpBtn->onClick.connect(std::bind(&DropDownSubMenu::scrollUp, this));
-			}
-
-			mContentLayout->insertElement(0, mScrollUpBtn);			
-		}
-		else
-		{
-			if(mScrollUpBtn != nullptr)
-			{
-				GUIElement::destroy(mScrollUpBtn);
-				mScrollUpBtn = nullptr;
-			}
-		}
-
-		mContent->setRange(pageStart, pageEnd);
-
-		// Add scroll down button
-		if(needsScrollDown)
-		{
-			if(mScrollDownBtn == nullptr)
-			{
-				mScrollDownBtn = GUIButton::create(GUIContent(HString(L""), mOwner->mScrollDownBtnArrow), mOwner->mScrollDownStyle);
-				mScrollDownBtn->onClick.connect(std::bind(&DropDownSubMenu::scrollDown, this));
-			}
-
-			mContentLayout->addElement(mScrollDownBtn);			
-		}
-		else
-		{
-			if(mScrollDownBtn != nullptr)
-			{
-				GUIElement::destroy(mScrollDownBtn);
-				mScrollDownBtn = nullptr;
-			}
-		}
-		
-		// Resize and reposition areas
-		INT32 actualY = y;
-
-		if(mOpenedUpward)	
-			actualY -= (INT32)pageHeight;
-
-		mBackgroundPanel->setWidth(width);
-		mBackgroundPanel->setHeight(pageHeight);
-		mBackgroundPanel->setPosition(x, actualY);
-
-		mVisibleBounds = Rect2I(x, actualY, width, pageHeight);
-
-		UINT32 contentWidth = (UINT32)std::max(0, (INT32)width - (INT32)backgroundStyle->margins.left - (INT32)backgroundStyle->margins.right);
-		UINT32 contentHeight = (UINT32)std::max(0, (INT32)pageHeight - (INT32)backgroundStyle->margins.top - (INT32)backgroundStyle->margins.bottom);
-
-		mContentPanel->setWidth(contentWidth);
-		mContentPanel->setHeight(contentHeight);
-		mContentPanel->setPosition(x + backgroundStyle->margins.left, actualY + backgroundStyle->margins.top);
-	}
-
-	void GUIDropDownBox::DropDownSubMenu::scrollDown()
-	{
-		mPage++;
-		if (mPage == (UINT32)getPageInfos().size())
-			mPage = 0;
-
-		updateGUIElements();
-
-		closeSubMenu();
-	}
-
-	void GUIDropDownBox::DropDownSubMenu::scrollUp()
-	{
-		if (mPage > 0)
-			mPage--;
-		else
-			mPage = (UINT32)getPageInfos().size() - 1;
-
-		updateGUIElements();
-		closeSubMenu();
-	}
-
-	void GUIDropDownBox::DropDownSubMenu::scrollToTop()
-	{
-		mPage = 0;
-		updateGUIElements();
-
-		closeSubMenu();
-	}
-
-	void GUIDropDownBox::DropDownSubMenu::scrollToBottom()
-	{
-		mPage = (UINT32)(getPageInfos().size() - 1);
-		updateGUIElements();
-
-		closeSubMenu();
-	}
-
-	void GUIDropDownBox::DropDownSubMenu::closeSubMenu()
-	{
-		if(mSubMenu != nullptr)
-		{
-			bs_delete(mSubMenu);
-			mSubMenu = nullptr;
-
-			mContent->setKeyboardFocus(true);
-		}
-	}
-
-	void GUIDropDownBox::DropDownSubMenu::elementActivated(UINT32 idx, const Rect2I& bounds)
-	{
-		closeSubMenu();
-
-		if (!mData.entries[idx].isSubMenu())
-		{
-			auto callback = mData.entries[idx].getCallback();
-			if (callback != nullptr)
-				callback();
-
-			GUIDropDownBoxManager::instance().closeDropDownBox();
-		}
-		else
-		{
-			mContent->setKeyboardFocus(false);
-
-			mSubMenu = bs_new<DropDownSubMenu>(mOwner, this, GUIDropDownAreaPlacement::aroundBoundsVert(bounds),
-				mAvailableBounds, mData.entries[idx].getSubMenuData(), mType, mDepthOffset + 1);
-		}
-	}
-
-	void GUIDropDownBox::DropDownSubMenu::close()
-	{
-		if (mParent != nullptr)
-			mParent->closeSubMenu();
-		else // We're the last sub-menu, close the whole thing
-			GUIDropDownBoxManager::instance().closeDropDownBox();
-	}
-
-	void GUIDropDownBox::DropDownSubMenu::elementSelected(UINT32 idx)
-	{
-		closeSubMenu();
-	}
+#include "BsGUIDropDownMenu.h"
+#include "BsGUIPanel.h"
+#include "BsGUILayoutY.h"
+#include "BsGUILayoutX.h"
+#include "BsGUITexture.h"
+#include "BsGUILabel.h"
+#include "BsGUIButton.h"
+#include "BsGUISpace.h"
+#include "BsGUIContent.h"
+#include "BsGUISkin.h"
+#include "BsViewport.h"
+#include "BsGUIListBox.h"
+#include "BsGUIDropDownBoxManager.h"
+#include "BsSceneObject.h"
+#include "BsGUIDropDownHitBox.h"
+#include "BsGUIDropDownContent.h"
+
+using namespace std::placeholders;
+
+namespace BansheeEngine
+{
+	const UINT32 GUIDropDownMenu::DROP_DOWN_BOX_WIDTH = 250;
+
+	GUIDropDownDataEntry GUIDropDownDataEntry::separator()
+	{
+		GUIDropDownDataEntry data;
+		data.mType = Type::Separator;
+		data.mCallback = nullptr;
+
+		return data;
+	}
+
+	GUIDropDownDataEntry GUIDropDownDataEntry::button(const WString& label, std::function<void()> callback, const WString& shortcutTag)
+	{
+		GUIDropDownDataEntry data;
+		data.mLabel = label;
+		data.mType = Type::Entry;
+		data.mCallback = callback;
+		data.mShortcutTag = shortcutTag;
+
+		return data;
+	}
+
+	GUIDropDownDataEntry GUIDropDownDataEntry::subMenu(const WString& label, const GUIDropDownData& data)
+	{
+		GUIDropDownDataEntry dataEntry;
+		dataEntry.mLabel = label;
+		dataEntry.mType = Type::SubMenu;
+		dataEntry.mChildData = data;
+
+		return dataEntry;
+	}
+
+	GUIDropDownMenu::GUIDropDownMenu(const HSceneObject& parent, Viewport* target, const DropDownAreaPlacement& placement,
+		const GUIDropDownData& dropDownData, const HGUISkin& skin, GUIDropDownType type)
+		:GUIWidget(parent, target), mRootMenu(nullptr), mHitBox(nullptr), mCaptureHitBox(nullptr)
+	{
+		String stylePrefix = "";
+		switch(type)
+		{
+		case GUIDropDownType::ContextMenu:
+			stylePrefix = "ContextMenu";
+			break;
+		case GUIDropDownType::ListBox:
+			stylePrefix = "ListBox";
+			break;
+		case GUIDropDownType::MenuBar:
+			stylePrefix = "MenuBar";
+			break;
+		}
+
+		mScrollUpStyle = stylePrefix + "ScrollUpBtn";
+		mScrollDownStyle = stylePrefix + "ScrollDownBtn";
+		mBackgroundStyle = stylePrefix + "Frame";
+		mContentStyle = stylePrefix + "Content";
+
+		mScrollUpBtnArrow = skin->getStyle(stylePrefix + "ScrollUpBtnArrow")->normal.texture;
+		mScrollDownBtnArrow = skin->getStyle(stylePrefix + "ScrollDownBtnArrow")->normal.texture;
+
+		setDepth(0); // Needs to be in front of everything
+		setSkin(skin);
+
+		mHitBox = GUIDropDownHitBox::create(false);
+		mHitBox->onFocusLost.connect(std::bind(&GUIDropDownMenu::dropDownFocusLost, this));
+		mHitBox->setFocus(true);
+		GUILayoutData hitboxLayoutData = mHitBox->_getLayoutData();
+		hitboxLayoutData.setWidgetDepth(0);
+		hitboxLayoutData.setPanelDepth(0);
+		mHitBox->_setLayoutData(hitboxLayoutData);
+		mHitBox->_changeParentWidget(this);
+		mHitBox->_markContentAsDirty();
+
+		mCaptureHitBox = GUIDropDownHitBox::create(true);
+		mCaptureHitBox->setBounds(Rect2I(0, 0, target->getWidth(), target->getHeight()));
+		GUILayoutData captureHitboxLayoutData = mCaptureHitBox->_getLayoutData();
+		captureHitboxLayoutData.setWidgetDepth(0);
+		captureHitboxLayoutData.setPanelDepth(200);
+		mCaptureHitBox->_setLayoutData(captureHitboxLayoutData);
+		mCaptureHitBox->_changeParentWidget(this);
+		mCaptureHitBox->_markContentAsDirty();
+
+		Rect2I availableBounds(target->getX(), target->getY(), target->getWidth(), target->getHeight());
+		mRootMenu = bs_new<DropDownSubMenu>(this, nullptr, placement, availableBounds, dropDownData, type, 0);
+	}
+
+	GUIDropDownMenu::~GUIDropDownMenu()
+	{
+
+	}
+
+	void GUIDropDownMenu::onDestroyed()
+	{
+		GUIElement::destroy(mHitBox);
+		GUIElement::destroy(mCaptureHitBox);
+		bs_delete(mRootMenu);
+
+		GUIWidget::onDestroyed();
+	}
+
+	void GUIDropDownMenu::dropDownFocusLost()
+	{
+		mRootMenu->closeSubMenu();
+		GUIDropDownBoxManager::instance().closeDropDownBox();
+	}
+
+	void GUIDropDownMenu::notifySubMenuOpened(DropDownSubMenu* subMenu)
+	{
+		Vector<Rect2I> bounds;
+
+		while(subMenu != nullptr)
+		{
+			bounds.push_back(subMenu->getVisibleBounds());
+
+			subMenu = subMenu->mSubMenu;
+		}
+
+		mHitBox->setBounds(bounds);
+	}
+
+	void GUIDropDownMenu::notifySubMenuClosed(DropDownSubMenu* subMenu)
+	{
+		Vector<Rect2I> bounds;
+
+		while(subMenu != nullptr)
+		{
+			bounds.push_back(subMenu->getVisibleBounds());
+
+			subMenu = subMenu->mSubMenu;
+		}
+
+		mHitBox->setBounds(bounds);
+	}
+
+	GUIDropDownMenu::DropDownSubMenu::DropDownSubMenu(GUIDropDownMenu* owner, DropDownSubMenu* parent, const DropDownAreaPlacement& placement,
+		const Rect2I& availableBounds, const GUIDropDownData& dropDownData, GUIDropDownType type, UINT32 depthOffset)
+		:mOwner(owner), mParent(parent), mPage(0), mBackgroundFrame(nullptr), mBackgroundPanel(nullptr), mContentPanel(nullptr),
+		mContentLayout(nullptr), mScrollUpBtn(nullptr), mScrollDownBtn(nullptr), x(0), y(0), width(0), height(0), 
+		mType(type), mSubMenu(nullptr), mData(dropDownData), mOpenedUpward(false), mDepthOffset(depthOffset), mContent(nullptr)
+	{
+		mAvailableBounds = availableBounds;
+
+		const GUIElementStyle* scrollUpStyle = mOwner->getSkin().getStyle(mOwner->mScrollUpStyle);
+		const GUIElementStyle* scrollDownStyle = mOwner->getSkin().getStyle(mOwner->mScrollDownStyle);
+		const GUIElementStyle* backgroundStyle = mOwner->getSkin().getStyle(mOwner->mBackgroundStyle);
+
+		// Create content GUI element
+		mContent = GUIDropDownContent::create(this, dropDownData, mOwner->mContentStyle);
+		mContent->setKeyboardFocus(true);
+
+		// Content area
+		mContentPanel = mOwner->getPanel()->addNewElement<GUIPanel>();
+		mContentPanel->setWidth(width);
+		mContentPanel->setHeight(height);
+		mContentPanel->setDepthRange(100 - depthOffset * 2 - 1);
+
+		mContentLayout = mContentPanel->addNewElement<GUILayoutY>();
+
+		// Background frame
+		mBackgroundPanel = mOwner->getPanel()->addNewElement<GUIPanel>();
+		mBackgroundPanel->setWidth(width);
+		mBackgroundPanel->setHeight(height);
+		mBackgroundPanel->setDepthRange(100 - depthOffset * 2);
+
+		GUILayout* backgroundLayout = mBackgroundPanel->addNewElement<GUILayoutX>();
+
+		mBackgroundFrame = GUITexture::create(GUIImageScaleMode::StretchToFit, mOwner->mBackgroundStyle);
+		backgroundLayout->addElement(mBackgroundFrame);
+
+		mContentLayout->addElement(mContent); // Note: It's important this is added to the layout before we 
+		// use it for size calculations, in order for its skin to be assigned
+
+		UINT32 helperElementHeight = scrollUpStyle->height + scrollDownStyle->height +
+			backgroundStyle->margins.top + backgroundStyle->margins.bottom;
+
+		UINT32 maxNeededHeight = helperElementHeight;
+		UINT32 numElements = (UINT32)dropDownData.entries.size();
+		for (UINT32 i = 0; i < numElements; i++)
+			maxNeededHeight += mContent->getElementHeight(i);
+
+		DropDownAreaPlacement::HorzDir horzDir;
+		DropDownAreaPlacement::VertDir vertDir;
+		Rect2I placementBounds = placement.getOptimalBounds(DROP_DOWN_BOX_WIDTH, maxNeededHeight, availableBounds, horzDir, vertDir);
+
+		mOpenedUpward = vertDir == DropDownAreaPlacement::VertDir::Up;
+
+		x = placementBounds.x;
+		y = placementBounds.y;
+		width = placementBounds.width;
+		height = placementBounds.height;
+
+		mContentPanel->setPosition(x, y);
+		mBackgroundPanel->setPosition(x, y);
+
+		updateGUIElements();
+
+		mOwner->notifySubMenuOpened(this);
+	}
+
+	GUIDropDownMenu::DropDownSubMenu::~DropDownSubMenu()
+	{
+		closeSubMenu();
+
+		mOwner->notifySubMenuClosed(this);
+
+		GUIElement::destroy(mContent);
+
+		if (mScrollUpBtn != nullptr)
+			GUIElement::destroy(mScrollUpBtn);
+
+		if (mScrollDownBtn != nullptr)
+			GUIElement::destroy(mScrollDownBtn);
+
+		GUIElement::destroy(mBackgroundFrame);
+
+		GUILayout::destroy(mBackgroundPanel);
+		GUILayout::destroy(mContentPanel);
+	}
+
+	Vector<GUIDropDownMenu::DropDownSubMenu::PageInfo> GUIDropDownMenu::DropDownSubMenu::getPageInfos() const
+	{
+		const GUIElementStyle* scrollUpStyle = mOwner->getSkin().getStyle(mOwner->mScrollUpStyle);
+		const GUIElementStyle* scrollDownStyle = mOwner->getSkin().getStyle(mOwner->mScrollDownStyle);
+		const GUIElementStyle* backgroundStyle = mOwner->getSkin().getStyle(mOwner->mBackgroundStyle);
+
+		INT32 numElements = (INT32)mData.entries.size();
+
+		PageInfo curPageInfo;
+		curPageInfo.start = 0;
+		curPageInfo.end = 0;
+		curPageInfo.idx = 0;
+		curPageInfo.height = backgroundStyle->margins.top + backgroundStyle->margins.bottom;
+		
+		Vector<PageInfo> pageInfos;
+		for (INT32 i = 0; i < numElements; i++)
+		{
+			curPageInfo.height += mContent->getElementHeight((UINT32)i);
+			curPageInfo.end++;
+
+			if (curPageInfo.height > height)
+			{
+				curPageInfo.height += scrollDownStyle->height;
+
+				// Remove last few elements until we fit again
+				while (curPageInfo.height > height && i >= 0)
+				{
+					curPageInfo.height -= mContent->getElementHeight((UINT32)i);
+					curPageInfo.end--;
+
+					i--;
+				}
+
+				// Nothing fits, break out of infinite loop
+				if (curPageInfo.start >= curPageInfo.end)
+					break;
+
+				pageInfos.push_back(curPageInfo);
+
+				curPageInfo.start = curPageInfo.end;
+				curPageInfo.height = backgroundStyle->margins.top + backgroundStyle->margins.bottom;
+				curPageInfo.height += scrollUpStyle->height;
+
+				curPageInfo.idx++;
+			}
+		}
+
+		if (curPageInfo.start < curPageInfo.end)
+			pageInfos.push_back(curPageInfo);
+
+		return pageInfos;
+	}
+
+	void GUIDropDownMenu::DropDownSubMenu::updateGUIElements()
+	{
+		// Remove all elements from content layout
+		while(mContentLayout->getNumChildren() > 0)
+			mContentLayout->removeElementAt(mContentLayout->getNumChildren() - 1);
+
+		mContentLayout->addElement(mContent); // Note: Needs to be added first so that size calculations have proper skin to work with
+
+		const GUIElementStyle* scrollUpStyle = mOwner->getSkin().getStyle(mOwner->mScrollUpStyle);
+		const GUIElementStyle* scrollDownStyle = mOwner->getSkin().getStyle(mOwner->mScrollDownStyle);
+		const GUIElementStyle* backgroundStyle = mOwner->getSkin().getStyle(mOwner->mBackgroundStyle);
+
+		// Determine if we need scroll up and/or down buttons, number of visible elements and actual height
+		bool needsScrollUp = mPage > 0;
+
+		UINT32 pageStart = 0, pageEnd = 0;
+		UINT32 pageHeight = 0;
+		Vector<PageInfo> pageInfos = getPageInfos();
+
+		if (pageInfos.size() > mPage)
+		{
+			pageStart = pageInfos[mPage].start;
+			pageEnd = pageInfos[mPage].end;
+			pageHeight = pageInfos[mPage].height;
+		}
+
+		UINT32 numElements = (UINT32)mData.entries.size();
+		bool needsScrollDown = pageEnd != numElements;
+
+		// Add scroll up button
+		if(needsScrollUp)
+		{
+			if(mScrollUpBtn == nullptr)
+			{
+				mScrollUpBtn = GUIButton::create(GUIContent(HString(L""), mOwner->mScrollUpBtnArrow), mOwner->mScrollUpStyle);
+				mScrollUpBtn->onClick.connect(std::bind(&DropDownSubMenu::scrollUp, this));
+			}
+
+			mContentLayout->insertElement(0, mScrollUpBtn);			
+		}
+		else
+		{
+			if(mScrollUpBtn != nullptr)
+			{
+				GUIElement::destroy(mScrollUpBtn);
+				mScrollUpBtn = nullptr;
+			}
+		}
+
+		mContent->setRange(pageStart, pageEnd);
+
+		// Add scroll down button
+		if(needsScrollDown)
+		{
+			if(mScrollDownBtn == nullptr)
+			{
+				mScrollDownBtn = GUIButton::create(GUIContent(HString(L""), mOwner->mScrollDownBtnArrow), mOwner->mScrollDownStyle);
+				mScrollDownBtn->onClick.connect(std::bind(&DropDownSubMenu::scrollDown, this));
+			}
+
+			mContentLayout->addElement(mScrollDownBtn);			
+		}
+		else
+		{
+			if(mScrollDownBtn != nullptr)
+			{
+				GUIElement::destroy(mScrollDownBtn);
+				mScrollDownBtn = nullptr;
+			}
+		}
+		
+		// Resize and reposition areas
+		INT32 actualY = y;
+
+		if(mOpenedUpward)	
+			actualY -= (INT32)pageHeight;
+
+		mBackgroundPanel->setWidth(width);
+		mBackgroundPanel->setHeight(pageHeight);
+		mBackgroundPanel->setPosition(x, actualY);
+
+		mVisibleBounds = Rect2I(x, actualY, width, pageHeight);
+
+		UINT32 contentWidth = (UINT32)std::max(0, (INT32)width - (INT32)backgroundStyle->margins.left - (INT32)backgroundStyle->margins.right);
+		UINT32 contentHeight = (UINT32)std::max(0, (INT32)pageHeight - (INT32)backgroundStyle->margins.top - (INT32)backgroundStyle->margins.bottom);
+
+		mContentPanel->setWidth(contentWidth);
+		mContentPanel->setHeight(contentHeight);
+		mContentPanel->setPosition(x + backgroundStyle->margins.left, actualY + backgroundStyle->margins.top);
+	}
+
+	void GUIDropDownMenu::DropDownSubMenu::scrollDown()
+	{
+		mPage++;
+		if (mPage == (UINT32)getPageInfos().size())
+			mPage = 0;
+
+		updateGUIElements();
+
+		closeSubMenu();
+	}
+
+	void GUIDropDownMenu::DropDownSubMenu::scrollUp()
+	{
+		if (mPage > 0)
+			mPage--;
+		else
+			mPage = (UINT32)getPageInfos().size() - 1;
+
+		updateGUIElements();
+		closeSubMenu();
+	}
+
+	void GUIDropDownMenu::DropDownSubMenu::scrollToTop()
+	{
+		mPage = 0;
+		updateGUIElements();
+
+		closeSubMenu();
+	}
+
+	void GUIDropDownMenu::DropDownSubMenu::scrollToBottom()
+	{
+		mPage = (UINT32)(getPageInfos().size() - 1);
+		updateGUIElements();
+
+		closeSubMenu();
+	}
+
+	void GUIDropDownMenu::DropDownSubMenu::closeSubMenu()
+	{
+		if(mSubMenu != nullptr)
+		{
+			bs_delete(mSubMenu);
+			mSubMenu = nullptr;
+
+			mContent->setKeyboardFocus(true);
+		}
+	}
+
+	void GUIDropDownMenu::DropDownSubMenu::elementActivated(UINT32 idx, const Rect2I& bounds)
+	{
+		closeSubMenu();
+
+		if (!mData.entries[idx].isSubMenu())
+		{
+			auto callback = mData.entries[idx].getCallback();
+			if (callback != nullptr)
+				callback();
+
+			GUIDropDownBoxManager::instance().closeDropDownBox();
+		}
+		else
+		{
+			mContent->setKeyboardFocus(false);
+
+			mSubMenu = bs_new<DropDownSubMenu>(mOwner, this, DropDownAreaPlacement::aroundBoundsVert(bounds),
+				mAvailableBounds, mData.entries[idx].getSubMenuData(), mType, mDepthOffset + 1);
+		}
+	}
+
+	void GUIDropDownMenu::DropDownSubMenu::close()
+	{
+		if (mParent != nullptr)
+			mParent->closeSubMenu();
+		else // We're the last sub-menu, close the whole thing
+			GUIDropDownBoxManager::instance().closeDropDownBox();
+	}
+
+	void GUIDropDownMenu::DropDownSubMenu::elementSelected(UINT32 idx)
+	{
+		closeSubMenu();
+	}
 }
 }

+ 2 - 2
BansheeEngine/Source/BsGUIListBox.cpp

@@ -106,9 +106,9 @@ namespace BansheeEngine
 		}
 		}
 
 
 		GUIWidget* widget = _getParentWidget();
 		GUIWidget* widget = _getParentWidget();
-		GUIDropDownAreaPlacement placement = GUIDropDownAreaPlacement::aroundBoundsHorz(_getLayoutData().area);
+		DropDownAreaPlacement placement = DropDownAreaPlacement::aroundBoundsHorz(_getLayoutData().area);
 
 
-		GameObjectHandle<GUIDropDownBox> dropDownBox = GUIDropDownBoxManager::instance().openDropDownBox(widget->getTarget(), 
+		GameObjectHandle<GUIDropDownMenu> dropDownBox = GUIDropDownBoxManager::instance().openDropDownBox(widget->getTarget(), 
 			placement, dropDownData, widget->getSkinResource(), GUIDropDownType::MenuBar, std::bind(&GUIListBox::onListBoxClosed, this));
 			placement, dropDownData, widget->getSkinResource(), GUIDropDownType::MenuBar, std::bind(&GUIListBox::onListBoxClosed, this));
 
 
 		_setOn(true);
 		_setOn(true);

+ 1 - 1
BansheeEngine/Source/BsGUIManager.cpp

@@ -22,7 +22,7 @@
 #include "BsGUIInputSelection.h"
 #include "BsGUIInputSelection.h"
 #include "BsGUIListBox.h"
 #include "BsGUIListBox.h"
 #include "BsGUIButton.h"
 #include "BsGUIButton.h"
-#include "BsGUIDropDownBox.h"
+#include "BsGUIDropDownMenu.h"
 #include "BsGUIContextMenu.h"
 #include "BsGUIContextMenu.h"
 #include "BsDragAndDropManager.h"
 #include "BsDragAndDropManager.h"
 #include "BsGUIDropDownBoxManager.h"
 #include "BsGUIDropDownBoxManager.h"

+ 1 - 1
BansheeEngine/Source/BsGUIMenu.cpp

@@ -1,5 +1,5 @@
 #include "BsGUIMenu.h"
 #include "BsGUIMenu.h"
-#include "BsGUIDropDownBox.h"
+#include "BsGUIDropDownMenu.h"
 #include "BsShortcutManager.h"
 #include "BsShortcutManager.h"
 
 
 namespace BansheeEngine
 namespace BansheeEngine

+ 87 - 0
MBansheeEditor/DropDownWindow.cs

@@ -0,0 +1,87 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using BansheeEngine;
+
+namespace BansheeEditor
+{
+    public class DropDownWindow : ScriptObject
+    {
+        private int width;
+        private int height;
+
+        public int Width
+        {
+            get { return width; }
+            set { Internal_SetWidth(mCachedPtr, value); width = value; }
+        }
+
+        public int Height
+        {
+            get { return height; }
+            set { Internal_SetHeight(mCachedPtr, value); height = value; }
+        }
+
+        protected GUIPanel GUI;
+
+        public static T Open<T>(EditorWindow parent, Vector2I position) where T : DropDownWindow, new()
+        {
+            T window = new T();
+            window.Initialize(parent, position);
+
+            return window;
+        }
+
+        protected DropDownWindow(int width = 200, int height = 200)
+        {
+            this.width = width;
+            this.height = height;
+        }
+
+        private void Initialize(EditorWindow parent, Vector2I position)
+        {
+            IntPtr parentPtr = IntPtr.Zero;
+            if (parent != null)
+                parentPtr = parent.GetCachedPtr();
+
+            Internal_CreateInstance(this, parentPtr, position, width, height);
+        }
+
+        protected Vector2I ScreenToWindowPos(Vector2I screenPos)
+        {
+            Vector2I windowPos;
+            Internal_ScreenToWindowPos(mCachedPtr, screenPos, out windowPos);
+            return windowPos;
+        }
+
+        protected Vector2I WindowToScreenPos(Vector2I windowPos)
+        {
+            Vector2I screenPos;
+            Internal_WindowToScreenPos(mCachedPtr, windowPos, out screenPos);
+            return screenPos;
+        }
+
+        protected void Close()
+        {
+            Internal_Close(mCachedPtr);
+        }
+
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern void Internal_CreateInstance(DropDownWindow instance, IntPtr parentWindow, Vector2I position, int width, int height);
+
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern void Internal_Close(IntPtr nativeInstance);
+
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern void Internal_SetWidth(IntPtr nativeInstance, int value);
+
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern void Internal_SetHeight(IntPtr nativeInstance, int value);
+
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern void Internal_ScreenToWindowPos(IntPtr nativeInstance, Vector2I position, out Vector2I windowPos);
+
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern void Internal_WindowToScreenPos(IntPtr nativeInstance, Vector2I position, out Vector2I screenPos);
+    }
+}

+ 1 - 0
MBansheeEditor/MBansheeEditor.csproj

@@ -54,6 +54,7 @@
     <Compile Include="Debug_Component2.cs" />
     <Compile Include="Debug_Component2.cs" />
     <Compile Include="DialogBox.cs" />
     <Compile Include="DialogBox.cs" />
     <Compile Include="DragDrop.cs" />
     <Compile Include="DragDrop.cs" />
+    <Compile Include="DropDownWindow.cs" />
     <Compile Include="DropTarget.cs" />
     <Compile Include="DropTarget.cs" />
     <Compile Include="EditorApplication.cs" />
     <Compile Include="EditorApplication.cs" />
     <Compile Include="EditorBuiltin.cs" />
     <Compile Include="EditorBuiltin.cs" />

+ 1 - 1
MBansheeEngine/ContextMenu.cs

@@ -6,7 +6,7 @@ namespace BansheeEngine
 {
 {
     public class ContextMenu : ScriptObject
     public class ContextMenu : ScriptObject
     {
     {
-        private List<Action> callbacks; 
+        private List<Action> callbacks = new List<Action>(); 
 
 
         public ContextMenu()
         public ContextMenu()
         {
         {

+ 74 - 0
SBansheeEditor/Include/BsScriptDropDownWindow.h

@@ -0,0 +1,74 @@
+#pragma once
+
+#include "BsScriptEditorPrerequisites.h"
+#include "BsScriptObject.h"
+#include "BsDropDownWindow.h"
+#include "BsVector2I.h"
+
+namespace BansheeEngine
+{
+	class ManagedDropDownWindow;
+
+	class BS_SCR_BED_EXPORT ScriptDropDownWindow : public ScriptObject <ScriptDropDownWindow>
+	{
+	public:
+		SCRIPT_OBJ(EDITOR_ASSEMBLY, "BansheeEditor", "DropDownWindow")
+
+		~ScriptDropDownWindow();
+
+	private:
+		friend class ManagedDropDownWindow;
+
+		ScriptDropDownWindow(ManagedDropDownWindow* window);
+
+		static void internal_CreateInstance(MonoObject* instance, ScriptEditorWindow* parentWindow, Vector2I position, int width, int height);
+		static void internal_Close(ScriptDropDownWindow* nativeInstance);
+		static void internal_SetWidth(ScriptDropDownWindow* nativeInstance, UINT32 value);
+		static void internal_SetHeight(ScriptDropDownWindow* nativeInstance, UINT32 value);
+		static void internal_ScreenToWindowPos(ScriptDropDownWindow* nativeInstance, Vector2I position, Vector2I* windowPos);
+		static void internal_WindowToScreenPos(ScriptDropDownWindow* nativeInstance, Vector2I position, Vector2I* screenPos);
+
+		void onAssemblyRefreshStarted();
+		void notifyWindowClosed();
+
+		ManagedDropDownWindow* mDropDownWindow;
+		HEvent mOnAssemblyRefreshStartedConn;
+
+		static MonoField* guiPanelField;
+	};
+
+	class BS_SCR_BED_EXPORT ManagedDropDownWindow : public DropDownWindow
+	{
+	public:
+		ManagedDropDownWindow(const RenderWindowPtr& parent, Viewport* target,
+			const Vector2I& position, MonoObject* managedInstance, UINT32 width, UINT32 height);
+		~ManagedDropDownWindow();
+
+		void initialize(ScriptDropDownWindow* parent);
+		void update() override;
+		void reloadMonoTypes(MonoClass* windowClass);
+		void triggerOnInitialize();
+		void triggerOnDestroy();
+
+		MonoObject* getManagedInstance() const { return mManagedInstance; }
+
+	private:
+		friend class ScriptModalWindow;
+
+		typedef void(__stdcall *OnInitializeThunkDef) (MonoObject*, MonoException**);
+		typedef void(__stdcall *OnDestroyThunkDef) (MonoObject*, MonoException**);
+		typedef void(__stdcall *UpdateThunkDef) (MonoObject*, MonoException**);
+
+		String mNamespace;
+		String mTypename;
+
+		OnInitializeThunkDef mOnInitializeThunk;
+		OnDestroyThunkDef mOnDestroyThunk;
+		UpdateThunkDef mUpdateThunk;
+
+		MonoObject* mManagedInstance;
+		uint32_t mGCHandle;
+
+		ScriptDropDownWindow* mScriptParent;
+	};
+}

+ 2 - 0
SBansheeEditor/SBansheeEditor.vcxproj

@@ -240,6 +240,7 @@
     <ClInclude Include="Include\BsScriptBuildManager.h" />
     <ClInclude Include="Include\BsScriptBuildManager.h" />
     <ClInclude Include="Include\BsScriptCodeEditor.h" />
     <ClInclude Include="Include\BsScriptCodeEditor.h" />
     <ClInclude Include="Include\BsScriptDragDropManager.h" />
     <ClInclude Include="Include\BsScriptDragDropManager.h" />
+    <ClInclude Include="Include\BsScriptDropDownWindow.h" />
     <ClInclude Include="Include\BsScriptDropTarget.h" />
     <ClInclude Include="Include\BsScriptDropTarget.h" />
     <ClInclude Include="Include\BsScriptEditorApplication.h" />
     <ClInclude Include="Include\BsScriptEditorApplication.h" />
     <ClInclude Include="Include\BsScriptEditorBuiltin.h" />
     <ClInclude Include="Include\BsScriptEditorBuiltin.h" />
@@ -284,6 +285,7 @@
     <ClCompile Include="Source\BsGUIGameObjectField.cpp" />
     <ClCompile Include="Source\BsGUIGameObjectField.cpp" />
     <ClCompile Include="Source\BsGUIResourceField.cpp" />
     <ClCompile Include="Source\BsGUIResourceField.cpp" />
     <ClCompile Include="Source\BsScriptBrowseDialog.cpp" />
     <ClCompile Include="Source\BsScriptBrowseDialog.cpp" />
+    <ClCompile Include="Source\BsScriptDropDownWindow.cpp" />
     <ClCompile Include="Source\BsScriptDropTarget.cpp" />
     <ClCompile Include="Source\BsScriptDropTarget.cpp" />
     <ClCompile Include="Source\BsScriptEditorApplication.cpp" />
     <ClCompile Include="Source\BsScriptEditorApplication.cpp" />
     <ClCompile Include="Source\BsScriptEditorBuiltin.cpp" />
     <ClCompile Include="Source\BsScriptEditorBuiltin.cpp" />

+ 6 - 0
SBansheeEditor/SBansheeEditor.vcxproj.filters

@@ -141,6 +141,9 @@
     <ClInclude Include="Include\BsScriptDropTarget.h">
     <ClInclude Include="Include\BsScriptDropTarget.h">
       <Filter>Header Files</Filter>
       <Filter>Header Files</Filter>
     </ClInclude>
     </ClInclude>
+    <ClInclude Include="Include\BsScriptDropDownWindow.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
     <ClCompile Include="Source\BsScriptEditorPlugin.cpp">
     <ClCompile Include="Source\BsScriptEditorPlugin.cpp">
@@ -269,5 +272,8 @@
     <ClCompile Include="Source\BsScriptDropTarget.cpp">
     <ClCompile Include="Source\BsScriptDropTarget.cpp">
       <Filter>Source Files</Filter>
       <Filter>Source Files</Filter>
     </ClCompile>
     </ClCompile>
+    <ClCompile Include="Source\BsScriptDropDownWindow.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
   </ItemGroup>
   </ItemGroup>
 </Project>
 </Project>

+ 210 - 0
SBansheeEditor/Source/BsScriptDropDownWindow.cpp

@@ -0,0 +1,210 @@
+#include "BsScriptDropDownWindow.h"
+#include "BsScriptMeta.h"
+#include "BsMonoField.h"
+#include "BsMonoClass.h"
+#include "BsMonoMethod.h"
+#include "BsMonoManager.h"
+#include "BsMonoUtil.h"
+#include "BsMonoAssembly.h"
+#include "BsRenderWindow.h"
+#include "BsScriptGUILayout.h"
+#include "BsScriptEditorWindow.h"
+#include "BsEditorWidget.h"
+#include "BsEditorWindow.h"
+#include "BsEditorWidgetContainer.h"
+#include "BsGUIWidget.h"
+#include "BsDropDownWindowManager.h"
+#include <BsScriptObjectManager.h>
+
+using namespace std::placeholders;
+
+namespace BansheeEngine
+{
+	MonoField* ScriptDropDownWindow::guiPanelField = nullptr;
+
+	ScriptDropDownWindow::ScriptDropDownWindow(ManagedDropDownWindow* window)
+		:ScriptObject(window->getManagedInstance()), mDropDownWindow(window)
+	{
+		mOnAssemblyRefreshStartedConn = ScriptObjectManager::instance().onRefreshStarted.connect(std::bind(&ScriptDropDownWindow::onAssemblyRefreshStarted, this));
+	}
+
+	ScriptDropDownWindow::~ScriptDropDownWindow()
+	{
+		mOnAssemblyRefreshStartedConn.disconnect();
+
+		// Window must have been marked as deleted already
+		assert(mDropDownWindow == nullptr);
+	}
+
+	void ScriptDropDownWindow::initRuntimeData()
+	{
+		metaData.scriptClass->addInternalCall("Internal_CreateInstance", &ScriptDropDownWindow::internal_CreateInstance);
+		metaData.scriptClass->addInternalCall("Internal_Close", &ScriptDropDownWindow::internal_Close);
+		metaData.scriptClass->addInternalCall("Internal_SetWidth", &ScriptDropDownWindow::internal_SetWidth);
+		metaData.scriptClass->addInternalCall("Internal_SetHeight", &ScriptDropDownWindow::internal_SetHeight);
+		metaData.scriptClass->addInternalCall("Internal_ScreenToWindowPos", &ScriptDropDownWindow::internal_ScreenToWindowPos);
+		metaData.scriptClass->addInternalCall("Internal_WindowToScreenPos", &ScriptDropDownWindow::internal_WindowToScreenPos);
+
+		guiPanelField = metaData.scriptClass->getField("GUI");
+	}
+
+	void ScriptDropDownWindow::internal_CreateInstance(MonoObject* instance, ScriptEditorWindow* parentWindow, 
+		Vector2I position, int width, int height)
+	{
+		ManagedDropDownWindow* dropDownWindow = nullptr;
+		if (parentWindow != nullptr)
+		{
+			EditorWidgetContainer* parentContainer = parentWindow->getEditorWidget()->_getParent();
+			if (parentContainer != nullptr)
+			{
+				RenderWindowPtr parentRenderWindow = parentContainer->getParentWindow()->getRenderWindow();
+				Viewport* parentTarget = parentContainer->getParentWidget().getTarget();
+
+				dropDownWindow = DropDownWindowManager::instance().open<ManagedDropDownWindow>(
+					parentRenderWindow, parentTarget, position, instance, width, height);
+			}
+		}
+
+		ScriptDropDownWindow* nativeInstance = new (bs_alloc<ScriptDropDownWindow>()) ScriptDropDownWindow(dropDownWindow);
+		dropDownWindow->initialize(nativeInstance);
+		dropDownWindow->triggerOnInitialize();
+	}
+
+	void ScriptDropDownWindow::internal_Close(ScriptDropDownWindow* thisPtr)
+	{
+		if (thisPtr->mDropDownWindow != nullptr)
+			DropDownWindowManager::instance().close();
+	}
+
+	void ScriptDropDownWindow::onAssemblyRefreshStarted()
+	{
+		if (mDropDownWindow != nullptr)
+			DropDownWindowManager::instance().close();
+	}
+
+	void ScriptDropDownWindow::notifyWindowClosed()
+	{
+		mDropDownWindow = nullptr;
+	}
+
+	void ScriptDropDownWindow::internal_SetWidth(ScriptDropDownWindow* thisPtr, UINT32 value)
+	{
+		if (thisPtr->mDropDownWindow != nullptr)
+			thisPtr->mDropDownWindow->setSize(value, thisPtr->mDropDownWindow->getHeight());
+	}
+
+	void ScriptDropDownWindow::internal_SetHeight(ScriptDropDownWindow* thisPtr, UINT32 value)
+	{
+		if (thisPtr->mDropDownWindow != nullptr)
+			thisPtr->mDropDownWindow->setSize(thisPtr->mDropDownWindow->getWidth(), value);
+	}
+
+	void ScriptDropDownWindow::internal_ScreenToWindowPos(ScriptDropDownWindow* thisPtr, Vector2I screenPos, Vector2I* windowPos)
+	{
+		if (thisPtr->mDropDownWindow != nullptr)
+			*windowPos = thisPtr->mDropDownWindow->screenToWindowPos(screenPos);
+		else
+			*windowPos = screenPos;
+	}
+
+	void ScriptDropDownWindow::internal_WindowToScreenPos(ScriptDropDownWindow* thisPtr, Vector2I windowPos, Vector2I* screenPos)
+	{
+		if (thisPtr->mDropDownWindow != nullptr)
+			*screenPos = thisPtr->mDropDownWindow->windowToScreenPos(windowPos);
+		else
+			*screenPos = windowPos;
+	}
+
+	ManagedDropDownWindow::ManagedDropDownWindow(const RenderWindowPtr& parent, Viewport* target,
+		const Vector2I& position, MonoObject* managedInstance, UINT32 width, UINT32 height)
+		:DropDownWindow(parent, target, position, width, height), mUpdateThunk(nullptr), mManagedInstance(managedInstance),
+		mOnInitializeThunk(nullptr), mOnDestroyThunk(nullptr), mGCHandle(0), mScriptParent(nullptr)
+	{
+		mGCHandle = mono_gchandle_new(mManagedInstance, false);
+
+		MonoObject* guiPanel = ScriptGUIPanel::createFromExisting(mContents);
+		ScriptDropDownWindow::guiPanelField->setValue(mManagedInstance, guiPanel);
+
+		::MonoClass* rawMonoClass = mono_object_get_class(mManagedInstance);
+		MonoClass* monoClass = MonoManager::instance().findClass(rawMonoClass);
+
+		mNamespace = monoClass->getNamespace();
+		mTypename = monoClass->getTypeName();
+
+		reloadMonoTypes(monoClass);
+	}
+
+	ManagedDropDownWindow::~ManagedDropDownWindow()
+	{
+		triggerOnDestroy();
+		mScriptParent->notifyWindowClosed();
+
+		mono_gchandle_free(mGCHandle);
+		mGCHandle = 0;
+	}
+
+	void ManagedDropDownWindow::initialize(ScriptDropDownWindow* parent)
+	{
+		mScriptParent = parent;
+	}
+
+	void ManagedDropDownWindow::triggerOnInitialize()
+	{
+		if (mOnInitializeThunk != nullptr && mManagedInstance != nullptr)
+		{
+			MonoException* exception = nullptr;
+
+			// Note: Not calling virtual methods. Can be easily done if needed but for now doing this
+			// for some extra speed.
+			mOnInitializeThunk(mManagedInstance, &exception);
+
+			MonoUtil::throwIfException(exception);
+		}
+	}
+
+	void ManagedDropDownWindow::triggerOnDestroy()
+	{
+		if (mOnDestroyThunk != nullptr && mManagedInstance != nullptr)
+		{
+			MonoException* exception = nullptr;
+
+			// Note: Not calling virtual methods. Can be easily done if needed but for now doing this
+			// for some extra speed.
+			mOnDestroyThunk(mManagedInstance, &exception);
+
+			MonoUtil::throwIfException(exception);
+		}
+	}
+
+	void ManagedDropDownWindow::update()
+	{
+		if (mUpdateThunk != nullptr && mManagedInstance != nullptr)
+		{
+			MonoException* exception = nullptr;
+
+			// Note: Not calling virtual methods. Can be easily done if needed but for now doing this
+			// for some extra speed.
+			mUpdateThunk(mManagedInstance, &exception);
+
+			MonoUtil::throwIfException(exception);
+		}
+	}
+
+	void ManagedDropDownWindow::reloadMonoTypes(MonoClass* windowClass)
+	{
+		MonoMethod* updateMethod = windowClass->getMethod("OnEditorUpdate", 0);
+
+		if (updateMethod != nullptr)
+			mUpdateThunk = (UpdateThunkDef)updateMethod->getThunk();
+
+		MonoMethod* onInitializeMethod = windowClass->getMethod("OnInitialize", 0);
+
+		if (onInitializeMethod != nullptr)
+			mOnInitializeThunk = (OnInitializeThunkDef)onInitializeMethod->getThunk();
+
+		MonoMethod* onDestroyMethod = windowClass->getMethod("OnDestroy", 0);
+
+		if (onDestroyMethod != nullptr)
+			mOnDestroyThunk = (OnDestroyThunkDef)onDestroyMethod->getThunk();
+	}
+}

+ 2 - 0
SBansheeEngine/Source/BsScriptContextMenu.cpp

@@ -12,6 +12,8 @@ using namespace std::placeholders;
 
 
 namespace BansheeEngine
 namespace BansheeEngine
 {
 {
+	ScriptContextMenu::OnEntryTriggeredThunkDef ScriptContextMenu::onEntryTriggered;
+
 	ScriptContextMenu::ScriptContextMenu(MonoObject* instance)
 	ScriptContextMenu::ScriptContextMenu(MonoObject* instance)
 		: ScriptObject(instance)
 		: ScriptObject(instance)
 	{
 	{