BsGUIScrollArea.cpp 16 KB

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