BsGUIScrollArea.cpp 16 KB

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