2
0
Эх сурвалжийг харах

WIP drop down box refactor so it has keyboard controls

Marko Pintera 10 жил өмнө
parent
commit
5b3416fc27

+ 45 - 22
BansheeEditor/Source/BsBuiltinEditorResources.cpp

@@ -22,6 +22,7 @@
 #include "BsGUIFoldout.h"
 #include "BsGUIProgressBar.h"
 #include "BsGUISlider.h"
+#include "BsGUIDropDownContent.h"
 
 #include "BsFont.h"
 #include "BsFontImportOptions.h"
@@ -662,9 +663,7 @@ namespace BansheeEngine
 		dropDownEntryBtnStyle.textHorzAlign = THA_Left;
 		dropDownEntryBtnStyle.textVertAlign = TVA_Top;
 
-		mSkin.setStyle("ListBoxEntryBtn", dropDownEntryBtnStyle);
-		mSkin.setStyle("MenuBarEntryBtn", dropDownEntryBtnStyle);
-		mSkin.setStyle("ContextMenuEntryBtn", dropDownEntryBtnStyle);
+		mSkin.setStyle(GUIDropDownContent::ENTRY_STYLE_TYPE, dropDownEntryBtnStyle);
 
 		// DropDown entry button with expand
 		GUIElementStyle dropDownEntryExpBtnStyle;
@@ -684,9 +683,33 @@ namespace BansheeEngine
 		dropDownEntryExpBtnStyle.textHorzAlign = THA_Left;
 		dropDownEntryExpBtnStyle.textVertAlign = TVA_Top;
 
-		mSkin.setStyle("ListBoxEntryExpBtn", dropDownEntryExpBtnStyle);
-		mSkin.setStyle("MenuBarEntryExpBtn", dropDownEntryExpBtnStyle);
-		mSkin.setStyle("ContextMenuEntryExpBtn", dropDownEntryExpBtnStyle);
+		mSkin.setStyle(GUIDropDownContent::ENTRY_EXP_STYLE_TYPE, dropDownEntryExpBtnStyle);
+
+		// Drop down separator
+		GUIElementStyle dropDownSeparatorStyle;
+		dropDownSeparatorStyle.normal.texture = getGUITexture(DropDownSeparatorTex);
+		dropDownSeparatorStyle.fixedHeight = true;
+		dropDownSeparatorStyle.fixedWidth = false;
+		dropDownSeparatorStyle.height = 3;
+		dropDownSeparatorStyle.width = 30;
+		dropDownSeparatorStyle.border.left = 1;
+		dropDownSeparatorStyle.border.right = 1;
+		dropDownSeparatorStyle.border.top = 1;
+		dropDownSeparatorStyle.border.bottom = 1;
+
+		mSkin.setStyle(GUIDropDownContent::SEPARATOR_STYLE_TYPE, dropDownSeparatorStyle);
+
+		// Drop down content
+		GUIElementStyle dropDownContentStyle;
+		dropDownContentStyle.minWidth = 50;
+		dropDownContentStyle.minHeight = 20;
+		dropDownContentStyle.subStyles[GUIDropDownContent::ENTRY_STYLE_TYPE] = GUIDropDownContent::ENTRY_STYLE_TYPE;
+		dropDownContentStyle.subStyles[GUIDropDownContent::ENTRY_EXP_STYLE_TYPE] = GUIDropDownContent::ENTRY_EXP_STYLE_TYPE;
+		dropDownContentStyle.subStyles[GUIDropDownContent::SEPARATOR_STYLE_TYPE] = GUIDropDownContent::SEPARATOR_STYLE_TYPE;
+
+		mSkin.setStyle("ListBoxContent", dropDownContentStyle);
+		mSkin.setStyle("MenuBarContent", dropDownContentStyle);
+		mSkin.setStyle("ContextMenuContent", dropDownContentStyle);
 
 		// DropDown box frame
 		GUIElementStyle dropDownBoxStyle;
@@ -708,22 +731,6 @@ namespace BansheeEngine
 		mSkin.setStyle("MenuBarFrame", dropDownBoxStyle);
 		mSkin.setStyle("ContextMenuFrame", dropDownBoxStyle);
 
-		// Drop down separator
-		GUIElementStyle dropDownSeparatorStyle;
-		dropDownSeparatorStyle.normal.texture = getGUITexture(DropDownSeparatorTex);
-		dropDownSeparatorStyle.fixedHeight = true;
-		dropDownSeparatorStyle.fixedWidth = false;
-		dropDownSeparatorStyle.height = 3;
-		dropDownSeparatorStyle.width = 30;
-		dropDownSeparatorStyle.border.left = 1;
-		dropDownSeparatorStyle.border.right = 1;
-		dropDownSeparatorStyle.border.top = 1;
-		dropDownSeparatorStyle.border.bottom = 1;
-
-		mSkin.setStyle("ListBoxSeparator", dropDownSeparatorStyle);
-		mSkin.setStyle("MenuBarSeparator", dropDownSeparatorStyle);
-		mSkin.setStyle("ContextMenuSeparator", dropDownSeparatorStyle);
-
 		/************************************************************************/
 		/* 								MENU BAR	                     		*/
 		/************************************************************************/
@@ -1134,6 +1141,22 @@ namespace BansheeEngine
 		colorPickerSlider2DHandleStyle.active.texture = colorPickerSlider2DHandleStyle.normal.texture;
 
 		mSkin.setStyle("ColorSlider2DHandle", colorPickerSlider2DHandleStyle);
+
+		/************************************************************************/
+		/* 									OTHER                      			*/
+		/************************************************************************/
+
+		// Right-aligned label
+		GUIElementStyle rightAlignedLabelStyle;
+		rightAlignedLabelStyle.font = font;
+		rightAlignedLabelStyle.fontSize = DefaultFontSize;
+		rightAlignedLabelStyle.fixedWidth = false;
+		rightAlignedLabelStyle.fixedHeight = true;
+		rightAlignedLabelStyle.height = 11;
+		rightAlignedLabelStyle.minWidth = 10;
+		rightAlignedLabelStyle.textHorzAlign = THA_Right;
+
+		mSkin.setStyle("RightAlignedLabel", rightAlignedLabelStyle);
 	}
 
 	void BuiltinEditorResources::preprocess()

+ 2 - 0
BansheeEngine/BansheeEngine.vcxproj

@@ -235,6 +235,7 @@
     <ClCompile Include="Source\BsCameraHandler.cpp" />
     <ClCompile Include="Source\BsCursor.cpp" />
     <ClCompile Include="Source\BsDrawHelper.cpp" />
+    <ClCompile Include="Source\BsGUIDropDownContent.cpp" />
     <ClCompile Include="Source\BsGUILayoutExplicit.cpp" />
     <ClCompile Include="Source\BsGUILayoutUtility.cpp" />
     <ClCompile Include="Source\BsGUIProgressBar.cpp" />
@@ -258,6 +259,7 @@
     <ClInclude Include="Include\BsCameraHandlerRTTI.h" />
     <ClInclude Include="Include\BsCursor.h" />
     <ClInclude Include="Include\BsDrawHelper.h" />
+    <ClInclude Include="Include\BsGUIDropDownContent.h" />
     <ClInclude Include="Include\BsGUILayoutExplicit.h" />
     <ClInclude Include="Include\BsGUIProgressBar.h" />
     <ClInclude Include="Include\BsGUISlider.h" />

+ 6 - 0
BansheeEngine/BansheeEngine.vcxproj.filters

@@ -323,6 +323,9 @@
     <ClInclude Include="Include\BsShortcutManager.h">
       <Filter>Header Files</Filter>
     </ClInclude>
+    <ClInclude Include="Include\BsGUIDropDownContent.h">
+      <Filter>Header Files\GUI</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="Source\BsGUIElement.cpp">
@@ -559,5 +562,8 @@
     <ClCompile Include="Source\BsShortcutKey.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="Source\BsGUIDropDownContent.cpp">
+      <Filter>Source Files\GUI</Filter>
+    </ClCompile>
   </ItemGroup>
 </Project>

+ 19 - 32
BansheeEngine/Include/BsGUIDropDownBox.h

@@ -189,20 +189,12 @@ namespace BansheeEngine
 		 */
 		struct DropDownSubMenu
 		{
-			/**
-			 * @brief	Contains various GUI elements used for displaying a single menu entry.
-			 */
-			struct EntryElementGUI
-			{
-				GUIButton* button;
-				GUILabel* shortcutLabel;
-			};
-
 		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.
@@ -211,7 +203,7 @@ namespace BansheeEngine
 			 *							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, const GUIDropDownAreaPlacement& placement, 
+			DropDownSubMenu(GUIDropDownBox* owner, DropDownSubMenu* parent, const GUIDropDownAreaPlacement& placement, 
 				const Rect2I& availableBounds, const GUIDropDownData& dropDownData, GUIDropDownType type, UINT32 depthOffset);
 			~DropDownSubMenu();
 
@@ -231,19 +223,18 @@ namespace BansheeEngine
 			void scrollUp();
 
 			/**
-			 * @brief	Returns height of a menu element at the specified index, in pixels.
+			 * @brief	Called when the user activates an element with the specified index.
 			 */
-			UINT32 getElementHeight(UINT32 idx) const;
+			void elementActivated(UINT32 idx);
 
 			/**
-			 * @brief	Called when the user clicks an element with the specified index.
+			 * @brief	Called when the user selects an element with the specified index.
+			 * 
+			 * @param	idx		Index of the element that was selected.
+			 * @param	bounds	Bounds of the GUI element that is used as a visual representation 
+			 *					of this drop down element.
 			 */
-			void elementClicked(UINT32 idx);
-
-			/**
-			 * @brief	Called when the user wants to open a sub-menu with the specified index.
-			 */
-			void openSubMenu(GUIButton* source, UINT32 elementIdx);
+			void elementSelected(UINT32 idx, const Rect2I& bounds);
 
 			/**
 			 * @brief	Called when the user wants to close the currently open sub-menu.
@@ -251,14 +242,14 @@ namespace BansheeEngine
 			void closeSubMenu();
 
 			/**
-			 * @brief	Returns actual visible bounds of the sub-menu.
+			 * @brief	Closes this sub-menu.
 			 */
-			Rect2I getVisibleBounds() const { return mVisibleBounds; }
+			void close();
 
 			/**
-			 * @brief	Get localized name of a menu item element with the specified index.
+			 * @brief	Returns actual visible bounds of the sub-menu.
 			 */
-			HString getElementLocalizedName(UINT32 idx) const;
+			Rect2I getVisibleBounds() const { return mVisibleBounds; }
 
 		public:
 			GUIDropDownBox* mOwner;
@@ -273,21 +264,22 @@ namespace BansheeEngine
 			UINT32 mDepthOffset;
 			bool mOpenedUpward;
 
-			Vector<GUITexture*> mCachedSeparators;
-			Vector<EntryElementGUI> mCachedEntryBtns;
-			Vector<GUIButton*> mCachedExpEntryBtns;
 			GUIButton* mScrollUpBtn;
 			GUIButton* mScrollDownBtn;
+			GUIDropDownContent* mContent;
 			GUITexture* mBackgroundFrame;
 
 			GUIArea* mBackgroundArea;
 			GUIArea* mContentArea;
 			GUILayout* mContentLayout;
 
+			DropDownSubMenu* mParent;
 			DropDownSubMenu* mSubMenu;
 		};
 
 	private:
+		friend class GUIDropDownContent;
+
 		/**
 		 * @brief	Called when the specified sub-menu is opened.
 		 */
@@ -308,10 +300,8 @@ namespace BansheeEngine
 
 		String mScrollUpStyle;
 		String mScrollDownStyle;
-		String mEntryBtnStyle;
-		String mEntryExpBtnStyle;
-		String mSeparatorStyle;
 		String mBackgroundStyle;
+		String mContentStyle;
 		HSpriteTexture mScrollUpBtnArrow;
 		HSpriteTexture mScrollDownBtnArrow;
 
@@ -321,8 +311,5 @@ namespace BansheeEngine
 		// (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; 
-
-		UnorderedMap<WString, HString> mLocalizedEntryNames;
-
 	};
 }

+ 126 - 0
BansheeEngine/Include/BsGUIDropDownContent.h

@@ -0,0 +1,126 @@
+#pragma once
+
+#include "BsPrerequisites.h"
+#include "BsGUIElementContainer.h"
+#include "BsGUIDropDownBox.h"
+
+namespace BansheeEngine
+{
+	/**
+	 * @brief	GUI element that is used for representing entries in a drop down menu.
+	 */
+	class BS_EXPORT GUIDropDownContent : public GUIElementContainer
+	{
+		/**
+		 * @brief	Contains various GUI elements used for displaying a single menu entry.
+		 */
+		struct VisibleElement
+		{
+			UINT32 idx = 0;
+			GUIButton* button = nullptr;
+			GUITexture* separator = nullptr;
+			GUILabel* shortcutLabel = nullptr;
+		};
+
+	public:
+		/**
+		 * Returns type name of the GUI element used for finding GUI element styles. 
+		 */
+		static const String& getGUITypeName();
+
+		/**
+		 * @brief	Creates a new drop down contents element.
+		 *
+		 * @param	parent			Parent sub-menu that owns the drop down contents.
+		 * @param	dropDownData	Data that will be used for initializing the child entries.
+		 * @param	styleName		Optional style to use for the element. Style will be retrieved
+		 *							from GUISkin of the GUIWidget the element is used on. If not specified
+		 *							default button style is used.
+		 */
+		static GUIDropDownContent* create(GUIDropDownBox::DropDownSubMenu* parent, const GUIDropDownData& dropDownData,
+			const String& style = StringUtil::BLANK);
+
+		/**
+		 * @brief	Creates a new drop down contents element.
+		 *
+		 * @param	parent			Parent sub-menu that owns the drop down contents.
+		 * @param	dropDownData	Data that will be used for initializing the child entries.
+		 * @param	layoutOptions	Options that allows you to control how is the element positioned in
+		 *							GUI layout. This will override any similar options set by style.
+		 * @param	styleName		Optional style to use for the element. Style will be retrieved
+		 *							from GUISkin of the GUIWidget the element is used on. If not specified
+		 *							default button style is used.
+		 */
+		static GUIDropDownContent* create(GUIDropDownBox::DropDownSubMenu* parent, const GUIDropDownData& dropDownData, 
+			const GUIOptions& layoutOptions, const String& style = StringUtil::BLANK);
+
+		/**
+		 * @brief	Changes the range of the displayed elements. 
+		 *
+		 * @note	This must be called at least once after creation.
+		 */
+		void setRange(UINT32 start, UINT32 end);
+
+		/**
+		 * @brief	Returns height of a menu element at the specified index, in pixels.
+		 */
+		UINT32 getElementHeight(UINT32 idx) const;
+
+		/**
+		 * @brief	Enables or disables keyboard focus. When keyboard focus is enabled the contents
+		 *			will respond to keyboard events.
+		 */
+		void setKeyboardFocus(bool focus) { mKeyboardFocus = focus; }
+
+		static const String ENTRY_STYLE_TYPE;
+		static const String ENTRY_EXP_STYLE_TYPE;
+		static const String SEPARATOR_STYLE_TYPE;
+	protected:
+		GUIDropDownContent(GUIDropDownBox::DropDownSubMenu* parent, const GUIDropDownData& dropDownData, 
+			const String& style, const GUILayoutOptions& layoutOptions);
+		~GUIDropDownContent() override;
+
+		/**
+		 * @brief	Get localized name of a menu item element with the specified index.
+		 */
+		HString getElementLocalizedName(UINT32 idx) const;
+
+		/**
+		 * @copydoc	GUIElementContainer::_getOptimalSize
+		 */
+		Vector2I _getOptimalSize() const override;
+
+		/**
+		 * @copydoc	GUIElementContainer::updateClippedBounds
+		 */
+		void updateClippedBounds() override;
+
+		/**
+		 * @copydoc	GUIElementContainer::_updateLayoutInternal
+		 */
+		void _updateLayoutInternal(INT32 x, INT32 y, UINT32 width, UINT32 height,
+			Rect2I clipRect, UINT8 widgetDepth, UINT16 areaDepth) override;
+
+		/**
+		 * @copydoc	GUIElementContainer::styleUpdated
+		 */
+		void styleUpdated() override;
+
+		/**
+		 * @copydoc	GUIElementContainer::_commandEvent
+		 */
+		bool _commandEvent(const GUICommandEvent& ev) override;
+
+		/**
+		 * @brief	Marks the element with the specified index as selected.
+		 */
+		void setSelected(UINT32 idx);
+
+		GUIDropDownData mDropDownData;
+		Vector<VisibleElement> mVisibleElements;
+		UINT32 mSelectedIdx;
+		UINT32 mRangeStart, mRangeEnd;
+		GUIDropDownBox::DropDownSubMenu* mParent;
+		bool mKeyboardFocus;
+	};
+}

+ 1 - 0
BansheeEngine/Include/BsPrerequisites.h

@@ -77,6 +77,7 @@ namespace BansheeEngine
 	class GUIContent;
 	class GUIContextMenu;
 	class GUIDropDownHitBox;
+	class GUIDropDownContent;
 	class RenderableElement;
 	class GUISlider;
 	class GUISliderVert;

+ 49 - 20
BansheeEngine/Source/BsBuiltinResources.cpp

@@ -5,6 +5,7 @@
 #include "BsGUIButton.h"
 #include "BsGUIInputBox.h"
 #include "BsGUIToggle.h"
+#include "BsGUIDropDownContent.h"
 #include "BsTextSprite.h"
 #include "BsSpriteTexture.h"
 
@@ -536,6 +537,9 @@ namespace BansheeEngine
 		dropDownEntryBtnStyle.normal.texture = getSkinTexture(DropDownBoxEntryNormalTex);
 		dropDownEntryBtnStyle.hover.texture = getSkinTexture(DropDownBoxEntryHoverTex);
 		dropDownEntryBtnStyle.active.texture = dropDownEntryBtnStyle.hover.texture;
+		dropDownEntryBtnStyle.normalOn.texture = dropDownEntryBtnStyle.hover.texture;
+		dropDownEntryBtnStyle.hoverOn.texture = dropDownEntryBtnStyle.hover.texture;
+		dropDownEntryBtnStyle.activeOn.texture = dropDownEntryBtnStyle.hover.texture;
 		dropDownEntryBtnStyle.fixedHeight = true;
 		dropDownEntryBtnStyle.fixedWidth = false;
 		dropDownEntryBtnStyle.height = 14;
@@ -549,15 +553,16 @@ namespace BansheeEngine
 		dropDownEntryBtnStyle.textHorzAlign = THA_Left;
 		dropDownEntryBtnStyle.textVertAlign = TVA_Top;
 
-		mSkin.setStyle("ListBoxEntryBtn", dropDownEntryBtnStyle);
-		mSkin.setStyle("MenuBarEntryBtn", dropDownEntryBtnStyle);
-		mSkin.setStyle("ContextMenuEntryBtn", dropDownEntryBtnStyle);
+		mSkin.setStyle(GUIDropDownContent::ENTRY_STYLE_TYPE, dropDownEntryBtnStyle);
 
 		// DropDown entry button with expand
 		GUIElementStyle dropDownEntryExpBtnStyle;
 		dropDownEntryExpBtnStyle.normal.texture = getSkinTexture(DropDownBoxEntryExpNormalTex);
 		dropDownEntryExpBtnStyle.hover.texture = getSkinTexture(DropDownBoxEntryExpHoverTex);
 		dropDownEntryExpBtnStyle.active.texture = dropDownEntryExpBtnStyle.hover.texture;
+		dropDownEntryExpBtnStyle.normalOn.texture = dropDownEntryExpBtnStyle.hover.texture;
+		dropDownEntryExpBtnStyle.hoverOn.texture = dropDownEntryExpBtnStyle.hover.texture;
+		dropDownEntryExpBtnStyle.activeOn.texture = dropDownEntryExpBtnStyle.hover.texture;
 		dropDownEntryExpBtnStyle.fixedHeight = true;
 		dropDownEntryExpBtnStyle.fixedWidth = false;
 		dropDownEntryExpBtnStyle.height = 14;
@@ -571,9 +576,33 @@ namespace BansheeEngine
 		dropDownEntryExpBtnStyle.textHorzAlign = THA_Left;
 		dropDownEntryExpBtnStyle.textVertAlign = TVA_Top;
 
-		mSkin.setStyle("ListBoxEntryExpBtn", dropDownEntryExpBtnStyle);
-		mSkin.setStyle("MenuBarEntryExpBtn", dropDownEntryExpBtnStyle);
-		mSkin.setStyle("ContextMenuEntryExpBtn", dropDownEntryExpBtnStyle);
+		mSkin.setStyle(GUIDropDownContent::ENTRY_EXP_STYLE_TYPE, dropDownEntryExpBtnStyle);
+
+		// Drop down separator
+		GUIElementStyle dropDownSeparatorStyle;
+		dropDownSeparatorStyle.normal.texture = getSkinTexture(DropDownSeparatorTex);
+		dropDownSeparatorStyle.fixedHeight = true;
+		dropDownSeparatorStyle.fixedWidth = false;
+		dropDownSeparatorStyle.height = 3;
+		dropDownSeparatorStyle.width = 30;
+		dropDownSeparatorStyle.border.left = 1;
+		dropDownSeparatorStyle.border.right = 1;
+		dropDownSeparatorStyle.border.top = 1;
+		dropDownSeparatorStyle.border.bottom = 1;
+
+		mSkin.setStyle(GUIDropDownContent::SEPARATOR_STYLE_TYPE, dropDownSeparatorStyle);
+
+		// Drop down content
+		GUIElementStyle dropDownContentStyle;
+		dropDownContentStyle.minWidth = 50;
+		dropDownContentStyle.minHeight = 20;
+		dropDownContentStyle.subStyles[GUIDropDownContent::ENTRY_STYLE_TYPE] = GUIDropDownContent::ENTRY_STYLE_TYPE;
+		dropDownContentStyle.subStyles[GUIDropDownContent::ENTRY_EXP_STYLE_TYPE] = GUIDropDownContent::ENTRY_EXP_STYLE_TYPE;
+		dropDownContentStyle.subStyles[GUIDropDownContent::SEPARATOR_STYLE_TYPE] = GUIDropDownContent::SEPARATOR_STYLE_TYPE;
+
+		mSkin.setStyle("ListBoxContent", dropDownContentStyle);
+		mSkin.setStyle("MenuBarContent", dropDownContentStyle);
+		mSkin.setStyle("ContextMenuContent", dropDownContentStyle);
 
 		// DropDown box frame
 		GUIElementStyle dropDownBoxStyle;
@@ -595,21 +624,21 @@ namespace BansheeEngine
 		mSkin.setStyle("MenuBarFrame", dropDownBoxStyle);
 		mSkin.setStyle("ContextMenuFrame", dropDownBoxStyle);
 
-		// Drop down separator
-		GUIElementStyle dropDownSeparatorStyle;
-		dropDownSeparatorStyle.normal.texture = getSkinTexture(DropDownSeparatorTex);
-		dropDownSeparatorStyle.fixedHeight = true;
-		dropDownSeparatorStyle.fixedWidth = false;
-		dropDownSeparatorStyle.height = 3;
-		dropDownSeparatorStyle.width = 30;
-		dropDownSeparatorStyle.border.left = 1;
-		dropDownSeparatorStyle.border.right = 1;
-		dropDownSeparatorStyle.border.top = 1;
-		dropDownSeparatorStyle.border.bottom = 1;
+		/************************************************************************/
+		/* 									OTHER                      			*/
+		/************************************************************************/
 
-		mSkin.setStyle("ListBoxSeparator", dropDownSeparatorStyle);
-		mSkin.setStyle("MenuBarSeparator", dropDownSeparatorStyle);
-		mSkin.setStyle("ContextMenuSeparator", dropDownSeparatorStyle);
+		// Right-aligned label
+		GUIElementStyle rightAlignedLabelStyle;
+		rightAlignedLabelStyle.font = font;
+		rightAlignedLabelStyle.fontSize = DefaultFontSize;
+		rightAlignedLabelStyle.fixedWidth = false;
+		rightAlignedLabelStyle.fixedHeight = true;
+		rightAlignedLabelStyle.height = 11;
+		rightAlignedLabelStyle.minWidth = 10;
+		rightAlignedLabelStyle.textHorzAlign = THA_Right;
+
+		mSkin.setStyle("RightAlignedLabel", rightAlignedLabelStyle);
 	}
 
 	void BuiltinResources::preprocess()

+ 37 - 159
BansheeEngine/Source/BsGUIDropDownBox.cpp

@@ -13,6 +13,9 @@
 #include "BsGUIDropDownBoxManager.h"
 #include "BsSceneObject.h"
 #include "BsGUIDropDownHitBox.h"
+#include "BsGUIDropDownContent.h"
+
+using namespace std::placeholders;
 
 namespace BansheeEngine
 {
@@ -95,10 +98,8 @@ namespace BansheeEngine
 
 		mScrollUpStyle = stylePrefix + "ScrollUpBtn";
 		mScrollDownStyle = stylePrefix + "ScrollDownBtn";
-		mEntryBtnStyle = stylePrefix + "EntryBtn";
-		mEntryExpBtnStyle = stylePrefix + "EntryExpBtn";
-		mSeparatorStyle = stylePrefix + "Separator";
 		mBackgroundStyle = stylePrefix + "Frame";
+		mContentStyle = stylePrefix + "Content";
 
 		mScrollUpBtnArrow = skin.getStyle(stylePrefix + "ScrollUpBtnArrow")->normal.texture;
 		mScrollDownBtnArrow = skin.getStyle(stylePrefix + "ScrollDownBtnArrow")->normal.texture;
@@ -106,8 +107,6 @@ namespace BansheeEngine
 		setDepth(0); // Needs to be in front of everything
 		setSkin(skin);
 
-		mLocalizedEntryNames = dropDownData.localizedNames;
-
 		mHitBox = GUIDropDownHitBox::create(false);
 		mHitBox->onFocusLost.connect(std::bind(&GUIDropDownBox::dropDownFocusLost, this));
 		mHitBox->setFocus(true);
@@ -122,7 +121,7 @@ namespace BansheeEngine
 		mCaptureHitBox->_changeParentWidget(this);
 
 		Rect2I availableBounds(target->getX(), target->getY(), target->getWidth(), target->getHeight());
-		mRootMenu = bs_new<DropDownSubMenu>(this, placement, availableBounds, dropDownData, type, 0);
+		mRootMenu = bs_new<DropDownSubMenu>(this, nullptr, placement, availableBounds, dropDownData, type, 0);
 	}
 
 	GUIDropDownBox::~GUIDropDownBox()
@@ -166,11 +165,11 @@ namespace BansheeEngine
 		mHitBox->setBounds(bounds);
 	}
 
-	GUIDropDownBox::DropDownSubMenu::DropDownSubMenu(GUIDropDownBox* owner, const GUIDropDownAreaPlacement& placement, 
+	GUIDropDownBox::DropDownSubMenu::DropDownSubMenu(GUIDropDownBox* owner, DropDownSubMenu* parent, const GUIDropDownAreaPlacement& placement,
 		const Rect2I& availableBounds, const GUIDropDownData& dropDownData, GUIDropDownType type, UINT32 depthOffset)
-		:mOwner(owner), mPage(0), mBackgroundFrame(nullptr), mBackgroundArea(nullptr), mContentArea(nullptr), 
+		:mOwner(owner), mParent(parent), mPage(0), mBackgroundFrame(nullptr), mBackgroundArea(nullptr), mContentArea(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)
+		mType(type), mSubMenu(nullptr), mData(dropDownData), mOpenedUpward(false), mDepthOffset(depthOffset), mContent(nullptr)
 	{
 		mAvailableBounds = availableBounds;
 
@@ -204,6 +203,10 @@ namespace BansheeEngine
 			break;
 		}
 
+		// Create content GUI element
+		mContent = GUIDropDownContent::create(this, dropDownData, mOwner->mContentStyle);
+		mContent->setKeyboardFocus(true);
+
 		// 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);
@@ -239,7 +242,7 @@ namespace BansheeEngine
 		UINT32 maxNeededHeight = helperElementHeight;
 		UINT32 numElements = (UINT32)dropDownData.entries.size();
 		for(UINT32 i = 0; i < numElements; i++)
-			maxNeededHeight += getElementHeight(i);
+			maxNeededHeight += mContent->getElementHeight(i);
 
 		height = 0;
 		if(maxNeededHeight <= availableDownwardHeight)
@@ -292,19 +295,7 @@ namespace BansheeEngine
 
 		mOwner->notifySubMenuClosed(this);
 
-		for(auto& elem : mCachedSeparators)
-			GUIElement::destroy(elem);
-
-		for (auto& entryData : mCachedEntryBtns)
-		{
-			GUIElement::destroy(entryData.button);
-
-			if (entryData.shortcutLabel != nullptr)
-				GUIElement::destroy(entryData.shortcutLabel);
-		}
-
-		for(auto& elem : mCachedExpEntryBtns)
-			GUIElement::destroy(elem);
+		GUIElement::destroy(mContent);
 
 		if(mScrollUpBtn != nullptr)
 			GUIElement::destroy(mScrollUpBtn);
@@ -339,7 +330,7 @@ namespace BansheeEngine
 		bool needsScrollDown = false;
 		for(UINT32 i = 0; i < numElements; i++)
 		{
-			usedHeight += getElementHeight(i);
+			usedHeight += mContent->getElementHeight(i);
 			pageEnd++;
 
 			if(usedHeight > height)
@@ -349,7 +340,7 @@ namespace BansheeEngine
 				// Remove last few elements until we fit again
 				while(usedHeight > height && i >= 0)
 				{
-					usedHeight -= getElementHeight(i);
+					usedHeight -= mContent->getElementHeight(i);
 					pageEnd--;
 
 					i--;
@@ -397,115 +388,8 @@ namespace BansheeEngine
 			}
 		}
 
-		Vector<GUITexture*> newSeparators;
-		Vector<EntryElementGUI> newEntryBtns;
-		Vector<GUIButton*> newExpEntryBtns;
-		for(UINT32 i = pageStart; i < pageEnd; i++)
-		{
-			GUIDropDownDataEntry& element = mData.entries[i];
-
-			if(element.isSeparator())
-			{
-				GUITexture* separator = nullptr;
-				if(!mCachedSeparators.empty())
-				{
-					separator = mCachedSeparators.back();
-					mCachedSeparators.erase(mCachedSeparators.end() - 1);
-				}
-				else
-				{
-					separator = GUITexture::create(GUIImageScaleMode::StretchToFit, mOwner->mSeparatorStyle);
-				}
-
-				mContentLayout->addElement(separator);
-				newSeparators.push_back(separator);
-			}
-			else if(element.isSubMenu())
-			{
-				GUIButton* expEntryBtn = nullptr;
-				if(!mCachedExpEntryBtns.empty())
-				{
-					expEntryBtn = mCachedExpEntryBtns.back();
-					mCachedExpEntryBtns.erase(mCachedExpEntryBtns.end() - 1);
-				}
-				else
-				{
-					expEntryBtn = GUIButton::create(getElementLocalizedName(i), mOwner->mEntryExpBtnStyle);
-					expEntryBtn->onHover.connect(std::bind(&DropDownSubMenu::openSubMenu, this, expEntryBtn, i));
-				}
-
-				mContentLayout->addElement(expEntryBtn);
-				newExpEntryBtns.push_back(expEntryBtn);
-			}
-			else
-			{
-				GUIButton* entryBtn = nullptr;
-				GUILabel* shortcutLabel = nullptr;
-				if(!mCachedEntryBtns.empty())
-				{
-					entryBtn = mCachedEntryBtns.back().button;
-					shortcutLabel = mCachedEntryBtns.back().shortcutLabel;
-
-					mCachedEntryBtns.erase(mCachedEntryBtns.end() - 1);
-				}
-				else
-				{
-					entryBtn = GUIButton::create(getElementLocalizedName(i), mOwner->mEntryBtnStyle);
-					entryBtn->onHover.connect(std::bind(&DropDownSubMenu::closeSubMenu, this));
-					entryBtn->onClick.connect(std::bind(&DropDownSubMenu::elementClicked, this,  i));
-				}
-
-				const WString& shortcutTag = element.getShortcutTag();
-				if (!shortcutTag.empty())
-				{
-					if (shortcutLabel == nullptr)
-						shortcutLabel = GUILabel::create(HString(shortcutTag));
-					else
-						shortcutLabel->setContent(GUIContent(HString(shortcutTag)));
-				}
-				else
-				{
-					if (shortcutLabel != nullptr)
-						GUIElement::destroy(shortcutLabel);
-
-					shortcutLabel = nullptr;
-				}
-				
-				EntryElementGUI entryElemGUI;
-
-				GUILayout& entryLayout = mContentLayout->addLayoutX();
-				entryLayout.addElement(entryBtn);
-				entryElemGUI.button = entryBtn;
-				entryElemGUI.shortcutLabel = shortcutLabel;
-
-				if (shortcutLabel != nullptr)
-				{
-					entryLayout.addFlexibleSpace();
-					entryLayout.addElement(shortcutLabel);
-				}
-
-				newEntryBtns.push_back(entryElemGUI);
-			}
-		}
-
-		// Destroy all unused cached elements
-		for(auto& elem : mCachedSeparators)
-			GUIElement::destroy(elem);
-
-		for (auto& entryData : mCachedEntryBtns)
-		{
-			GUIElement::destroy(entryData.button);
-
-			if (entryData.shortcutLabel != nullptr)
-				GUIElement::destroy(entryData.shortcutLabel);
-		}
-
-		for(auto& elem : mCachedExpEntryBtns)
-			GUIElement::destroy(elem);
-
-		mCachedSeparators = newSeparators;
-		mCachedEntryBtns = newEntryBtns;
-		mCachedExpEntryBtns = newExpEntryBtns;
+		mContent->setRange(pageStart, pageEnd);
+		mContentLayout->addElement(mContent);
 
 		// Add scroll down button
 		if(needsScrollDown)
@@ -544,27 +428,6 @@ namespace BansheeEngine
 		mContentArea->setPosition(x + backgroundStyle->margins.left, actualY + backgroundStyle->margins.top);
 	}
 
-	UINT32 GUIDropDownBox::DropDownSubMenu::getElementHeight(UINT32 idx) const
-	{
-		if(mData.entries[idx].isSeparator())
-			return mOwner->getSkin().getStyle(mOwner->mSeparatorStyle)->height;
-		else if(mData.entries[idx].isSubMenu())
-			return mOwner->getSkin().getStyle(mOwner->mEntryExpBtnStyle)->height;
-		else
-			return mOwner->getSkin().getStyle(mOwner->mEntryBtnStyle)->height;
-	}
-
-	HString GUIDropDownBox::DropDownSubMenu::getElementLocalizedName(UINT32 idx) const
-	{
-		const WString& label = mData.entries[idx].getLabel();
-
-		auto findLocalizedName = mOwner->mLocalizedEntryNames.find(label);
-		if(findLocalizedName != mOwner->mLocalizedEntryNames.end())
-			return findLocalizedName->second;
-		else
-			return HString(label);
-	}
-
 	void GUIDropDownBox::DropDownSubMenu::scrollDown()
 	{
 		mPage++;
@@ -590,10 +453,12 @@ namespace BansheeEngine
 		{
 			bs_delete(mSubMenu);
 			mSubMenu = nullptr;
+
+			mContent->setKeyboardFocus(true);
 		}
 	}
 
-	void GUIDropDownBox::DropDownSubMenu::elementClicked(UINT32 idx)
+	void GUIDropDownBox::DropDownSubMenu::elementActivated(UINT32 idx)
 	{
 		closeSubMenu();
 
@@ -604,11 +469,24 @@ namespace BansheeEngine
 		GUIDropDownBoxManager::instance().closeDropDownBox();
 	}
 
-	void GUIDropDownBox::DropDownSubMenu::openSubMenu(GUIButton* source, UINT32 idx)
+	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, const Rect2I& bounds)
 	{
 		closeSubMenu();
 
-		mSubMenu = bs_new<DropDownSubMenu>(mOwner, GUIDropDownAreaPlacement::aroundBoundsVert(source->_getCachedBounds()), 
-			mAvailableBounds, mData.entries[idx].getSubMenuData(), mType, mDepthOffset + 1);
+		if (mData.entries[idx].isSubMenu())
+		{
+			mContent->setKeyboardFocus(false);
+
+			mSubMenu = bs_new<DropDownSubMenu>(mOwner, this, GUIDropDownAreaPlacement::aroundBoundsVert(bounds),
+				mAvailableBounds, mData.entries[idx].getSubMenuData(), mType, mDepthOffset + 1);
+		}
 	}
 }

+ 362 - 0
BansheeEngine/Source/BsGUIDropDownContent.cpp

@@ -0,0 +1,362 @@
+#include "BsGUIDropDownContent.h"
+#include "BsGUIArea.h"
+#include "BsGUILayout.h"
+#include "BsGUITexture.h"
+#include "BsGUIButton.h"
+#include "BsGUILabel.h"
+#include "BsGUISpace.h"
+#include "BsGUIWidget.h"
+#include "BsGUIToggle.h"
+#include "BsGUISkin.h"
+#include "BsGUIMouseEvent.h"
+#include "BsGUICommandEvent.h"
+
+using namespace std::placeholders;
+
+namespace BansheeEngine
+{
+	const String GUIDropDownContent::ENTRY_STYLE_TYPE = "DropDownEntryBtn";
+	const String GUIDropDownContent::ENTRY_EXP_STYLE_TYPE = "DropDownEntryExpBtn";
+	const String GUIDropDownContent::SEPARATOR_STYLE_TYPE = "DropDownSeparator";
+
+	GUIDropDownContent::GUIDropDownContent(GUIDropDownBox::DropDownSubMenu* parent, const GUIDropDownData& dropDownData, 
+		const String& style, const GUILayoutOptions& layoutOptions)
+		:GUIElementContainer(layoutOptions, style), mDropDownData(dropDownData), 
+		mSelectedIdx(UINT_MAX), mRangeStart(0), mRangeEnd(0), mParent(parent)
+	{
+		
+	}
+
+	GUIDropDownContent::~GUIDropDownContent()
+	{
+
+	}
+
+	GUIDropDownContent* GUIDropDownContent::create(GUIDropDownBox::DropDownSubMenu* parent, 
+		const GUIDropDownData& dropDownData, const String& style)
+	{
+		const String* curStyle = &style;
+		if (*curStyle == StringUtil::BLANK)
+			curStyle = &GUIDropDownContent::getGUITypeName();
+
+		return new (bs_alloc<GUIDropDownContent>()) GUIDropDownContent(parent, dropDownData, *curStyle, GUILayoutOptions::create());
+	}
+
+	GUIDropDownContent* GUIDropDownContent::create(GUIDropDownBox::DropDownSubMenu* parent, 
+		const GUIDropDownData& dropDownData, const GUIOptions& layoutOptions,
+		const String& style)
+	{
+		const String* curStyle = &style;
+		if (*curStyle == StringUtil::BLANK)
+			curStyle = &GUIDropDownContent::getGUITypeName();
+
+		return new (bs_alloc<GUIDropDownContent>()) GUIDropDownContent(parent, dropDownData, *curStyle, GUILayoutOptions::create(layoutOptions));
+	}
+
+	void GUIDropDownContent::styleUpdated()
+	{
+		for (auto& visElem : mVisibleElements)
+		{
+			GUIDropDownDataEntry& element = mDropDownData.entries[visElem.idx];
+
+			if (element.isSeparator())
+				visElem.separator->setStyle(getSubStyleName(SEPARATOR_STYLE_TYPE));
+			else if (element.isSubMenu())
+				visElem.separator->setStyle(getSubStyleName(ENTRY_EXP_STYLE_TYPE));
+			else
+				visElem.separator->setStyle(getSubStyleName(ENTRY_STYLE_TYPE));
+		}
+	}
+
+	void GUIDropDownContent::setRange(UINT32 start, UINT32 end)
+	{
+		std::function<void(UINT32, UINT32)> onHover = 
+			[&](UINT32 idx, UINT32 visIdx) 
+		{ 
+			mSelectedIdx = visIdx; 
+			mParent->elementSelected(idx, mVisibleElements[visIdx].button->_getCachedBounds()); 
+		};
+
+		std::function<void(UINT32)> onClick = [&](UINT32 idx) { mParent->elementActivated(idx); };
+
+		// Remove all elements
+		while (_getNumChildren() > 0)
+		{
+			GUIElementBase* child = _getChild(_getNumChildren() - 1);
+			assert(child->_getType() == GUIElementBase::Type::Element);
+
+			GUIElement::destroy(static_cast<GUIElement*>(child));
+		}
+
+		mRangeStart = start;
+		mRangeEnd = end;
+		
+		mVisibleElements.clear();
+		UINT32 curVisIdx = 0;
+		for (UINT32 i = start; i < end; i++)
+		{
+			mVisibleElements.push_back(VisibleElement());
+			VisibleElement& visElem = mVisibleElements.back();
+			visElem.idx = i;
+			GUIDropDownDataEntry& element = mDropDownData.entries[i];
+
+			if (element.isSeparator())
+			{
+				visElem.separator = GUITexture::create(GUIImageScaleMode::StretchToFit, getSubStyleName(SEPARATOR_STYLE_TYPE));
+				_registerChildElement(visElem.separator);
+			}
+			else if (element.isSubMenu())
+			{
+				visElem.button = GUIButton::create(getElementLocalizedName(i), getSubStyleName(ENTRY_EXP_STYLE_TYPE));
+				visElem.button->onHover.connect(std::bind(onHover, i, curVisIdx));
+				_registerChildElement(visElem.button);
+			}
+			else
+			{
+				visElem.button = GUIButton::create(getElementLocalizedName(i), getSubStyleName(ENTRY_STYLE_TYPE));
+				visElem.button->onHover.connect(std::bind(onHover, i, curVisIdx));
+				visElem.button->onClick.connect(std::bind(onClick, i));
+				_registerChildElement(visElem.button);
+
+				const WString& shortcutTag = element.getShortcutTag();
+				if (!shortcutTag.empty())
+				{
+					visElem.shortcutLabel = GUILabel::create(HString(shortcutTag), "RightAlignedLabel");
+					_registerChildElement(visElem.shortcutLabel);
+				}
+			}
+
+			curVisIdx++;
+		}
+
+		if (mSelectedIdx == UINT_MAX)
+			setSelected(0);
+	}
+
+	UINT32 GUIDropDownContent::getElementHeight(UINT32 idx) const
+	{
+		if (mDropDownData.entries[idx].isSeparator())
+			return _getParentWidget()->getSkin().getStyle(getSubStyleName(SEPARATOR_STYLE_TYPE))->height;
+		else if (mDropDownData.entries[idx].isSubMenu())
+			return _getParentWidget()->getSkin().getStyle(getSubStyleName(ENTRY_EXP_STYLE_TYPE))->height;
+		else
+			return _getParentWidget()->getSkin().getStyle(getSubStyleName(ENTRY_STYLE_TYPE))->height;
+	}
+
+	HString GUIDropDownContent::getElementLocalizedName(UINT32 idx) const
+	{
+		const WString& label = mDropDownData.entries[idx].getLabel();
+
+		auto findLocalizedName = mDropDownData.localizedNames.find(label);
+		if (findLocalizedName != mDropDownData.localizedNames.end())
+			return findLocalizedName->second;
+		else
+			return HString(label);
+	}
+
+	bool GUIDropDownContent::_commandEvent(const GUICommandEvent& ev)
+	{
+		if (!mKeyboardFocus)
+			return false;
+
+		UINT32 maxElemIdx = (UINT32)mDropDownData.entries.size() - 1;
+
+		switch (ev.getType())
+		{
+		case GUICommandEventType::MoveDown:
+		{
+			{
+				UINT32 curIdx = mVisibleElements[mSelectedIdx].idx;
+				UINT32 nextIdx = curIdx;
+				bool gotNextIndex = false;
+
+				for (nextIdx = curIdx + 1; nextIdx <= maxElemIdx; nextIdx++)
+				{
+					GUIDropDownDataEntry& entry = mDropDownData.entries[nextIdx];
+					if (!entry.isSeparator())
+					{
+						gotNextIndex = true;
+						break;
+					}
+				}
+
+				if (gotNextIndex)
+				{
+					while (nextIdx > mRangeEnd)
+					{
+						if (maxElemIdx == mRangeEnd)
+							break;
+
+						mParent->scrollDown();
+					}
+
+					if (nextIdx <= mRangeEnd)
+					{
+						UINT32 visIdx = 0;
+						for (auto& visElem : mVisibleElements)
+						{
+							if (visElem.idx == nextIdx)
+							{
+								setSelected(visIdx);
+								break;
+							}
+
+							visIdx++;
+						}
+					}
+				}
+			}
+		}
+			return true;
+		case GUICommandEventType::MoveUp:
+		{
+			{
+				UINT32 curIdx = mVisibleElements[mSelectedIdx].idx;
+				INT32 prevIdx = curIdx;
+				bool gotNextIndex = false;
+
+				for (prevIdx = (INT32)curIdx - 1; prevIdx >= 0; prevIdx--)
+				{
+					GUIDropDownDataEntry& entry = mDropDownData.entries[prevIdx];
+					if (!entry.isSeparator())
+					{
+						gotNextIndex = true;
+						break;
+					}
+				}
+
+				if (gotNextIndex)
+				{
+					while (prevIdx < (INT32)mRangeStart)
+					{
+						if (mRangeStart == 0)
+							break;
+
+						mParent->scrollUp();
+					}
+
+					if (prevIdx >= (INT32)mRangeStart)
+					{
+						UINT32 visIdx = 0;
+						for (auto& visElem : mVisibleElements)
+						{
+							if (visElem.idx == prevIdx)
+							{
+								setSelected(visIdx);
+								break;
+							}
+
+							visIdx++;
+						}
+					}
+				}
+			}
+		}
+			return true;
+		case GUICommandEventType::Escape:
+		case GUICommandEventType::MoveLeft:
+			mParent->close();
+			return true;
+		case GUICommandEventType::MoveRight:
+		{
+			GUIDropDownDataEntry& entry = mDropDownData.entries[mVisibleElements[mSelectedIdx].idx];
+			if (entry.isSubMenu())
+				mParent->elementActivated(mVisibleElements[mSelectedIdx].idx);
+		}
+			return true;
+		case GUICommandEventType::Return:
+			mParent->elementActivated(mVisibleElements[mSelectedIdx].idx);
+			return true;
+		}
+
+		// TODO - When closing a sub-menu I might need to also call GUIDropDownBoxManager::instance().closeDropDownBox();
+		// TODO - I should be able to set focus to this element, so only the most recent element receives keyboard input
+
+		return false;
+	}
+
+	void GUIDropDownContent::setSelected(UINT32 idx)
+	{
+		if (mSelectedIdx != UINT_MAX)
+			mVisibleElements[mSelectedIdx].button->_setOn(false);
+
+		mSelectedIdx = idx;
+		mVisibleElements[mSelectedIdx].button->_setOn(true);
+
+		mParent->elementSelected(mVisibleElements[mSelectedIdx].idx, mVisibleElements[mSelectedIdx].button->_getCachedBounds());
+	}
+
+	Vector2I GUIDropDownContent::_getOptimalSize() const
+	{
+		Vector2I optimalSize;
+		for (auto& visElem : mVisibleElements)
+		{
+			const GUIDropDownDataEntry& element = mDropDownData.entries[visElem.idx];
+
+			optimalSize.y += (INT32)getElementHeight(visElem.idx);
+
+			if (element.isSeparator())
+				optimalSize.x = std::max(optimalSize.x, visElem.separator->_getOptimalSize().x);
+			else
+				optimalSize.x = std::max(optimalSize.x, visElem.button->_getOptimalSize().x);
+		}
+
+		return optimalSize;
+	}
+
+	void GUIDropDownContent::updateClippedBounds()
+	{
+		Vector2I offset = _getOffset();
+		mClippedBounds = Rect2I(offset.x, offset.y, _getWidth(), _getHeight());
+
+		Rect2I localClipRect(mClipRect.x + mOffset.x, mClipRect.y + mOffset.y, mClipRect.width, mClipRect.height);
+		mClippedBounds.clip(localClipRect);
+	}
+
+	void GUIDropDownContent::_updateLayoutInternal(INT32 x, INT32 y, UINT32 width, UINT32 height,
+		Rect2I clipRect, UINT8 widgetDepth, UINT16 areaDepth)
+	{
+		INT32 yOffset = 0;
+		for (auto& visElem : mVisibleElements)
+		{
+			const GUIDropDownDataEntry& element = mDropDownData.entries[visElem.idx];
+
+			GUIElement* guiMainElement = nullptr;
+			if (element.isSeparator())
+				guiMainElement = visElem.separator;
+			else
+				guiMainElement = visElem.button;
+
+			UINT32 elemHeight = getElementHeight(visElem.idx);
+			Vector2I offset(x, yOffset);
+			yOffset += elemHeight;
+
+			guiMainElement->setOffset(offset);
+			guiMainElement->setWidth(width);
+			guiMainElement->setHeight(elemHeight);
+			guiMainElement->_setAreaDepth(areaDepth);
+			guiMainElement->_setWidgetDepth(widgetDepth);
+
+			Rect2I elemClipRect(clipRect.x - offset.x, clipRect.y - offset.y, clipRect.width, clipRect.height);
+			guiMainElement->_setClipRect(elemClipRect);
+
+			// Shortcut label
+			GUILabel* shortcutLabel = visElem.shortcutLabel;
+			if (shortcutLabel != nullptr)
+			{
+				shortcutLabel->setOffset(offset);
+				shortcutLabel->setWidth(width);
+				shortcutLabel->setHeight(elemHeight);
+				shortcutLabel->_setAreaDepth(areaDepth);
+				shortcutLabel->_setWidgetDepth(widgetDepth);
+				shortcutLabel->_setClipRect(elemClipRect);
+			}
+		}
+	}
+
+	const String& GUIDropDownContent::getGUITypeName()
+	{
+		static String typeName = "GUIDropDownContent";
+		return typeName;
+	}
+}

+ 3 - 2
TODO.txt

@@ -23,8 +23,9 @@ MenuItem
 
 TEST the new MenuItem (C#) and menu shortcut GUI, and menu shortcut functionality
 
-While I'm dealing with menus also add support for keyboard navigation (this also includes GUIList)
- - arrows for navigation, enter to confirm, esc to close
+When closing a sub-menu I think I need to call "GUIDropDownBoxManager::instance().closeDropDownBox();" at some point (i.e. when last sub-menu is closed?).
+ - Also when a submenu is closed or opened I should set some kind of focus on it, so it knows to accept/reject keyboard input
+ - "Esc" should move back one sub-menu instead of closing all of it
 
 ----------------------------------------------------------------------
 VisualStudio integration