BsGUIScrollArea.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519
  1. #include "BsGUIScrollArea.h"
  2. #include "BsGUIElementStyle.h"
  3. #include "BsGUISkin.h"
  4. #include "BsCGUIWidget.h"
  5. #include "BsGUIDimensions.h"
  6. #include "BsGUILayoutY.h"
  7. #include "BsGUISkin.h"
  8. #include "BsGUIScrollBarVert.h"
  9. #include "BsGUIScrollBarHorz.h"
  10. #include "BsGUIMouseEvent.h"
  11. #include "BsGUILayoutUtility.h"
  12. #include "BsGUISpace.h"
  13. #include "BsException.h"
  14. #include "BsProfilerCPU.h"
  15. using namespace std::placeholders;
  16. namespace BansheeEngine
  17. {
  18. const UINT32 GUIScrollArea::ScrollBarWidth = 16;
  19. const UINT32 GUIScrollArea::MinHandleSize = 4;
  20. const UINT32 GUIScrollArea::WheelScrollAmount = 50;
  21. GUIScrollArea::GUIScrollArea(ScrollBarType vertBarType, ScrollBarType horzBarType,
  22. const String& scrollBarStyle, const String& scrollAreaStyle, const GUIDimensions& dimensions)
  23. :GUIElementContainer(dimensions), mVertScroll(nullptr), mHorzScroll(nullptr), mVertOffset(0), mHorzOffset(0),
  24. mVertBarType(vertBarType), mHorzBarType(horzBarType), mScrollBarStyle(scrollBarStyle)
  25. {
  26. mContentLayout = GUILayoutY::create();
  27. _registerChildElement(mContentLayout);
  28. mHorzScroll = GUIScrollBarHorz::create(mScrollBarStyle);
  29. mVertScroll = GUIScrollBarVert::create(mScrollBarStyle);
  30. _registerChildElement(mHorzScroll);
  31. _registerChildElement(mVertScroll);
  32. mHorzScroll->onScrollPositionChanged.connect(std::bind(&GUIScrollArea::horzScrollUpdate, this, _1));
  33. mVertScroll->onScrollPositionChanged.connect(std::bind(&GUIScrollArea::vertScrollUpdate, this, _1));
  34. }
  35. GUIScrollArea::~GUIScrollArea()
  36. {
  37. }
  38. void GUIScrollArea::updateClippedBounds()
  39. {
  40. mClippedBounds = mLayoutData.area;
  41. mClippedBounds.clip(mLayoutData.clipRect);
  42. }
  43. Vector2I GUIScrollArea::_getOptimalSize() const
  44. {
  45. Vector2I optimalSize = mContentLayout->_getOptimalSize();
  46. // Provide 10x10 in case underlying layout is empty because
  47. // 0 doesn't work well with the layout system
  48. optimalSize.x = std::max(10, optimalSize.x);
  49. optimalSize.y = std::max(10, optimalSize.y);
  50. return optimalSize;
  51. }
  52. LayoutSizeRange GUIScrollArea::_calculateLayoutSizeRange() const
  53. {
  54. // I'm ignoring scroll bars here since if the content layout fits
  55. // then they're not needed and the range is valid. And if it doesn't
  56. // fit the area will get clipped anyway and including the scroll bars
  57. // won't change the size much, but it would complicate this method significantly.
  58. return mDimensions.calculateSizeRange(_getOptimalSize());
  59. }
  60. LayoutSizeRange GUIScrollArea::_getLayoutSizeRange() const
  61. {
  62. return mSizeRange;
  63. }
  64. void GUIScrollArea::_updateOptimalLayoutSizes()
  65. {
  66. // Update all children first, otherwise we can't determine our own optimal size
  67. GUIElementBase::_updateOptimalLayoutSizes();
  68. if (mChildren.size() != mChildSizeRanges.size())
  69. mChildSizeRanges.resize(mChildren.size());
  70. UINT32 childIdx = 0;
  71. for (auto& child : mChildren)
  72. {
  73. mChildSizeRanges[childIdx] = child->_getLayoutSizeRange();
  74. childIdx++;
  75. }
  76. mSizeRange = mDimensions.calculateSizeRange(_getOptimalSize());
  77. }
  78. void GUIScrollArea::_getElementAreas(const Rect2I& layoutArea, Rect2I* elementAreas, UINT32 numElements,
  79. const Vector<LayoutSizeRange>& sizeRanges, const LayoutSizeRange& mySizeRange) const
  80. {
  81. Vector2I visibleSize, contentSize;
  82. _getElementAreas(layoutArea, elementAreas, numElements, sizeRanges, visibleSize, contentSize);
  83. }
  84. void GUIScrollArea::_getElementAreas(const Rect2I& layoutArea, Rect2I* elementAreas, UINT32 numElements,
  85. const Vector<LayoutSizeRange>& sizeRanges, Vector2I& visibleSize, Vector2I& contentSize) const
  86. {
  87. assert(mChildren.size() == numElements && numElements == 3);
  88. UINT32 layoutIdx = 0;
  89. UINT32 horzScrollIdx = 0;
  90. UINT32 vertScrollIdx = 0;
  91. UINT32 idx = 0;
  92. for (auto& child : mChildren)
  93. {
  94. if (child == mContentLayout)
  95. layoutIdx = idx;
  96. if (child == mHorzScroll)
  97. horzScrollIdx = idx;
  98. if (child == mVertScroll)
  99. vertScrollIdx = idx;
  100. idx++;
  101. }
  102. // Calculate content layout bounds
  103. //// We want elements to use their optimal height, since scroll area
  104. //// technically provides "infinite" space
  105. UINT32 optimalContentWidth = layoutArea.width;
  106. if (mHorzBarType != ScrollBarType::NeverShow)
  107. optimalContentWidth = sizeRanges[layoutIdx].optimal.x;
  108. UINT32 optimalContentHeight = layoutArea.height;
  109. if (mVertBarType != ScrollBarType::NeverShow)
  110. optimalContentHeight = sizeRanges[layoutIdx].optimal.y;
  111. UINT32 layoutWidth = std::max(optimalContentWidth, (UINT32)layoutArea.width);
  112. UINT32 layoutHeight = std::max(optimalContentHeight, (UINT32)layoutArea.height);
  113. contentSize = GUILayoutUtility::calcActualSize(layoutWidth, layoutHeight, mContentLayout, false);
  114. visibleSize = Vector2I(layoutArea.width, layoutArea.height);
  115. bool addHorzScrollbar = (mHorzBarType == ScrollBarType::ShowIfDoesntFit && contentSize.x > visibleSize.x) ||
  116. mHorzBarType == ScrollBarType::AlwaysShow && mHorzBarType != ScrollBarType::NeverShow;
  117. bool hasHorzScrollbar = false;
  118. bool hasVertScrollbar = false;
  119. if (addHorzScrollbar)
  120. {
  121. // Make room for scrollbar
  122. visibleSize.y = (UINT32)std::max(0, (INT32)layoutArea.height - (INT32)ScrollBarWidth);
  123. optimalContentHeight = (UINT32)std::max(0, (INT32)optimalContentHeight - (INT32)ScrollBarWidth);
  124. if (sizeRanges[layoutIdx].min.y > 0)
  125. optimalContentHeight = std::max((UINT32)sizeRanges[layoutIdx].min.y, optimalContentHeight);
  126. layoutHeight = std::max(optimalContentHeight, (UINT32)visibleSize.y); // Never go below optimal size
  127. contentSize = GUILayoutUtility::calcActualSize(layoutWidth, layoutHeight, mContentLayout, true);
  128. hasHorzScrollbar = true;
  129. }
  130. bool addVertScrollbar = (mVertBarType == ScrollBarType::ShowIfDoesntFit && contentSize.y > visibleSize.y) ||
  131. mVertBarType == ScrollBarType::AlwaysShow && mVertBarType != ScrollBarType::NeverShow;
  132. if (addVertScrollbar)
  133. {
  134. // Make room for scrollbar
  135. visibleSize.x = (UINT32)std::max(0, (INT32)layoutArea.width - (INT32)ScrollBarWidth);
  136. optimalContentWidth = (UINT32)std::max(0, (INT32)optimalContentWidth - (INT32)ScrollBarWidth);
  137. if (sizeRanges[layoutIdx].min.x > 0)
  138. optimalContentWidth = std::max((UINT32)sizeRanges[layoutIdx].min.x, optimalContentWidth);
  139. layoutWidth = std::max(optimalContentWidth, (UINT32)visibleSize.x); // Never go below optimal size
  140. contentSize = GUILayoutUtility::calcActualSize(layoutWidth, layoutHeight, mContentLayout, true);
  141. hasVertScrollbar = true;
  142. if (!hasHorzScrollbar) // Since width has been reduced, we need to check if we require the horizontal scrollbar
  143. {
  144. addHorzScrollbar = (mHorzBarType == ScrollBarType::ShowIfDoesntFit && contentSize.x > visibleSize.x) && mHorzBarType != ScrollBarType::NeverShow;
  145. if (addHorzScrollbar)
  146. {
  147. // Make room for scrollbar
  148. visibleSize.y = (UINT32)std::max(0, (INT32)layoutArea.height - (INT32)ScrollBarWidth);
  149. optimalContentHeight = (UINT32)std::max(0, (INT32)optimalContentHeight - (INT32)ScrollBarWidth);
  150. if (sizeRanges[layoutIdx].min.y > 0)
  151. optimalContentHeight = std::max((UINT32)sizeRanges[layoutIdx].min.y, optimalContentHeight);
  152. layoutHeight = std::max(optimalContentHeight, (UINT32)visibleSize.y); // Never go below optimal size
  153. contentSize = GUILayoutUtility::calcActualSize(layoutWidth, layoutHeight, mContentLayout, true);
  154. hasHorzScrollbar = true;
  155. }
  156. }
  157. }
  158. elementAreas[layoutIdx] = Rect2I(layoutArea.x - Math::floorToInt(mHorzOffset), layoutArea.y - Math::floorToInt(mVertOffset), layoutWidth, layoutHeight);
  159. // Calculate vertical scrollbar bounds
  160. if (hasVertScrollbar)
  161. {
  162. INT32 scrollBarOffset = (UINT32)std::max(0, (INT32)layoutArea.width - (INT32)ScrollBarWidth);
  163. UINT32 scrollBarHeight = layoutArea.height;
  164. if (hasHorzScrollbar)
  165. scrollBarHeight = (UINT32)std::max(0, (INT32)scrollBarHeight - (INT32)ScrollBarWidth);
  166. elementAreas[vertScrollIdx] = Rect2I(layoutArea.x + scrollBarOffset, layoutArea.y, ScrollBarWidth, scrollBarHeight);
  167. }
  168. else
  169. {
  170. elementAreas[vertScrollIdx] = Rect2I(layoutArea.x + layoutWidth, layoutArea.y, 0, 0);
  171. }
  172. // Calculate horizontal scrollbar bounds
  173. if (hasHorzScrollbar)
  174. {
  175. INT32 scrollBarOffset = (UINT32)std::max(0, (INT32)layoutArea.height - (INT32)ScrollBarWidth);
  176. UINT32 scrollBarWidth = layoutArea.width;
  177. if (hasVertScrollbar)
  178. scrollBarWidth = (UINT32)std::max(0, (INT32)scrollBarWidth - (INT32)ScrollBarWidth);
  179. elementAreas[horzScrollIdx] = Rect2I(layoutArea.x, layoutArea.y + scrollBarOffset, scrollBarWidth, ScrollBarWidth);
  180. }
  181. else
  182. {
  183. elementAreas[horzScrollIdx] = Rect2I(layoutArea.x, layoutArea.y + layoutHeight, 0, 0);
  184. }
  185. }
  186. void GUIScrollArea::_updateLayoutInternal(const GUILayoutData& data)
  187. {
  188. UINT32 numElements = (UINT32)mChildren.size();
  189. Rect2I* elementAreas = nullptr;
  190. if (numElements > 0)
  191. elementAreas = bs_stack_new<Rect2I>(numElements);
  192. UINT32 layoutIdx = 0;
  193. UINT32 horzScrollIdx = 0;
  194. UINT32 vertScrollIdx = 0;
  195. for (UINT32 i = 0; i < numElements; i++)
  196. {
  197. GUIElementBase* child = _getChild(i);
  198. if (child == mContentLayout)
  199. layoutIdx = i;
  200. if (child == mHorzScroll)
  201. horzScrollIdx = i;
  202. if (child == mVertScroll)
  203. vertScrollIdx = i;
  204. }
  205. _getElementAreas(data.area, elementAreas, numElements, mChildSizeRanges, mVisibleSize, mContentSize);
  206. Rect2I& layoutBounds = elementAreas[layoutIdx];
  207. Rect2I& horzScrollBounds = elementAreas[horzScrollIdx];
  208. Rect2I& vertScrollBounds = elementAreas[vertScrollIdx];
  209. // Layout
  210. Rect2I layoutClipRect = data.clipRect;
  211. layoutClipRect.width = (UINT32)mVisibleSize.x;
  212. layoutClipRect.height = (UINT32)mVisibleSize.y;
  213. GUILayoutData layoutData = data;
  214. layoutData.area = layoutBounds;
  215. layoutData.clipRect = layoutClipRect;
  216. mContentLayout->_setLayoutData(layoutData);
  217. mContentLayout->_updateLayoutInternal(layoutData);
  218. // Vertical scrollbar
  219. {
  220. GUILayoutData vertScrollData = data;
  221. vertScrollData.area = vertScrollBounds;
  222. vertScrollData.clipRect = vertScrollBounds;
  223. vertScrollData.clipRect.clip(data.clipRect);
  224. mVertScroll->_setLayoutData(vertScrollData);
  225. mVertScroll->_updateLayoutInternal(vertScrollData);
  226. // Set new handle size and update position to match the new size
  227. UINT32 newHandleSize = (UINT32)Math::floorToInt(mVertScroll->getMaxHandleSize() * (vertScrollBounds.height / (float)mContentSize.y));
  228. newHandleSize = std::max(newHandleSize, MinHandleSize);
  229. UINT32 scrollableHeight = (UINT32)std::max(0, INT32(mContentSize.y) - INT32(vertScrollBounds.height));
  230. float newScrollPct = 0.0f;
  231. if (scrollableHeight > 0)
  232. newScrollPct = mVertOffset / scrollableHeight;
  233. mVertScroll->_setHandleSize(newHandleSize);
  234. mVertScroll->_setScrollPos(newScrollPct);
  235. }
  236. // Horizontal scrollbar
  237. {
  238. GUILayoutData horzScrollData = data;
  239. horzScrollData.area = horzScrollBounds;
  240. horzScrollData.clipRect = horzScrollBounds;
  241. horzScrollData.clipRect.clip(data.clipRect);
  242. mHorzScroll->_setLayoutData(horzScrollData);
  243. mHorzScroll->_updateLayoutInternal(horzScrollData);
  244. // Set new handle size and update position to match the new size
  245. UINT32 newHandleSize = (UINT32)Math::floorToInt(mHorzScroll->getMaxHandleSize() * (horzScrollBounds.width / (float)mContentSize.x));
  246. newHandleSize = std::max(newHandleSize, MinHandleSize);
  247. UINT32 scrollableWidth = (UINT32)std::max(0, INT32(mContentSize.x) - INT32(horzScrollBounds.width));
  248. float newScrollPct = 0.0f;
  249. if (scrollableWidth > 0)
  250. newScrollPct = mHorzOffset / scrollableWidth;
  251. mHorzScroll->_setHandleSize(newHandleSize);
  252. mHorzScroll->_setScrollPos(newScrollPct);
  253. }
  254. if (elementAreas != nullptr)
  255. bs_stack_free(elementAreas);
  256. }
  257. void GUIScrollArea::vertScrollUpdate(float scrollPos)
  258. {
  259. scrollToVertical(scrollPos);
  260. }
  261. void GUIScrollArea::horzScrollUpdate(float scrollPos)
  262. {
  263. scrollToHorizontal(scrollPos);
  264. }
  265. void GUIScrollArea::scrollToVertical(float pct)
  266. {
  267. UINT32 scrollableHeight = (UINT32)std::max(0, INT32(mContentSize.y) - INT32(mVisibleSize.y));
  268. mVertOffset = scrollableHeight * Math::clamp01(pct);
  269. _markLayoutAsDirty();
  270. }
  271. void GUIScrollArea::scrollToHorizontal(float pct)
  272. {
  273. UINT32 scrollableWidth = (UINT32)std::max(0, INT32(mContentSize.x) - INT32(mVisibleSize.x));
  274. mHorzOffset = scrollableWidth * Math::clamp01(pct);
  275. _markLayoutAsDirty();
  276. }
  277. float GUIScrollArea::getVerticalScroll() const
  278. {
  279. if (mVertScroll != nullptr)
  280. return mVertScroll->getScrollPos();
  281. return 0.0f;
  282. }
  283. float GUIScrollArea::getHorizontalScroll() const
  284. {
  285. if (mHorzScroll != nullptr)
  286. return mHorzScroll->getScrollPos();
  287. return 0.0f;
  288. }
  289. Rect2I GUIScrollArea::getContentBounds()
  290. {
  291. Rect2I bounds = getBounds();
  292. if (mHorzScroll)
  293. bounds.height -= ScrollBarWidth;
  294. if (mVertScroll)
  295. bounds.width -= ScrollBarWidth;
  296. return bounds;
  297. }
  298. void GUIScrollArea::scrollUpPx(UINT32 pixels)
  299. {
  300. if(mVertScroll != nullptr)
  301. {
  302. UINT32 scrollableSize = (UINT32)std::max(0, INT32(mContentSize.y) - INT32(mVisibleSize.y));
  303. float offset = 0.0f;
  304. if(scrollableSize > 0)
  305. offset = pixels / (float)scrollableSize;
  306. mVertScroll->scroll(offset);
  307. }
  308. }
  309. void GUIScrollArea::scrollDownPx(UINT32 pixels)
  310. {
  311. if(mVertScroll != nullptr)
  312. {
  313. UINT32 scrollableSize = (UINT32)std::max(0, INT32(mContentSize.y) - INT32(mVisibleSize.y));
  314. float offset = 0.0f;
  315. if(scrollableSize > 0)
  316. offset = pixels / (float)scrollableSize;
  317. mVertScroll->scroll(-offset);
  318. }
  319. }
  320. void GUIScrollArea::scrollLeftPx(UINT32 pixels)
  321. {
  322. if(mHorzScroll != nullptr)
  323. {
  324. UINT32 scrollableSize = (UINT32)std::max(0, INT32(mContentSize.x) - INT32(mVisibleSize.x));
  325. float offset = 0.0f;
  326. if(scrollableSize > 0)
  327. offset = pixels / (float)scrollableSize;
  328. mHorzScroll->scroll(offset);
  329. }
  330. }
  331. void GUIScrollArea::scrollRightPx(UINT32 pixels)
  332. {
  333. if(mHorzScroll != nullptr)
  334. {
  335. UINT32 scrollableSize = (UINT32)std::max(0, INT32(mContentSize.x) - INT32(mVisibleSize.x));
  336. float offset = 0.0f;
  337. if(scrollableSize > 0)
  338. offset = pixels / (float)scrollableSize;
  339. mHorzScroll->scroll(-offset);
  340. }
  341. }
  342. void GUIScrollArea::scrollUpPct(float percent)
  343. {
  344. if(mVertScroll != nullptr)
  345. mVertScroll->scroll(percent);
  346. }
  347. void GUIScrollArea::scrollDownPct(float percent)
  348. {
  349. if(mVertScroll != nullptr)
  350. mVertScroll->scroll(-percent);
  351. }
  352. void GUIScrollArea::scrollLeftPct(float percent)
  353. {
  354. if(mHorzScroll != nullptr)
  355. mHorzScroll->scroll(percent);
  356. }
  357. void GUIScrollArea::scrollRightPct(float percent)
  358. {
  359. if(mHorzScroll != nullptr)
  360. mHorzScroll->scroll(-percent);
  361. }
  362. bool GUIScrollArea::_mouseEvent(const GUIMouseEvent& ev)
  363. {
  364. if(ev.getType() == GUIMouseEventType::MouseWheelScroll)
  365. {
  366. // Mouse wheel only scrolls on the Y axis
  367. if(mVertScroll != nullptr)
  368. {
  369. UINT32 scrollableHeight = (UINT32)std::max(0, INT32(mContentSize.y) - INT32(mVisibleSize.y));
  370. float additionalScroll = (float)WheelScrollAmount / scrollableHeight;
  371. mVertScroll->scroll(additionalScroll * ev.getWheelScrollAmount());
  372. return true;
  373. }
  374. }
  375. return false;
  376. }
  377. GUIScrollArea* GUIScrollArea::create(ScrollBarType vertBarType, ScrollBarType horzBarType,
  378. const String& scrollBarStyle, const String& scrollAreaStyle)
  379. {
  380. return new (bs_alloc<GUIScrollArea>()) GUIScrollArea(vertBarType, horzBarType, scrollBarStyle,
  381. getStyleName<GUIScrollArea>(scrollAreaStyle), GUIDimensions::create());
  382. }
  383. GUIScrollArea* GUIScrollArea::create(const GUIOptions& options, const String& scrollBarStyle,
  384. const String& scrollAreaStyle)
  385. {
  386. return new (bs_alloc<GUIScrollArea>()) GUIScrollArea(ScrollBarType::ShowIfDoesntFit,
  387. ScrollBarType::ShowIfDoesntFit, scrollBarStyle, getStyleName<GUIScrollArea>(scrollAreaStyle), GUIDimensions::create(options));
  388. }
  389. GUIScrollArea* GUIScrollArea::create(const String& scrollBarStyle, const String& scrollAreaStyle)
  390. {
  391. return new (bs_alloc<GUIScrollArea>()) GUIScrollArea(ScrollBarType::ShowIfDoesntFit, ScrollBarType::ShowIfDoesntFit, scrollBarStyle,
  392. getStyleName<GUIScrollArea>(scrollAreaStyle), GUIDimensions::create());
  393. }
  394. GUIScrollArea* GUIScrollArea::create(ScrollBarType vertBarType,
  395. ScrollBarType horzBarType, const GUIOptions& options, const String& scrollBarStyle,
  396. const String& scrollAreaStyle)
  397. {
  398. return new (bs_alloc<GUIScrollArea>()) GUIScrollArea(vertBarType, horzBarType, scrollBarStyle,
  399. getStyleName<GUIScrollArea>(scrollAreaStyle), GUIDimensions::create(options));
  400. }
  401. const String& GUIScrollArea::getGUITypeName()
  402. {
  403. static String typeName = "ScrollArea";
  404. return typeName;
  405. }
  406. }