Просмотр исходного кода

Much more flexible way of dealing with drop down boxes

Marko Pintera 12 лет назад
Родитель
Сommit
2d8119eafc

+ 5 - 0
BansheeEngine/Include/BsEngineGUI.h

@@ -79,6 +79,11 @@ namespace BansheeEngine
 		static const CM::String DropDownBoxBtnUpNormalTex;
 		static const CM::String DropDownBoxBtnUpNormalTex;
 		static const CM::String DropDownBoxBtnDownNormalTex;
 		static const CM::String DropDownBoxBtnDownNormalTex;
 
 
+		static const CM::String DropDownBoxEntryExpNormalTex;
+		static const CM::String DropDownBoxEntryExpHoverTex;
+
+		static const CM::String DropDownSeparatorTex;
+
 		static const CM::String DropDownBoxBtnUpArrowTex;
 		static const CM::String DropDownBoxBtnUpArrowTex;
 		static const CM::String DropDownBoxBtnDownArrowTex;
 		static const CM::String DropDownBoxBtnDownArrowTex;
 	};
 	};

+ 53 - 6
BansheeEngine/Include/BsGUIDropDownBox.h

@@ -4,24 +4,71 @@
 
 
 namespace BansheeEngine
 namespace BansheeEngine
 {
 {
+	class BS_EXPORT GUIDropDownData
+	{
+		enum class Type
+		{
+			Separator,
+			Entry,
+			EntryExpandable
+		};
+
+	public:
+		static GUIDropDownData separator();
+		static GUIDropDownData button(const CM::WString& label, std::function<void()> callback, bool isExpandable = false);
+
+		bool isSeparator() const { return mType == Type::Separator; }
+		bool isExpandable() const { return mType == Type::EntryExpandable; }
+
+		const CM::WString& getLabel() const { return mLabel; }
+		std::function<void()> getCallback() const { return mCallback; }
+	private:
+		GUIDropDownData() { }
+
+		std::function<void()> mCallback;
+		CM::WString mLabel;
+		Type mType; 
+	};
+
 	class BS_EXPORT GUIDropDownBox : public GUIWidget
 	class BS_EXPORT GUIDropDownBox : public GUIWidget
 	{
 	{
 	public:
 	public:
 		GUIDropDownBox(const CM::HSceneObject& parent);
 		GUIDropDownBox(const CM::HSceneObject& parent);
 		~GUIDropDownBox();
 		~GUIDropDownBox();
 
 
-		void initialize(CM::Viewport* target, CM::RenderWindow* window, GUIDropDownList* parentList,
-			const CM::Vector<CM::WString>::type& elements, std::function<void(CM::UINT32)> selectedCallback, const GUISkin& skin);
+		void initialize(CM::Viewport* target, CM::RenderWindow* window, GUIElement* parentElem,
+			const CM::Vector<GUIDropDownData>::type& elements, const GUISkin& skin);
 	private:
 	private:
 		static const CM::UINT32 DROP_DOWN_BOX_WIDTH;
 		static const CM::UINT32 DROP_DOWN_BOX_WIDTH;
 
 
-		std::function<void(CM::UINT32)> mSelectedDropDownEntryCallback;
-		CM::Vector<CM::WString>::type mDropDownElements;
-		CM::Vector<GUIButton*>::type mDropDownElementButtons;
+		CM::Vector<GUIDropDownData>::type mElements;
 		CM::UINT32 mDropDownStartIdx;
 		CM::UINT32 mDropDownStartIdx;
 
 
+		CM::Vector<GUITexture*>::type mCachedSeparators;
+		CM::Vector<GUIButton*>::type mCachedEntryBtns;
+		CM::Vector<GUIButton*>::type mCachedExpEntryBtns;
+		GUIButton* mScrollUpBtn;
+		GUIButton* mScrollDownBtn;
+		GUITexture* mBackgroundFrame;
+
+		const GUIElementStyle* mScrollUpStyle;
+		const GUIElementStyle* mScrollDownStyle;
+		const GUIElementStyle* mEntryBtnStyle;
+		const GUIElementStyle* mEntryExpBtnStyle;
+		const GUIElementStyle* mSeparatorStyle;
+		const GUIElementStyle* mBackgroundStyle;
+		SpriteTexturePtr mScrollUpBtnArrow;
+		SpriteTexturePtr mScrollDownBtnArrow;
+
+		GUIArea* mBackgroundArea;
+		GUIArea* mContentArea;
+		GUILayout* mContentLayout;
+
+		void updateGUIElements(CM::INT32 x, CM::INT32 y, CM::UINT32 width, CM::UINT32 height);
+
 		void scrollDown();
 		void scrollDown();
 		void scrollUp();
 		void scrollUp();
-		void entrySelected(CM::UINT32 idx);
+
+		CM::UINT32 getElementHeight(CM::UINT32 idx) const;
 	};
 	};
 }
 }

+ 3 - 0
BansheeEngine/Include/BsGUILayout.h

@@ -30,6 +30,9 @@ namespace BansheeEngine
 		void removeFlexibleSpace(GUIFlexibleSpace& space);
 		void removeFlexibleSpace(GUIFlexibleSpace& space);
 		GUIFlexibleSpace& insertFlexibleSpace(CM::UINT32 idx);
 		GUIFlexibleSpace& insertFlexibleSpace(CM::UINT32 idx);
 
 
+		CM::UINT32 getNumChildren() const { return (CM::UINT32)mChildren.size(); }
+		void removeChildAt(CM::UINT32 idx);
+
 		CM::UINT32 _getOptimalWidth() const { return mOptimalWidth; }
 		CM::UINT32 _getOptimalWidth() const { return mOptimalWidth; }
 		CM::UINT32 _getOptimalHeight() const { return mOptimalHeight; }
 		CM::UINT32 _getOptimalHeight() const { return mOptimalHeight; }
 		Type _getType() const { return GUIElementBase::Type::Layout; }
 		Type _getType() const { return GUIElementBase::Type::Layout; }

+ 46 - 0
BansheeEngine/Source/BsEngineGUI.cpp

@@ -77,6 +77,11 @@ namespace BansheeEngine
 	const String EngineGUI::DropDownBoxBtnUpNormalTex = "..\\..\\..\\..\\Data\\Editor\\Skin\\DropDownBoxBtnUpNormal.psd";
 	const String EngineGUI::DropDownBoxBtnUpNormalTex = "..\\..\\..\\..\\Data\\Editor\\Skin\\DropDownBoxBtnUpNormal.psd";
 	const String EngineGUI::DropDownBoxBtnDownNormalTex = "..\\..\\..\\..\\Data\\Editor\\Skin\\DropDownBoxBtnDownNormal.psd";
 	const String EngineGUI::DropDownBoxBtnDownNormalTex = "..\\..\\..\\..\\Data\\Editor\\Skin\\DropDownBoxBtnDownNormal.psd";
 
 
+	const String EngineGUI::DropDownBoxEntryExpNormalTex = "..\\..\\..\\..\\Data\\Editor\\Skin\\DropDownExpNormal.psd";
+	const String EngineGUI::DropDownBoxEntryExpHoverTex = "..\\..\\..\\..\\Data\\Editor\\Skin\\DropDownExpHover.psd";
+
+	const String EngineGUI::DropDownSeparatorTex = "..\\..\\..\\..\\Data\\Editor\\Skin\\DropDownSeparator.psd";
+
 	const String EngineGUI::DropDownBoxBtnUpArrowTex = "..\\..\\..\\..\\Data\\Editor\\Skin\\DropDownBoxBtnUpArrow.psd";
 	const String EngineGUI::DropDownBoxBtnUpArrowTex = "..\\..\\..\\..\\Data\\Editor\\Skin\\DropDownBoxBtnUpArrow.psd";
 	const String EngineGUI::DropDownBoxBtnDownArrowTex = "..\\..\\..\\..\\Data\\Editor\\Skin\\DropDownBoxBtnDownArrow.psd";
 	const String EngineGUI::DropDownBoxBtnDownArrowTex = "..\\..\\..\\..\\Data\\Editor\\Skin\\DropDownBoxBtnDownArrow.psd";
 
 
@@ -518,6 +523,29 @@ namespace BansheeEngine
 
 
 		mSkin.setStyle("DropDownEntryBtn", dropDownEntryBtnStyle);
 		mSkin.setStyle("DropDownEntryBtn", dropDownEntryBtnStyle);
 
 
+		// DropDown entry button with expand
+		HTexture dropDownExpEntryBtnNormal = static_resource_cast<Texture>(Importer::instance().import(FileSystem::getCurrentPath() + "\\" + DropDownBoxEntryExpNormalTex));
+		HTexture dropDownExpEntryBtnHover = static_resource_cast<Texture>(Importer::instance().import(FileSystem::getCurrentPath() + "\\" + DropDownBoxEntryExpNormalTex));
+
+		GUIElementStyle dropDownEntryExpBtnStyle;
+		dropDownEntryExpBtnStyle.normal.texture = cm_shared_ptr<SpriteTexture, PoolAlloc>(std::cref(dropDownExpEntryBtnNormal));
+		dropDownEntryExpBtnStyle.hover.texture = cm_shared_ptr<SpriteTexture, PoolAlloc>(std::cref(dropDownExpEntryBtnHover));
+		dropDownEntryExpBtnStyle.active.texture = dropDownEntryExpBtnStyle.hover.texture;
+		dropDownEntryExpBtnStyle.fixedHeight = true;
+		dropDownEntryExpBtnStyle.fixedWidth = false;
+		dropDownEntryExpBtnStyle.height = 14;
+		dropDownEntryExpBtnStyle.width = 30;
+		dropDownEntryExpBtnStyle.border.left = 1;
+		dropDownEntryExpBtnStyle.border.right = 6;
+		dropDownEntryExpBtnStyle.border.top = 1;
+		dropDownEntryExpBtnStyle.border.bottom = 1;
+		dropDownEntryExpBtnStyle.font = font;
+		dropDownEntryExpBtnStyle.fontSize = DefaultFontSize;
+		dropDownEntryExpBtnStyle.textHorzAlign = THA_Left;
+		dropDownEntryExpBtnStyle.textVertAlign = TVA_Top;
+
+		mSkin.setStyle("DropDownEntryExpBtn", dropDownEntryExpBtnStyle);
+
 		// DropDown box frame
 		// DropDown box frame
 		HTexture dropDownBoxBgTex = static_resource_cast<Texture>(Importer::instance().import(FileSystem::getCurrentPath() + "\\" + DropDownBoxBgTex));
 		HTexture dropDownBoxBgTex = static_resource_cast<Texture>(Importer::instance().import(FileSystem::getCurrentPath() + "\\" + DropDownBoxBgTex));
 
 
@@ -535,5 +563,23 @@ namespace BansheeEngine
 		dropDownBoxStyle.border.bottom = 1;
 		dropDownBoxStyle.border.bottom = 1;
 
 
 		mSkin.setStyle("DropDownBox", dropDownBoxStyle);
 		mSkin.setStyle("DropDownBox", dropDownBoxStyle);
+
+		// Drop down separator
+		HTexture dropDownSeparatorTex = static_resource_cast<Texture>(Importer::instance().import(FileSystem::getCurrentPath() + "\\" + DropDownSeparatorTex));
+
+		GUIElementStyle dropDownSeparatorStyle;
+		dropDownSeparatorStyle.normal.texture = cm_shared_ptr<SpriteTexture, PoolAlloc>(std::cref(dropDownSeparatorTex));
+		dropDownSeparatorStyle.hover.texture = dropDownSeparatorStyle.normal.texture;
+		dropDownSeparatorStyle.active.texture = dropDownSeparatorStyle.hover.texture;
+		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("DropDownSeparator", dropDownSeparatorStyle);
 	}
 	}
 }
 }

+ 245 - 87
BansheeEngine/Source/BsGUIDropDownBox.cpp

@@ -14,8 +14,30 @@ namespace BansheeEngine
 {
 {
 	const UINT32 GUIDropDownBox::DROP_DOWN_BOX_WIDTH = 150;
 	const UINT32 GUIDropDownBox::DROP_DOWN_BOX_WIDTH = 150;
 
 
+	GUIDropDownData GUIDropDownData::separator()
+	{
+		GUIDropDownData data;
+		data.mType = Type::Separator;
+		data.mCallback = nullptr;
+
+		return data;
+	}
+
+	GUIDropDownData GUIDropDownData::button(const CM::WString& label, std::function<void()> callback, bool isExpandable)
+	{
+		GUIDropDownData data;
+		data.mLabel = label;
+		data.mType = isExpandable ? Type::EntryExpandable : Type::Entry;
+		data.mCallback = callback;
+
+		return data;
+	}
+
 	GUIDropDownBox::GUIDropDownBox(const HSceneObject& parent)
 	GUIDropDownBox::GUIDropDownBox(const HSceneObject& parent)
-		:GUIWidget(parent), mDropDownStartIdx(0)
+		:GUIWidget(parent), mDropDownStartIdx(0), mBackgroundFrame(nullptr), mScrollUpStyle(nullptr),
+		mScrollDownStyle(nullptr), mEntryBtnStyle(nullptr), mEntryExpBtnStyle(nullptr), 
+		mSeparatorStyle(nullptr), mBackgroundStyle(nullptr), mBackgroundArea(nullptr), mContentArea(nullptr), 
+		mContentLayout(nullptr), mScrollUpBtn(nullptr), mScrollDownBtn(nullptr)
 	{
 	{
 
 
 	}
 	}
@@ -25,19 +47,26 @@ namespace BansheeEngine
 
 
 	}
 	}
 
 
-	void GUIDropDownBox::initialize(Viewport* target, RenderWindow* window, GUIDropDownList* parentList, 
-		const CM::Vector<WString>::type& elements, std::function<void(CM::UINT32)> selectedCallback, const GUISkin& skin)
+	void GUIDropDownBox::initialize(Viewport* target, RenderWindow* window, GUIElement* parentElem, 
+		const CM::Vector<GUIDropDownData>::type& elements, const GUISkin& skin)
 	{
 	{
 		GUIWidget::initialize(target, window);
 		GUIWidget::initialize(target, window);
 
 
-		mDropDownElements = elements;
-		mSelectedDropDownEntryCallback = selectedCallback;
+		mElements = elements;
 
 
-		setDepth(0); // Needs to be in front of everything
+		mScrollUpStyle = skin.getStyle("DropDownScrollUpBtn");
+		mScrollDownStyle = skin.getStyle("DropDownScrollDownBtn");
+		mEntryBtnStyle = skin.getStyle("DropDownEntryBtn");
+		mEntryExpBtnStyle = skin.getStyle("DropDownEntryExpBtn");
+		mSeparatorStyle = skin.getStyle("DropDownSeparator");
+		mBackgroundStyle = skin.getStyle("DropDownBox");
 
 
-		const GUIElementStyle* dropDownBoxStyle = skin.getStyle("DropDownBox");
+		mScrollUpBtnArrow = skin.getStyle("DropDownScrollUpBtnArrow")->normal.texture;
+		mScrollDownBtnArrow = skin.getStyle("DropDownScrollDownBtnArrow")->normal.texture;
+
+		setDepth(0); // Needs to be in front of everything
 
 
-		Rect dropDownListBounds = parentList->getBounds();
+		Rect dropDownListBounds = parentElem->getBounds();
 
 
 		Int2 position;
 		Int2 position;
 		// Determine x position and whether to align to left or right side of the drop down list
 		// Determine x position and whether to align to left or right side of the drop down list
@@ -58,125 +87,254 @@ namespace BansheeEngine
 		// Determine maximum width
 		// Determine maximum width
 		UINT32 maxPossibleWidth = (UINT32)std::max(0, (target->getLeft() + target->getWidth()) - position.x);
 		UINT32 maxPossibleWidth = (UINT32)std::max(0, (target->getLeft() + target->getWidth()) - position.x);
 		UINT32 width = std::min(DROP_DOWN_BOX_WIDTH, maxPossibleWidth);
 		UINT32 width = std::min(DROP_DOWN_BOX_WIDTH, maxPossibleWidth);
-		UINT32 contentWidth = (UINT32)std::max(0, (INT32)width - (INT32)dropDownBoxStyle->margins.left - (INT32)dropDownBoxStyle->margins.right);
 
 
 		// Determine y position and whether to open upward or downward
 		// Determine y position and whether to open upward or downward
-		UINT32 scrollButtonUpHeight = skin.getStyle("DropDownScrollUpBtn")->height;
-		UINT32 scrollButtonDownHeight = skin.getStyle("DropDownScrollDownBtn")->height;
-		UINT32 helperElementHeight = scrollButtonUpHeight + scrollButtonDownHeight + dropDownBoxStyle->margins.top + dropDownBoxStyle->margins.bottom;
-		UINT32 elementButtonHeight = skin.getStyle("DropDownEntryBtn")->height;
+		UINT32 helperElementHeight = mScrollUpStyle->height + mScrollDownStyle->height + mBackgroundStyle->margins.top + mBackgroundStyle->margins.bottom;
 
 
-		UINT32 maxNeededHeight = elementButtonHeight * (UINT32)mDropDownElements.size() + helperElementHeight;
-		UINT32 availableDownwardHeight = (UINT32)std::max(0, (target->getTop() + target->getHeight()) - (dropDownListBounds.y + dropDownListBounds.height));
+		UINT32 maxNeededHeight = helperElementHeight;
+		UINT32 numElements = (UINT32)mElements.size();
+		for(UINT32 i = 0; i < numElements; i++)
+			maxNeededHeight += getElementHeight(i);
 
 
+		UINT32 availableDownwardHeight = (UINT32)std::max(0, (target->getTop() + target->getHeight()) - (dropDownListBounds.y + dropDownListBounds.height));
 		UINT32 availableUpwardHeight = (UINT32)std::max(0, dropDownListBounds.y - target->getTop());
 		UINT32 availableUpwardHeight = (UINT32)std::max(0, dropDownListBounds.y - target->getTop());
 
 
 		//// Prefer down if possible
 		//// Prefer down if possible
+		UINT32 height = 0;
 		if(maxNeededHeight <= availableDownwardHeight)
 		if(maxNeededHeight <= availableDownwardHeight)
+		{
 			position.y = dropDownListBounds.y + dropDownListBounds.height;
 			position.y = dropDownListBounds.y + dropDownListBounds.height;
+			height = availableDownwardHeight;
+		}
 		else
 		else
 		{
 		{
 			if(availableDownwardHeight >= availableUpwardHeight)
 			if(availableDownwardHeight >= availableUpwardHeight)
+			{
 				position.y = dropDownListBounds.y + dropDownListBounds.height;
 				position.y = dropDownListBounds.y + dropDownListBounds.height;
+				height = availableDownwardHeight;
+			}
 			else
 			else
+			{
 				position.y = dropDownListBounds.y - std::min(maxNeededHeight, availableUpwardHeight);
 				position.y = dropDownListBounds.y - std::min(maxNeededHeight, availableUpwardHeight);
+				height = availableUpwardHeight;
+			}
 		}
 		}
 
 
-		// Determine height of the box, and how many visible elements we can fit in it
-		UINT32 maxPossibleHeight = (UINT32)std::max(0, (target->getTop() + target->getHeight()) - position.y);
-		UINT32 heightAvailableForContent = (UINT32)std::max(0, (INT32)maxPossibleHeight - (INT32)helperElementHeight);
-		UINT32 numVisElements = 0;
-		
-		UINT32 contentAreaHeight = 0;
-		for(UINT32 i = 0; i < (UINT32)mDropDownElements.size(); i++)
+		// Content area
+		mContentArea = GUIArea::create(*this, position.x, position.y, width, height);
+		mContentLayout = &mContentArea->getLayout().addLayoutY();
+
+		// Background frame
+		mBackgroundArea = GUIArea::create(*this, position.x, position.y, width, height);
+		mBackgroundArea->setDepth(102);
+		mBackgroundArea->getLayout().addElement(GUITexture::create(*this, GUIImageScaleMode::ScaleToFit, skin.getStyle("DropDownBox")));
+
+		updateGUIElements(position.x, position.y, width, height);
+	}
+
+	void GUIDropDownBox::updateGUIElements(CM::INT32 x, CM::INT32 y, CM::UINT32 width, CM::UINT32 height)
+	{
+		// Remove all elements from content layout
+		while(mContentLayout->getNumChildren() > 0)
+			mContentLayout->removeChildAt(mContentLayout->getNumChildren() - 1);
+
+		// Determine if we need scroll up and/or down buttons, number of visible elements and actual height
+		bool needsScrollUp = mDropDownStartIdx > 0;
+		UINT32 numElements = (UINT32)mElements.size();
+
+		UINT32 usedHeight = mBackgroundStyle->margins.top + mBackgroundStyle->margins.bottom;
+		if(needsScrollUp)
+			usedHeight += mScrollUpStyle->height;
+
+		UINT32 numVisibleElements = 0;
+		bool needsScrollDown = false;
+		for(UINT32 i = 0; i < numElements; i++)
 		{
 		{
-			if(contentAreaHeight >= heightAvailableForContent)
-				break;
+			usedHeight += getElementHeight(i);
+			numVisibleElements++;
 
 
-			contentAreaHeight += elementButtonHeight;
-			numVisElements++;
-		}
+			if(usedHeight > height)
+			{
+				usedHeight += mScrollDownStyle->height;
+				needsScrollDown = true;
 
 
-		UINT32 totalHeight = contentAreaHeight + helperElementHeight; 
-		
-		// Scroll up buttons
-		GUIArea* scrollUpBtnArea = GUIArea::create(*this, position.x + dropDownBoxStyle->margins.left, position.y, contentWidth, scrollButtonUpHeight);
-		scrollUpBtnArea->setDepth(100);
-		const GUIElementStyle* scrollUpBtnArrow = skin.getStyle("DropDownScrollUpBtnArrow");
-		GUIButton* scrollUpBtn = GUIButton::create(*this, GUIContent(L"", scrollUpBtnArrow->normal.texture), skin.getStyle("DropDownScrollUpBtn"));
-		scrollUpBtnArea->getLayout().addElement(scrollUpBtn);
+				// Remove last few elements until we fit again
+				UINT32 j = i;
+				while(usedHeight > height && j >= 0)
+				{
+					usedHeight -= getElementHeight(j);
+					numVisibleElements--;
 
 
-		// Entry buttons
-		// TODO - It may happen there is not enough place for even a single element, in which case we just don't render any.
-		//  Some smart solution to deal with that case might be employed, but I think it's a fairly rare case and can be deal with in other ways.
+					j--;
+				}
 
 
-		UINT32 contentAreaYOffset = scrollButtonUpHeight + dropDownBoxStyle->margins.top;
+				break;
+			}
+		}
 
 
-		GUIArea* dropDownEntriesArea = GUIArea::create(*this, position.x + dropDownBoxStyle->margins.left, 
-			position.y + contentAreaYOffset, contentWidth, contentAreaHeight);
-		GUILayout* dropDownEntriesLayout = &dropDownEntriesArea->getLayout().addLayoutY();
+		// Add scroll up button
+		if(needsScrollUp)
+		{
+			if(mScrollUpBtn == nullptr)
+			{
+				mScrollUpBtn = GUIButton::create(*this, GUIContent(L"", mScrollUpBtnArrow), mScrollUpStyle);
+				mScrollUpBtn->onClick.connect(boost::bind(&GUIDropDownBox::scrollUp, this));
+			}
 
 
-		for(UINT32 i = 0; i < numVisElements; i++)
+			mContentLayout->addElement(mScrollUpBtn);			
+		}
+		else
 		{
 		{
-			GUIButton* button = GUIButton::create(*this, mDropDownElements[i], skin.getStyle("DropDownEntryBtn"));
-			button->onClick.connect(boost::bind(&GUIDropDownBox::entrySelected, this, i));
+			if(mScrollUpBtn != nullptr)
+			{
+				GUIElement::destroy(mScrollUpBtn);
+				mScrollUpBtn = nullptr;
+			}
+		}
 
 
-			dropDownEntriesLayout->addElement(button);
-			mDropDownElementButtons.push_back(button);
+		CM::Vector<GUITexture*>::type newSeparators;
+		CM::Vector<GUIButton*>::type newEntryBtns;
+		CM::Vector<GUIButton*>::type newExpEntryBtns;
+		for(UINT32 i = 0; i < numVisibleElements; i++)
+		{
+			GUIDropDownData& element = mElements[i];
+
+			if(element.isSeparator())
+			{
+				GUITexture* separator = nullptr;
+				if(!mCachedSeparators.empty())
+				{
+					separator = mCachedSeparators.back();
+					mCachedSeparators.erase(mCachedSeparators.end() - 1);
+				}
+				else
+				{
+					separator = GUITexture::create(*this, GUIImageScaleMode::StretchToFit, mSeparatorStyle);
+				}
+
+				mContentLayout->addElement(separator);
+				newSeparators.push_back(separator);
+			}
+			else if(element.isExpandable())
+			{
+				GUIButton* expEntryBtn = nullptr;
+				if(!mCachedExpEntryBtns.empty())
+				{
+					expEntryBtn = mCachedExpEntryBtns.back();
+					mCachedExpEntryBtns.erase(mCachedExpEntryBtns.end() - 1);
+				}
+				else
+				{
+					expEntryBtn = GUIButton::create(*this, mElements[i].getLabel(), mEntryExpBtnStyle);
+					expEntryBtn->onClick.connect(mElements[i].getCallback());
+				}
+
+				mContentLayout->addElement(expEntryBtn);
+				newExpEntryBtns.push_back(expEntryBtn);
+			}
+			else
+			{
+				GUIButton* entryBtn = nullptr;
+				if(!mCachedEntryBtns.empty())
+				{
+					entryBtn = mCachedEntryBtns.back();
+					mCachedEntryBtns.erase(mCachedEntryBtns.end() - 1);
+				}
+				else
+				{
+					entryBtn = GUIButton::create(*this, mElements[i].getLabel(), mEntryBtnStyle);
+					entryBtn->onClick.connect(mElements[i].getCallback());
+				}
+
+				mContentLayout->addElement(entryBtn);
+				newEntryBtns.push_back(entryBtn);
+			}
 		}
 		}
 
 
-		// Scroll down buttons
-		UINT32 scrollBtnDownOffset = position.y + contentAreaYOffset + contentAreaHeight;
+		// Destroy all unused cached elements
+		for(auto& elem : mCachedSeparators)
+			GUIElement::destroy(elem);
 
 
-		GUIArea* scrollDownBtnArea = GUIArea::create(*this, position.x + dropDownBoxStyle->margins.left, 
-			scrollBtnDownOffset, contentWidth, scrollButtonDownHeight);
-		scrollDownBtnArea->setDepth(100);
-		const GUIElementStyle* scrollDownBtnArrow = skin.getStyle("DropDownScrollDownBtnArrow");
-		GUIButton* scrollDownBtn = GUIButton::create(*this, GUIContent(L"", scrollDownBtnArrow->normal.texture), skin.getStyle("DropDownScrollDownBtn"));
-		scrollDownBtnArea->getLayout().addElement(scrollDownBtn);
-		
-		// Background frame
-		GUIArea* dropDownBoxArea = GUIArea::create(*this, position.x, position.y, width, totalHeight);
-		dropDownBoxArea->setDepth(102);
-		dropDownBoxArea->getLayout().addElement(GUITexture::create(*this, GUIImageScaleMode::ScaleToFit, skin.getStyle("DropDownBox")));
-	}
+		for(auto& elem : mCachedEntryBtns)
+			GUIElement::destroy(elem);
 
 
-	void GUIDropDownBox::scrollDown()
-	{
-		INT32 maxNumElements = (INT32)mDropDownElements.size();
-		INT32 numVisibleElements = (INT32)mDropDownElementButtons.size();
+		for(auto& elem : mCachedExpEntryBtns)
+			GUIElement::destroy(elem);
 
 
-		INT32 newIdx = mDropDownStartIdx + numVisibleElements;
-		INT32 clampedNewIdx = std::min(newIdx, maxNumElements - numVisibleElements);
-		mDropDownStartIdx = (UINT32)std::min(newIdx, clampedNewIdx);
+		mCachedSeparators = newSeparators;
+		mCachedEntryBtns = newEntryBtns;
+		mCachedExpEntryBtns = newExpEntryBtns;
 
 
-		UINT32 i = mDropDownStartIdx;
-		for(auto& button : mDropDownElementButtons)
+		// Add scroll down button
+		if(needsScrollDown)
 		{
 		{
-			button->setContent(GUIContent(mDropDownElements[i]));
-			i++;
+			if(mScrollDownBtn == nullptr)
+			{
+				mScrollDownBtn = GUIButton::create(*this, GUIContent(L"", mScrollDownBtnArrow), mScrollDownStyle);
+				mScrollDownBtn->onClick.connect(boost::bind(&GUIDropDownBox::scrollDown, this));
+			}
+
+			mContentLayout->addElement(mScrollDownBtn);			
 		}
 		}
+		else
+		{
+			if(mScrollDownBtn != nullptr)
+			{
+				GUIElement::destroy(mScrollDownBtn);
+				mScrollDownBtn = nullptr;
+			}
+		}
+		
+		// Resize and reposition areas
+		mBackgroundArea->setSize(width, usedHeight);
+		mBackgroundArea->setPosition(x, y);
+
+		UINT32 contentWidth = (UINT32)std::max(0, (INT32)width - (INT32)mBackgroundStyle->margins.left - (INT32)mBackgroundStyle->margins.right);
+		UINT32 contentHeight = (UINT32)std::max(0, (INT32)usedHeight - (INT32)mBackgroundStyle->margins.top - (INT32)mBackgroundStyle->margins.bottom);
+		mContentArea->setSize(contentWidth, contentHeight);
+		mContentArea->setPosition(x + mBackgroundStyle->margins.left, y + mBackgroundStyle->margins.top);
 	}
 	}
 
 
-	void GUIDropDownBox::scrollUp()
+	UINT32 GUIDropDownBox::getElementHeight(CM::UINT32 idx) const
 	{
 	{
-		INT32 numVisibleElements = (INT32)mDropDownElementButtons.size();
-
-		INT32 newIdx = mDropDownStartIdx - numVisibleElements;
-		INT32 clampedNewIdx = std::max(newIdx, 0);
-		mDropDownStartIdx = (UINT32)std::min(newIdx, clampedNewIdx);
+		if(mElements[idx].isSeparator())
+			return mSeparatorStyle->height;
+		else if(mElements[idx].isExpandable())
+			return mEntryExpBtnStyle->height;
+		else
+			return mEntryBtnStyle->height;
+	}
 
 
-		UINT32 i = mDropDownStartIdx;
-		for(auto& button : mDropDownElementButtons)
-		{
-			button->setContent(GUIContent(mDropDownElements[i]));
-			i++;
-		}
+	void GUIDropDownBox::scrollDown()
+	{
+		//INT32 maxNumElements = (INT32)mDropDownElements.size();
+		//INT32 numVisibleElements = (INT32)mDropDownElementButtons.size();
+
+		//INT32 newIdx = mDropDownStartIdx + numVisibleElements;
+		//INT32 clampedNewIdx = std::min(newIdx, maxNumElements - numVisibleElements);
+		//mDropDownStartIdx = (UINT32)std::min(newIdx, clampedNewIdx);
+
+		//UINT32 i = mDropDownStartIdx;
+		//for(auto& button : mDropDownElementButtons)
+		//{
+		//	button->setContent(GUIContent(mDropDownElements[i]));
+		//	i++;
+		//}
 	}
 	}
 
 
-	void GUIDropDownBox::entrySelected(UINT32 idx)
+	void GUIDropDownBox::scrollUp()
 	{
 	{
-		if(mSelectedDropDownEntryCallback != nullptr)
-			mSelectedDropDownEntryCallback(mDropDownStartIdx + idx);
+		//INT32 numVisibleElements = (INT32)mDropDownElementButtons.size();
+
+		//INT32 newIdx = mDropDownStartIdx - numVisibleElements;
+		//INT32 clampedNewIdx = std::max(newIdx, 0);
+		//mDropDownStartIdx = (UINT32)std::min(newIdx, clampedNewIdx);
+
+		//UINT32 i = mDropDownStartIdx;
+		//for(auto& button : mDropDownElementButtons)
+		//{
+		//	button->setContent(GUIContent(mDropDownElements[i]));
+		//	i++;
+		//}
 	}
 	}
 }
 }

+ 16 - 0
BansheeEngine/Source/BsGUILayout.cpp

@@ -75,6 +75,22 @@ namespace BansheeEngine
 		markContentAsDirty();
 		markContentAsDirty();
 	}
 	}
 
 
+	void GUILayout::removeChildAt(CM::UINT32 idx)
+	{
+		if(idx < 0 || idx >= (UINT32)mChildren.size())
+			CM_EXCEPT(InvalidParametersException, "Index out of range: " + toString(idx) + ". Valid range: 0 .. " + toString((UINT32)mChildren.size()));
+
+		GUIElementBase* child = mChildren[idx];
+		mChildren.erase(mChildren.begin() + idx);
+
+		if(child->_getType() == GUIElementBase::Type::Element)
+			child->_setParent(nullptr);
+		else
+			cm_delete<PoolAlloc>(child);
+
+		markContentAsDirty();
+	}
+
 	GUIFixedSpace& GUILayout::addSpace(UINT32 size)
 	GUIFixedSpace& GUILayout::addSpace(UINT32 size)
 	{
 	{
 		GUIFixedSpace* entry = cm_new<GUIFixedSpace, PoolAlloc>(size);
 		GUIFixedSpace* entry = cm_new<GUIFixedSpace, PoolAlloc>(size);

+ 10 - 2
BansheeEngine/Source/BsGUIManager.cpp

@@ -512,8 +512,16 @@ namespace BansheeEngine
 		mDropDownBox = mDropDownSO->addComponent<GUIDropDownBox>();
 		mDropDownBox = mDropDownSO->addComponent<GUIDropDownBox>();
 
 
 		GUIWidget& widget = parentList->_getParentWidget();
 		GUIWidget& widget = parentList->_getParentWidget();
-		mDropDownBox->initialize(widget.getTarget(), widget.getOwnerWindow(), parentList, elements, 
-			boost::bind(&GUIManager::closeDropDownBox, this, _1), skin);
+
+		Vector<GUIDropDownData>::type dropDownData;
+		UINT32 i = 0;
+		for(auto& elem : elements)
+		{
+			dropDownData.push_back(GUIDropDownData::button(elem, boost::bind(&GUIManager::closeDropDownBox, this, i)));
+			i++;
+		}
+
+		mDropDownBox->initialize(widget.getTarget(), widget.getOwnerWindow(), parentList, dropDownData, skin);
 
 
 		mDropDownBoxOpenScheduled = true;
 		mDropDownBoxOpenScheduled = true;
 		mDropDownSelectionMade = selectedCallback;
 		mDropDownSelectionMade = selectedCallback;

+ 12 - 10
DropDown.txt

@@ -1,16 +1,18 @@
-Add support for images on buttons. Replace dropdownbox scroll buttons with that.
-Drop down box gets closed the same frame it was opened on! Need to delay the close one frame.
-
 GUI ignores image in GUIContent for most elements.
 GUI ignores image in GUIContent for most elements.
- - Also I don't have a way of specifiying drop down box skin. It always uses whichever skin is active but this might be a problem when
-   the user wants his own skin for the game and I want my own skin for the editor. I might want to cache the active skin on DropDownList initialization
-   and then provide it to GUIManager::openDropDownBox.
 
 
 Immediate TODO:
 Immediate TODO:
- - Add support for images in GUIButton
-   - With placement options (left/right of the text)
- - Update DropDown ScrollUp and ScrollDown buttons so they don't use two GUIAreas each and instead use a GUIButton with an image
-   - This will require slightly updated hover graphic (Arrow icon no longer should change color)
+ - Add separator images
+   - Make it in PS, just a single image
+   - Add support for it in DropDownBox
+      - Elements with "-" as contents automatically become separators
+	  - PROBLEM: With scrolling. Right now I assume all elements are the same size but when separators
+	    are used I can no longer scroll that way. TODO
+		 - Add a better way of updating GUIButtons (a way that actuall adds/removes them)
+		 - Can also be used for handling of add/remove scrollup/scrolldown buttons
+ - Add expand right arrow
+   - Add scale9grid where the right side has the arrow
+   - Callback hooked up just opens another context window
+     - Managing this is taken care of on a higher level (GUIManager or similar)
  - Upon scroll up/down remove ScrollUp or ScrollDown buttons if that direction isn't usable anymore
  - Upon scroll up/down remove ScrollUp or ScrollDown buttons if that direction isn't usable anymore
   - Delete its GUIArea and offset the content area, and possibly ScrollDown area
   - Delete its GUIArea and offset the content area, and possibly ScrollDown area