BsGUIDropDownBox.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. #include "BsGUIDropDownBox.h"
  2. #include "BsGUIArea.h"
  3. #include "BsGUILayout.h"
  4. #include "BsGUITexture.h"
  5. #include "BsGUIButton.h"
  6. #include "BsGUIContent.h"
  7. #include "BsGUISkin.h"
  8. #include "CmViewport.h"
  9. #include "BsGUIListBox.h"
  10. #include "CmSceneObject.h"
  11. using namespace CamelotFramework;
  12. namespace BansheeEngine
  13. {
  14. const UINT32 GUIDropDownBox::DROP_DOWN_BOX_WIDTH = 150;
  15. GUIDropDownData GUIDropDownData::separator()
  16. {
  17. GUIDropDownData data;
  18. data.mType = Type::Separator;
  19. data.mCallback = nullptr;
  20. return data;
  21. }
  22. GUIDropDownData GUIDropDownData::button(const WString& label, std::function<void()> callback)
  23. {
  24. GUIDropDownData data;
  25. data.mLabel = label;
  26. data.mType = Type::Entry;
  27. data.mCallback = callback;
  28. return data;
  29. }
  30. GUIDropDownData GUIDropDownData::subMenu(const WString& label, const Vector<GUIDropDownData>::type& entries)
  31. {
  32. GUIDropDownData data;
  33. data.mLabel = label;
  34. data.mType = Type::SubMenu;
  35. data.mChildEntries = entries;
  36. return data;
  37. }
  38. GUIDropDownAreaPlacement GUIDropDownAreaPlacement::aroundPosition(const CM::Int2& position)
  39. {
  40. GUIDropDownAreaPlacement instance;
  41. instance.mType = Type::Position;
  42. instance.mPosition = position;
  43. return instance;
  44. }
  45. GUIDropDownAreaPlacement GUIDropDownAreaPlacement::aroundBoundsVert(const CM::Rect& bounds)
  46. {
  47. GUIDropDownAreaPlacement instance;
  48. instance.mType = Type::BoundsVert;
  49. instance.mBounds = bounds;
  50. return instance;
  51. }
  52. GUIDropDownAreaPlacement GUIDropDownAreaPlacement::aroundBoundsHorz(const CM::Rect& bounds)
  53. {
  54. GUIDropDownAreaPlacement instance;
  55. instance.mType = Type::BoundsHorz;
  56. instance.mBounds = bounds;
  57. return instance;
  58. }
  59. GUIDropDownBox::GUIDropDownBox(const HSceneObject& parent)
  60. :GUIWidget(parent), mPage(0), mBackgroundFrame(nullptr), mScrollUpStyle(nullptr),
  61. mScrollDownStyle(nullptr), mEntryBtnStyle(nullptr), mEntryExpBtnStyle(nullptr),
  62. mSeparatorStyle(nullptr), mBackgroundStyle(nullptr), mBackgroundArea(nullptr), mContentArea(nullptr),
  63. mContentLayout(nullptr), mScrollUpBtn(nullptr), mScrollDownBtn(nullptr), x(0), y(0), width(0), height(0),
  64. mType(GUIDropDownType::ListBox)
  65. {
  66. }
  67. GUIDropDownBox::~GUIDropDownBox()
  68. {
  69. closeSubMenu();
  70. }
  71. void GUIDropDownBox::initialize(Viewport* target, RenderWindow* window, const GUIDropDownAreaPlacement& placement,
  72. const CM::Vector<GUIDropDownData>::type& elements, const GUISkin& skin, GUIDropDownType type)
  73. {
  74. GUIWidget::initialize(target, window);
  75. String stylePrefix = "";
  76. switch(type)
  77. {
  78. case GUIDropDownType::ContextMenu:
  79. stylePrefix = "ContextMenu";
  80. break;
  81. case GUIDropDownType::ListBox:
  82. stylePrefix = "ListBox";
  83. break;
  84. case GUIDropDownType::MenuBar:
  85. stylePrefix = "MenuBar";
  86. break;
  87. }
  88. mType = type;
  89. mElements = elements;
  90. mScrollUpStyle = skin.getStyle(stylePrefix + "ScrollUpBtn");
  91. mScrollDownStyle = skin.getStyle(stylePrefix + "ScrollDownBtn");
  92. mEntryBtnStyle = skin.getStyle(stylePrefix + "EntryBtn");
  93. mEntryExpBtnStyle = skin.getStyle(stylePrefix + "EntryExpBtn");
  94. mSeparatorStyle = skin.getStyle(stylePrefix + "Separator");
  95. mBackgroundStyle = skin.getStyle(stylePrefix + "Frame");
  96. mScrollUpBtnArrow = skin.getStyle("ScrollUpBtnArrow")->normal.texture;
  97. mScrollDownBtnArrow = skin.getStyle("ScrollDownBtnArrow")->normal.texture;
  98. setDepth(0); // Needs to be in front of everything
  99. setSkin(skin);
  100. Rect dropDownListBounds = placement.getBounds();
  101. int potentialLeftStart = 0;
  102. int potentialRightStart = 0;
  103. int potentialTopStart = 0;
  104. int potentialBottomStart = 0;
  105. switch(placement.getType())
  106. {
  107. case GUIDropDownAreaPlacement::Type::Position:
  108. potentialLeftStart = potentialRightStart = placement.getPosition().x;
  109. potentialTopStart = potentialBottomStart = placement.getPosition().y;
  110. break;
  111. case GUIDropDownAreaPlacement::Type::BoundsHorz:
  112. potentialRightStart = placement.getBounds().x;
  113. potentialLeftStart = placement.getBounds().x + placement.getBounds().width;
  114. potentialBottomStart = placement.getBounds().y + placement.getBounds().height;
  115. potentialTopStart = placement.getBounds().y;
  116. break;
  117. case GUIDropDownAreaPlacement::Type::BoundsVert:
  118. potentialRightStart = placement.getBounds().x + placement.getBounds().width;
  119. potentialLeftStart = placement.getBounds().x;
  120. potentialBottomStart = placement.getBounds().y;
  121. potentialTopStart = placement.getBounds().y + placement.getBounds().height;
  122. break;
  123. }
  124. // Determine x position and whether to align to left or right side of the drop down list
  125. UINT32 availableRightwardWidth = (UINT32)std::max(0, (target->getLeft() + target->getWidth()) - potentialRightStart);
  126. UINT32 availableLeftwardWidth = (UINT32)std::max(0, potentialLeftStart - target->getLeft());
  127. //// Prefer right if possible
  128. if(DROP_DOWN_BOX_WIDTH <= availableRightwardWidth)
  129. x = potentialRightStart;
  130. else
  131. {
  132. if(availableRightwardWidth >= availableLeftwardWidth)
  133. x = potentialRightStart;
  134. else
  135. x = potentialLeftStart - std::min(DROP_DOWN_BOX_WIDTH, availableLeftwardWidth);
  136. }
  137. // Determine maximum width
  138. UINT32 maxPossibleWidth = (UINT32)std::max(0, (target->getLeft() + target->getWidth()) - x);
  139. width = std::min(DROP_DOWN_BOX_WIDTH, maxPossibleWidth);
  140. // Determine y position and whether to open upward or downward
  141. UINT32 availableDownwardHeight = (UINT32)std::max(0, (target->getTop() + target->getHeight()) - potentialBottomStart);
  142. UINT32 availableUpwardHeight = (UINT32)std::max(0, potentialTopStart - target->getTop());
  143. //// Prefer down if possible
  144. UINT32 helperElementHeight = mScrollUpStyle->height + mScrollDownStyle->height + mBackgroundStyle->margins.top + mBackgroundStyle->margins.bottom;
  145. UINT32 maxNeededHeight = helperElementHeight;
  146. UINT32 numElements = (UINT32)mElements.size();
  147. for(UINT32 i = 0; i < numElements; i++)
  148. maxNeededHeight += getElementHeight(i);
  149. height = 0;
  150. if(maxNeededHeight <= availableDownwardHeight)
  151. {
  152. y = potentialBottomStart;
  153. height = availableDownwardHeight;
  154. }
  155. else
  156. {
  157. if(availableDownwardHeight >= availableUpwardHeight)
  158. {
  159. y = potentialBottomStart;
  160. height = availableDownwardHeight;
  161. }
  162. else
  163. {
  164. y = potentialTopStart - std::min(maxNeededHeight, availableUpwardHeight);
  165. height = availableUpwardHeight;
  166. }
  167. }
  168. // Content area
  169. mContentArea = GUIArea::create(*this, x, y, width, height);
  170. mContentLayout = &mContentArea->getLayout().addLayoutY();
  171. // Background frame
  172. mBackgroundArea = GUIArea::create(*this, x, y, width, height);
  173. mBackgroundArea->setDepth(102);
  174. mBackgroundArea->getLayout().addElement(GUITexture::create(*this, GUIImageScaleMode::ScaleToFit, mBackgroundStyle));
  175. updateGUIElements();
  176. }
  177. void GUIDropDownBox::updateGUIElements()
  178. {
  179. // Remove all elements from content layout
  180. while(mContentLayout->getNumChildren() > 0)
  181. mContentLayout->removeChildAt(mContentLayout->getNumChildren() - 1);
  182. // Determine if we need scroll up and/or down buttons, number of visible elements and actual height
  183. bool needsScrollUp = mPage > 0;
  184. UINT32 numElements = (UINT32)mElements.size();
  185. UINT32 usedHeight = mBackgroundStyle->margins.top + mBackgroundStyle->margins.bottom;
  186. UINT32 pageStart = 0, pageEnd = 0;
  187. UINT32 curPage = 0;
  188. bool needsScrollDown = false;
  189. for(UINT32 i = 0; i < numElements; i++)
  190. {
  191. usedHeight += getElementHeight(i);
  192. pageEnd++;
  193. if(usedHeight > height)
  194. {
  195. usedHeight += mScrollDownStyle->height;
  196. needsScrollDown = true;
  197. // Remove last few elements until we fit again
  198. while(usedHeight > height && i >= 0)
  199. {
  200. usedHeight -= getElementHeight(i);
  201. pageEnd--;
  202. i--;
  203. }
  204. // We found our page and are done
  205. if(curPage == mPage)
  206. break;
  207. // Nothing fits, break out of infinite loop
  208. if(pageStart == pageEnd)
  209. break;
  210. pageStart = pageEnd;
  211. usedHeight = mBackgroundStyle->margins.top + mBackgroundStyle->margins.bottom;
  212. usedHeight += mScrollUpStyle->height;
  213. curPage++;
  214. }
  215. }
  216. // Add scroll up button
  217. if(needsScrollUp)
  218. {
  219. if(mScrollUpBtn == nullptr)
  220. {
  221. mScrollUpBtn = GUIButton::create(*this, GUIContent(L"", mScrollUpBtnArrow), mScrollUpStyle);
  222. mScrollUpBtn->onClick.connect(boost::bind(&GUIDropDownBox::scrollUp, this));
  223. }
  224. mContentLayout->addElement(mScrollUpBtn);
  225. }
  226. else
  227. {
  228. if(mScrollUpBtn != nullptr)
  229. {
  230. GUIElement::destroy(mScrollUpBtn);
  231. mScrollUpBtn = nullptr;
  232. }
  233. }
  234. CM::Vector<GUITexture*>::type newSeparators;
  235. CM::Vector<GUIButton*>::type newEntryBtns;
  236. CM::Vector<GUIButton*>::type newExpEntryBtns;
  237. for(UINT32 i = pageStart; i < pageEnd; i++)
  238. {
  239. GUIDropDownData& element = mElements[i];
  240. if(element.isSeparator())
  241. {
  242. GUITexture* separator = nullptr;
  243. if(!mCachedSeparators.empty())
  244. {
  245. separator = mCachedSeparators.back();
  246. mCachedSeparators.erase(mCachedSeparators.end() - 1);
  247. }
  248. else
  249. {
  250. separator = GUITexture::create(*this, GUIImageScaleMode::StretchToFit, mSeparatorStyle);
  251. }
  252. mContentLayout->addElement(separator);
  253. newSeparators.push_back(separator);
  254. }
  255. else if(element.isSubMenu())
  256. {
  257. GUIButton* expEntryBtn = nullptr;
  258. if(!mCachedExpEntryBtns.empty())
  259. {
  260. expEntryBtn = mCachedExpEntryBtns.back();
  261. mCachedExpEntryBtns.erase(mCachedExpEntryBtns.end() - 1);
  262. }
  263. else
  264. {
  265. expEntryBtn = GUIButton::create(*this, mElements[i].getLabel(), mEntryExpBtnStyle);
  266. expEntryBtn->onHover.connect(boost::bind(&GUIDropDownBox::openSubMenu, this, expEntryBtn, i));
  267. }
  268. mContentLayout->addElement(expEntryBtn);
  269. newExpEntryBtns.push_back(expEntryBtn);
  270. }
  271. else
  272. {
  273. GUIButton* entryBtn = nullptr;
  274. if(!mCachedEntryBtns.empty())
  275. {
  276. entryBtn = mCachedEntryBtns.back();
  277. mCachedEntryBtns.erase(mCachedEntryBtns.end() - 1);
  278. }
  279. else
  280. {
  281. entryBtn = GUIButton::create(*this, mElements[i].getLabel(), mEntryBtnStyle);
  282. entryBtn->onClick.connect(boost::bind(&GUIDropDownBox::elementClicked, this, i));
  283. }
  284. mContentLayout->addElement(entryBtn);
  285. newEntryBtns.push_back(entryBtn);
  286. }
  287. }
  288. // Destroy all unused cached elements
  289. for(auto& elem : mCachedSeparators)
  290. GUIElement::destroy(elem);
  291. for(auto& elem : mCachedEntryBtns)
  292. GUIElement::destroy(elem);
  293. for(auto& elem : mCachedExpEntryBtns)
  294. GUIElement::destroy(elem);
  295. mCachedSeparators = newSeparators;
  296. mCachedEntryBtns = newEntryBtns;
  297. mCachedExpEntryBtns = newExpEntryBtns;
  298. // Add scroll down button
  299. if(needsScrollDown)
  300. {
  301. if(mScrollDownBtn == nullptr)
  302. {
  303. mScrollDownBtn = GUIButton::create(*this, GUIContent(L"", mScrollDownBtnArrow), mScrollDownStyle);
  304. mScrollDownBtn->onClick.connect(boost::bind(&GUIDropDownBox::scrollDown, this));
  305. }
  306. mContentLayout->addElement(mScrollDownBtn);
  307. }
  308. else
  309. {
  310. if(mScrollDownBtn != nullptr)
  311. {
  312. GUIElement::destroy(mScrollDownBtn);
  313. mScrollDownBtn = nullptr;
  314. }
  315. }
  316. // Resize and reposition areas
  317. mBackgroundArea->setSize(width, usedHeight);
  318. mBackgroundArea->setPosition(x, y);
  319. UINT32 contentWidth = (UINT32)std::max(0, (INT32)width - (INT32)mBackgroundStyle->margins.left - (INT32)mBackgroundStyle->margins.right);
  320. UINT32 contentHeight = (UINT32)std::max(0, (INT32)usedHeight - (INT32)mBackgroundStyle->margins.top - (INT32)mBackgroundStyle->margins.bottom);
  321. mContentArea->setSize(contentWidth, contentHeight);
  322. mContentArea->setPosition(x + mBackgroundStyle->margins.left, y + mBackgroundStyle->margins.top);
  323. }
  324. UINT32 GUIDropDownBox::getElementHeight(CM::UINT32 idx) const
  325. {
  326. if(mElements[idx].isSeparator())
  327. return mSeparatorStyle->height;
  328. else if(mElements[idx].isSubMenu())
  329. return mEntryExpBtnStyle->height;
  330. else
  331. return mEntryBtnStyle->height;
  332. }
  333. void GUIDropDownBox::scrollDown()
  334. {
  335. mPage++;
  336. updateGUIElements();
  337. closeSubMenu();
  338. }
  339. void GUIDropDownBox::scrollUp()
  340. {
  341. if(mPage > 0)
  342. {
  343. mPage--;
  344. updateGUIElements();
  345. }
  346. closeSubMenu();
  347. }
  348. void GUIDropDownBox::closeSubMenu()
  349. {
  350. if(mSubMenuSO != nullptr)
  351. {
  352. mSubMenuSO->destroy();
  353. mSubMenuSO = HSceneObject();
  354. mSubMenuDropDownBox = GameObjectHandle<GUIDropDownBox>();
  355. }
  356. }
  357. void GUIDropDownBox::elementClicked(UINT32 idx)
  358. {
  359. closeSubMenu();
  360. mElements[idx].getCallback()();
  361. }
  362. void GUIDropDownBox::openSubMenu(GUIButton* source, UINT32 idx)
  363. {
  364. closeSubMenu();
  365. mSubMenuSO = SceneObject::create("DropDownBox");
  366. mSubMenuDropDownBox = mSubMenuSO->addComponent<GUIDropDownBox>();
  367. mSubMenuDropDownBox->initialize(getTarget(), getOwnerWindow(),
  368. GUIDropDownAreaPlacement::aroundBoundsVert(source->getBounds()), mElements[idx].getSubMenuEntries(), getSkin(), mType);
  369. }
  370. }