BsGUIDropDownMenu.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481
  1. #include "BsGUIDropDownMenu.h"
  2. #include "BsGUIPanel.h"
  3. #include "BsGUILayoutY.h"
  4. #include "BsGUILayoutX.h"
  5. #include "BsGUITexture.h"
  6. #include "BsGUILabel.h"
  7. #include "BsGUIButton.h"
  8. #include "BsGUISpace.h"
  9. #include "BsGUIContent.h"
  10. #include "BsGUISkin.h"
  11. #include "BsViewport.h"
  12. #include "BsGUIListBox.h"
  13. #include "BsGUIDropDownBoxManager.h"
  14. #include "BsSceneObject.h"
  15. #include "BsGUIDropDownHitBox.h"
  16. #include "BsGUIDropDownContent.h"
  17. #include "BsDebug.h"
  18. using namespace std::placeholders;
  19. namespace BansheeEngine
  20. {
  21. const UINT32 GUIDropDownMenu::DROP_DOWN_BOX_WIDTH = 250;
  22. GUIDropDownDataEntry GUIDropDownDataEntry::separator()
  23. {
  24. GUIDropDownDataEntry data;
  25. data.mType = Type::Separator;
  26. data.mCallback = nullptr;
  27. return data;
  28. }
  29. GUIDropDownDataEntry GUIDropDownDataEntry::button(const WString& label, std::function<void()> callback, const WString& shortcutTag)
  30. {
  31. GUIDropDownDataEntry data;
  32. data.mLabel = label;
  33. data.mType = Type::Entry;
  34. data.mCallback = callback;
  35. data.mShortcutTag = shortcutTag;
  36. return data;
  37. }
  38. GUIDropDownDataEntry GUIDropDownDataEntry::subMenu(const WString& label, const GUIDropDownData& data)
  39. {
  40. GUIDropDownDataEntry dataEntry;
  41. dataEntry.mLabel = label;
  42. dataEntry.mType = Type::SubMenu;
  43. dataEntry.mChildData = data;
  44. return dataEntry;
  45. }
  46. GUIDropDownMenu::GUIDropDownMenu(const HSceneObject& parent, Viewport* target, const DropDownAreaPlacement& placement,
  47. const GUIDropDownData& dropDownData, const HGUISkin& skin, GUIDropDownType type)
  48. :GUIWidget(parent, target), mRootMenu(nullptr), mFrontHitBox(nullptr), mCaptureHitBox(nullptr), mBackHitBox(nullptr)
  49. {
  50. String stylePrefix = "";
  51. switch(type)
  52. {
  53. case GUIDropDownType::ContextMenu:
  54. stylePrefix = "ContextMenu";
  55. break;
  56. case GUIDropDownType::ListBox:
  57. stylePrefix = "ListBox";
  58. break;
  59. case GUIDropDownType::MenuBar:
  60. stylePrefix = "MenuBar";
  61. break;
  62. }
  63. mScrollUpStyle = stylePrefix + "ScrollUpBtn";
  64. mScrollDownStyle = stylePrefix + "ScrollDownBtn";
  65. mBackgroundStyle = stylePrefix + "Frame";
  66. mContentStyle = stylePrefix + "Content";
  67. mScrollUpBtnArrow = skin->getStyle(stylePrefix + "ScrollUpBtnArrow")->normal.texture;
  68. mScrollDownBtnArrow = skin->getStyle(stylePrefix + "ScrollDownBtnArrow")->normal.texture;
  69. setDepth(0); // Needs to be in front of everything
  70. setSkin(skin);
  71. mFrontHitBox = GUIDropDownHitBox::create(false, false);
  72. mFrontHitBox->onFocusLost.connect(std::bind(&GUIDropDownMenu::dropDownFocusLost, this));
  73. mFrontHitBox->setFocus(true);
  74. GUILayoutData hitboxLayoutData = mFrontHitBox->_getLayoutData();
  75. hitboxLayoutData.setWidgetDepth(0);
  76. hitboxLayoutData.setPanelDepth(std::numeric_limits<INT16>::min());
  77. mFrontHitBox->_setLayoutData(hitboxLayoutData);
  78. mFrontHitBox->_changeParentWidget(this);
  79. mFrontHitBox->_markContentAsDirty();
  80. mBackHitBox = GUIDropDownHitBox::create(false, true);
  81. GUILayoutData backHitboxLayoutData = mBackHitBox->_getLayoutData();
  82. backHitboxLayoutData.setWidgetDepth(0);
  83. backHitboxLayoutData.setPanelDepth(std::numeric_limits<INT16>::max());
  84. mBackHitBox->_setLayoutData(backHitboxLayoutData);
  85. mBackHitBox->_changeParentWidget(this);
  86. mBackHitBox->_markContentAsDirty();
  87. mCaptureHitBox = GUIDropDownHitBox::create(true, false);
  88. mCaptureHitBox->setBounds(Rect2I(0, 0, target->getWidth(), target->getHeight()));
  89. GUILayoutData captureHitboxLayoutData = mCaptureHitBox->_getLayoutData();
  90. captureHitboxLayoutData.setWidgetDepth(0);
  91. captureHitboxLayoutData.setPanelDepth(std::numeric_limits<INT16>::max());
  92. mCaptureHitBox->_setLayoutData(captureHitboxLayoutData);
  93. mCaptureHitBox->_changeParentWidget(this);
  94. mCaptureHitBox->_markContentAsDirty();
  95. Rect2I availableBounds(target->getX(), target->getY(), target->getWidth(), target->getHeight());
  96. mRootMenu = bs_new<DropDownSubMenu>(this, nullptr, placement, availableBounds, dropDownData, type, 0);
  97. }
  98. GUIDropDownMenu::~GUIDropDownMenu()
  99. {
  100. }
  101. void GUIDropDownMenu::onDestroyed()
  102. {
  103. GUIElement::destroy(mFrontHitBox);
  104. GUIElement::destroy(mBackHitBox);
  105. GUIElement::destroy(mCaptureHitBox);
  106. bs_delete(mRootMenu);
  107. GUIWidget::onDestroyed();
  108. }
  109. void GUIDropDownMenu::dropDownFocusLost()
  110. {
  111. mRootMenu->closeSubMenu();
  112. GUIDropDownBoxManager::instance().closeDropDownBox();
  113. }
  114. void GUIDropDownMenu::notifySubMenuOpened(DropDownSubMenu* subMenu)
  115. {
  116. Vector<Rect2I> bounds;
  117. while(subMenu != nullptr)
  118. {
  119. bounds.push_back(subMenu->getVisibleBounds());
  120. subMenu = subMenu->mSubMenu;
  121. }
  122. mFrontHitBox->setBounds(bounds);
  123. mBackHitBox->setBounds(bounds);
  124. }
  125. void GUIDropDownMenu::notifySubMenuClosed(DropDownSubMenu* subMenu)
  126. {
  127. Vector<Rect2I> bounds;
  128. while(subMenu != nullptr)
  129. {
  130. bounds.push_back(subMenu->getVisibleBounds());
  131. subMenu = subMenu->mSubMenu;
  132. }
  133. mFrontHitBox->setBounds(bounds);
  134. mBackHitBox->setBounds(bounds);
  135. }
  136. GUIDropDownMenu::DropDownSubMenu::DropDownSubMenu(GUIDropDownMenu* owner, DropDownSubMenu* parent, const DropDownAreaPlacement& placement,
  137. const Rect2I& availableBounds, const GUIDropDownData& dropDownData, GUIDropDownType type, UINT32 depthOffset)
  138. :mOwner(owner), mParent(parent), mPage(0), mBackgroundFrame(nullptr), mBackgroundPanel(nullptr), mContentPanel(nullptr),
  139. mContentLayout(nullptr), mScrollUpBtn(nullptr), mScrollDownBtn(nullptr), x(0), y(0), width(0), height(0),
  140. mType(type), mSubMenu(nullptr), mData(dropDownData), mOpenedUpward(false), mDepthOffset(depthOffset), mContent(nullptr)
  141. {
  142. mAvailableBounds = availableBounds;
  143. const GUIElementStyle* scrollUpStyle = mOwner->getSkin().getStyle(mOwner->mScrollUpStyle);
  144. const GUIElementStyle* scrollDownStyle = mOwner->getSkin().getStyle(mOwner->mScrollDownStyle);
  145. const GUIElementStyle* backgroundStyle = mOwner->getSkin().getStyle(mOwner->mBackgroundStyle);
  146. // Create content GUI element
  147. mContent = GUIDropDownContent::create(this, dropDownData, mOwner->mContentStyle);
  148. mContent->setKeyboardFocus(true);
  149. // Content area
  150. mContentPanel = mOwner->getPanel()->addNewElement<GUIPanel>();
  151. mContentPanel->setWidth(width);
  152. mContentPanel->setHeight(height);
  153. mContentPanel->setDepthRange(100 - depthOffset * 2 - 1);
  154. // Background frame
  155. mBackgroundPanel = mOwner->getPanel()->addNewElement<GUIPanel>();
  156. mBackgroundPanel->setWidth(width);
  157. mBackgroundPanel->setHeight(height);
  158. mBackgroundPanel->setDepthRange(100 - depthOffset * 2);
  159. GUILayout* backgroundLayout = mBackgroundPanel->addNewElement<GUILayoutX>();
  160. mBackgroundFrame = GUITexture::create(GUIImageScaleMode::StretchToFit, mOwner->mBackgroundStyle);
  161. backgroundLayout->addElement(mBackgroundFrame);
  162. mContentLayout = mContentPanel->addNewElement<GUILayoutY>();
  163. mContentLayout->addElement(mContent); // Note: It's important this is added to the layout before we
  164. // use it for size calculations, in order for its skin to be assigned
  165. UINT32 helperElementHeight = scrollUpStyle->height + scrollDownStyle->height +
  166. backgroundStyle->margins.top + backgroundStyle->margins.bottom;
  167. UINT32 maxNeededHeight = helperElementHeight;
  168. UINT32 numElements = (UINT32)dropDownData.entries.size();
  169. for (UINT32 i = 0; i < numElements; i++)
  170. maxNeededHeight += mContent->getElementHeight(i);
  171. DropDownAreaPlacement::HorzDir horzDir;
  172. DropDownAreaPlacement::VertDir vertDir;
  173. Rect2I placementBounds = placement.getOptimalBounds(DROP_DOWN_BOX_WIDTH, maxNeededHeight, availableBounds, horzDir, vertDir);
  174. mOpenedUpward = vertDir == DropDownAreaPlacement::VertDir::Up;
  175. UINT32 actualY = placementBounds.y;
  176. if (mOpenedUpward)
  177. y = placementBounds.y + placementBounds.height;
  178. else
  179. y = placementBounds.y;
  180. x = placementBounds.x;
  181. width = placementBounds.width;
  182. height = placementBounds.height;
  183. mContentPanel->setPosition(x, actualY);
  184. mBackgroundPanel->setPosition(x, actualY);
  185. updateGUIElements();
  186. mOwner->notifySubMenuOpened(this);
  187. }
  188. GUIDropDownMenu::DropDownSubMenu::~DropDownSubMenu()
  189. {
  190. closeSubMenu();
  191. mOwner->notifySubMenuClosed(this);
  192. GUIElement::destroy(mContent);
  193. if (mScrollUpBtn != nullptr)
  194. GUIElement::destroy(mScrollUpBtn);
  195. if (mScrollDownBtn != nullptr)
  196. GUIElement::destroy(mScrollDownBtn);
  197. GUIElement::destroy(mBackgroundFrame);
  198. GUILayout::destroy(mBackgroundPanel);
  199. GUILayout::destroy(mContentPanel);
  200. }
  201. Vector<GUIDropDownMenu::DropDownSubMenu::PageInfo> GUIDropDownMenu::DropDownSubMenu::getPageInfos() const
  202. {
  203. const GUIElementStyle* scrollUpStyle = mOwner->getSkin().getStyle(mOwner->mScrollUpStyle);
  204. const GUIElementStyle* scrollDownStyle = mOwner->getSkin().getStyle(mOwner->mScrollDownStyle);
  205. const GUIElementStyle* backgroundStyle = mOwner->getSkin().getStyle(mOwner->mBackgroundStyle);
  206. INT32 numElements = (INT32)mData.entries.size();
  207. PageInfo curPageInfo;
  208. curPageInfo.start = 0;
  209. curPageInfo.end = 0;
  210. curPageInfo.idx = 0;
  211. curPageInfo.height = backgroundStyle->margins.top + backgroundStyle->margins.bottom;
  212. Vector<PageInfo> pageInfos;
  213. for (INT32 i = 0; i < numElements; i++)
  214. {
  215. curPageInfo.height += mContent->getElementHeight((UINT32)i);
  216. curPageInfo.end++;
  217. if (curPageInfo.height > height)
  218. {
  219. curPageInfo.height += scrollDownStyle->height;
  220. // Remove last few elements until we fit again
  221. while (curPageInfo.height > height && i >= 0)
  222. {
  223. curPageInfo.height -= mContent->getElementHeight((UINT32)i);
  224. curPageInfo.end--;
  225. i--;
  226. }
  227. // Nothing fits, break out of infinite loop
  228. if (curPageInfo.start >= curPageInfo.end)
  229. break;
  230. pageInfos.push_back(curPageInfo);
  231. curPageInfo.start = curPageInfo.end;
  232. curPageInfo.height = backgroundStyle->margins.top + backgroundStyle->margins.bottom;
  233. curPageInfo.height += scrollUpStyle->height;
  234. curPageInfo.idx++;
  235. }
  236. }
  237. if (curPageInfo.start < curPageInfo.end)
  238. pageInfos.push_back(curPageInfo);
  239. return pageInfos;
  240. }
  241. void GUIDropDownMenu::DropDownSubMenu::updateGUIElements()
  242. {
  243. // Remove all elements from content layout
  244. while(mContentLayout->getNumChildren() > 0)
  245. mContentLayout->removeElementAt(mContentLayout->getNumChildren() - 1);
  246. mContentLayout->addElement(mContent); // Note: Needs to be added first so that size calculations have proper skin to work with
  247. const GUIElementStyle* scrollUpStyle = mOwner->getSkin().getStyle(mOwner->mScrollUpStyle);
  248. const GUIElementStyle* scrollDownStyle = mOwner->getSkin().getStyle(mOwner->mScrollDownStyle);
  249. const GUIElementStyle* backgroundStyle = mOwner->getSkin().getStyle(mOwner->mBackgroundStyle);
  250. // Determine if we need scroll up and/or down buttons, number of visible elements and actual height
  251. bool needsScrollUp = mPage > 0;
  252. UINT32 pageStart = 0, pageEnd = 0;
  253. UINT32 pageHeight = 0;
  254. Vector<PageInfo> pageInfos = getPageInfos();
  255. if (pageInfos.size() > mPage)
  256. {
  257. pageStart = pageInfos[mPage].start;
  258. pageEnd = pageInfos[mPage].end;
  259. pageHeight = pageInfos[mPage].height;
  260. }
  261. UINT32 numElements = (UINT32)mData.entries.size();
  262. bool needsScrollDown = pageEnd != numElements;
  263. // Add scroll up button
  264. if(needsScrollUp)
  265. {
  266. if(mScrollUpBtn == nullptr)
  267. {
  268. mScrollUpBtn = GUIButton::create(GUIContent(HString(L""), mOwner->mScrollUpBtnArrow), mOwner->mScrollUpStyle);
  269. mScrollUpBtn->onClick.connect(std::bind(&DropDownSubMenu::scrollUp, this));
  270. }
  271. mContentLayout->insertElement(0, mScrollUpBtn);
  272. }
  273. else
  274. {
  275. if(mScrollUpBtn != nullptr)
  276. {
  277. GUIElement::destroy(mScrollUpBtn);
  278. mScrollUpBtn = nullptr;
  279. }
  280. }
  281. mContent->setRange(pageStart, pageEnd);
  282. // Add scroll down button
  283. if(needsScrollDown)
  284. {
  285. if(mScrollDownBtn == nullptr)
  286. {
  287. mScrollDownBtn = GUIButton::create(GUIContent(HString(L""), mOwner->mScrollDownBtnArrow), mOwner->mScrollDownStyle);
  288. mScrollDownBtn->onClick.connect(std::bind(&DropDownSubMenu::scrollDown, this));
  289. }
  290. mContentLayout->addElement(mScrollDownBtn);
  291. }
  292. else
  293. {
  294. if(mScrollDownBtn != nullptr)
  295. {
  296. GUIElement::destroy(mScrollDownBtn);
  297. mScrollDownBtn = nullptr;
  298. }
  299. }
  300. // Resize and reposition areas
  301. INT32 actualY = y;
  302. if(mOpenedUpward)
  303. actualY -= (INT32)pageHeight;
  304. mBackgroundPanel->setWidth(width);
  305. mBackgroundPanel->setHeight(pageHeight);
  306. mBackgroundPanel->setPosition(x, actualY);
  307. mVisibleBounds = Rect2I(x, actualY, width, pageHeight);
  308. UINT32 contentWidth = (UINT32)std::max(0, (INT32)width - (INT32)backgroundStyle->margins.left - (INT32)backgroundStyle->margins.right);
  309. UINT32 contentHeight = (UINT32)std::max(0, (INT32)pageHeight - (INT32)backgroundStyle->margins.top - (INT32)backgroundStyle->margins.bottom);
  310. mContentPanel->setWidth(contentWidth);
  311. mContentPanel->setHeight(contentHeight);
  312. mContentPanel->setPosition(x + backgroundStyle->margins.left, actualY + backgroundStyle->margins.top);
  313. }
  314. void GUIDropDownMenu::DropDownSubMenu::scrollDown()
  315. {
  316. mPage++;
  317. if (mPage == (UINT32)getPageInfos().size())
  318. mPage = 0;
  319. updateGUIElements();
  320. closeSubMenu();
  321. }
  322. void GUIDropDownMenu::DropDownSubMenu::scrollUp()
  323. {
  324. if (mPage > 0)
  325. mPage--;
  326. else
  327. mPage = (UINT32)getPageInfos().size() - 1;
  328. updateGUIElements();
  329. closeSubMenu();
  330. }
  331. void GUIDropDownMenu::DropDownSubMenu::scrollToTop()
  332. {
  333. mPage = 0;
  334. updateGUIElements();
  335. closeSubMenu();
  336. }
  337. void GUIDropDownMenu::DropDownSubMenu::scrollToBottom()
  338. {
  339. mPage = (UINT32)(getPageInfos().size() - 1);
  340. updateGUIElements();
  341. closeSubMenu();
  342. }
  343. void GUIDropDownMenu::DropDownSubMenu::closeSubMenu()
  344. {
  345. if(mSubMenu != nullptr)
  346. {
  347. bs_delete(mSubMenu);
  348. mSubMenu = nullptr;
  349. mContent->setKeyboardFocus(true);
  350. }
  351. }
  352. void GUIDropDownMenu::DropDownSubMenu::elementActivated(UINT32 idx, const Rect2I& bounds)
  353. {
  354. closeSubMenu();
  355. if (!mData.entries[idx].isSubMenu())
  356. {
  357. auto callback = mData.entries[idx].getCallback();
  358. if (callback != nullptr)
  359. callback();
  360. GUIDropDownBoxManager::instance().closeDropDownBox();
  361. }
  362. else
  363. {
  364. mContent->setKeyboardFocus(false);
  365. mSubMenu = bs_new<DropDownSubMenu>(mOwner, this, DropDownAreaPlacement::aroundBoundsVert(bounds),
  366. mAvailableBounds, mData.entries[idx].getSubMenuData(), mType, mDepthOffset + 1);
  367. }
  368. }
  369. void GUIDropDownMenu::DropDownSubMenu::close()
  370. {
  371. if (mParent != nullptr)
  372. mParent->closeSubMenu();
  373. else // We're the last sub-menu, close the whole thing
  374. GUIDropDownBoxManager::instance().closeDropDownBox();
  375. }
  376. void GUIDropDownMenu::DropDownSubMenu::elementSelected(UINT32 idx)
  377. {
  378. closeSubMenu();
  379. }
  380. }