3
0

UiDynamicScrollBoxComponent.h 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #pragma once
  9. #include <LyShine/Bus/UiDynamicScrollBoxBus.h>
  10. #include <LyShine/Bus/UiScrollBoxBus.h>
  11. #include <LyShine/Bus/UiInitializationBus.h>
  12. #include <LyShine/Bus/UiTransformBus.h>
  13. #include <LyShine/Bus/UiElementBus.h>
  14. #include <LyShine/UiComponentTypes.h>
  15. #include <AzCore/Component/Component.h>
  16. #include <AzCore/Math/Vector2.h>
  17. ////////////////////////////////////////////////////////////////////////////////////////////////////
  18. //! This component dynamically sets up scrollbox content as a horizontal or vertical list of
  19. //! elements that are cloned from prototype entities. Only the minimum number of entities are
  20. //! created for efficient scrolling, and are reused when new elements come into view.
  21. //! The list can consist of only items, or it can be divided into sections that include a header at
  22. //! the beginning of each section, followed by items that belong to that section.
  23. //! The meaning of "element" differs in the public and private interface, mainly for backward
  24. //! compatibility. In the private interface, "element" refers to a generic entry in the list which
  25. //! can be of different types. Currently there are two types of elements: headers and items.
  26. //! In the public interface however, "element" means the same thing as "item" does in the private
  27. //! interface, and "item" is unused (the public interface does not need to define the concept of a
  28. //! generic entry in the list.)
  29. //! Both headers and items can have fixed sizes determined by their corresponding prototype entities,
  30. //! or they could vary in size. If they vary in size, another option is available to indicate whether
  31. //! to auto calculate the sizes or request the sizes via a bus interface. There is also the option to
  32. //! provide an estimated size that will be used until the elements scroll into view and their real
  33. //! size calculated.
  34. //! For lists with a large number of elements, it is advisable to use the estimated size as
  35. //! calculating the sizes of all elements up front could be costly. When elements vary in size,
  36. //! a cache is maintained and the element sizes are only calculated once.
  37. class UiDynamicScrollBoxComponent
  38. : public AZ::Component
  39. , public UiDynamicScrollBoxBus::Handler
  40. , public UiScrollBoxNotificationBus::Handler
  41. , public UiInitializationBus::Handler
  42. , public UiTransformChangeNotificationBus::Handler
  43. , public UiElementNotificationBus::Handler
  44. {
  45. public: // member functions
  46. AZ_COMPONENT(UiDynamicScrollBoxComponent, LyShine::UiDynamicScrollBoxComponentUuid, AZ::Component);
  47. UiDynamicScrollBoxComponent();
  48. ~UiDynamicScrollBoxComponent() override;
  49. // UiDynamicScrollBoxInterface
  50. void RefreshContent() override;
  51. void AddElementsToEnd(int numElementsToAdd, bool scrollToEndIfWasAtEnd) override;
  52. void RemoveElementsFromFront(int numElementsToRemove) override;
  53. void ScrollToEnd() override;
  54. int GetElementIndexOfChild(AZ::EntityId childElement) override;
  55. int GetSectionIndexOfChild(AZ::EntityId childElement) override;
  56. AZ::EntityId GetChildAtElementIndex(int index) override;
  57. AZ::EntityId GetChildAtSectionAndElementIndex(int sectionIndex, int index) override;
  58. bool GetAutoRefreshOnPostActivate() override;
  59. void SetAutoRefreshOnPostActivate(bool autoRefresh) override;
  60. AZ::EntityId GetPrototypeElement() override;
  61. void SetPrototypeElement(AZ::EntityId prototypeElement) override;
  62. bool GetElementsVaryInSize() override;
  63. void SetElementsVaryInSize(bool varyInSize) override;
  64. bool GetAutoCalculateVariableElementSize() override;
  65. void SetAutoCalculateVariableElementSize(bool autoCalculateSize) override;
  66. float GetEstimatedVariableElementSize() override;
  67. void SetEstimatedVariableElementSize(float estimatedSize) override;
  68. bool GetSectionsEnabled() override;
  69. void SetSectionsEnabled(bool enabled) override;
  70. AZ::EntityId GetPrototypeHeader() override;
  71. void SetPrototypeHeader(AZ::EntityId prototypeHeader) override;
  72. bool GetHeadersSticky() override;
  73. void SetHeadersSticky(bool stickyHeaders) override;
  74. bool GetHeadersVaryInSize() override;
  75. void SetHeadersVaryInSize(bool varyInSize) override;
  76. bool GetAutoCalculateVariableHeaderSize() override;
  77. void SetAutoCalculateVariableHeaderSize(bool autoCalculateSize) override;
  78. float GetEstimatedVariableHeaderSize() override;
  79. void SetEstimatedVariableHeaderSize(float estimatedSize) override;
  80. // ~UiDynamicScrollBoxInterface
  81. // UiScrollBoxNotifications
  82. void OnScrollOffsetChanging(AZ::Vector2 newScrollOffset) override;
  83. void OnScrollOffsetChanged(AZ::Vector2 newScrollOffset) override;
  84. // ~UiScrollBoxNotifications
  85. // UiInitializationInterface
  86. void InGamePostActivate() override;
  87. // ~UiInitializationInterface
  88. // UiTransformChangeNotification
  89. void OnCanvasSpaceRectChanged(AZ::EntityId entityId, const UiTransformInterface::Rect& oldRect, const UiTransformInterface::Rect& newRect) override;
  90. // ~UiTransformChangeNotification
  91. // UiElementNotifications
  92. void OnUiElementBeingDestroyed() override;
  93. // ~UiElementNotifications
  94. public: // static member functions
  95. static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
  96. {
  97. provided.push_back(AZ_CRC("UiDynamicScrollBoxService", 0x11112f1a));
  98. provided.push_back(AZ_CRC("UiDynamicContentService", 0xc5af0b83));
  99. }
  100. static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
  101. {
  102. incompatible.push_back(AZ_CRC("UiDynamicContentService", 0xc5af0b83));
  103. }
  104. static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
  105. {
  106. required.push_back(AZ_CRC("UiElementService", 0x3dca7ad4));
  107. required.push_back(AZ_CRC("UiTransformService", 0x3a838e34));
  108. required.push_back(AZ_CRC("UiScrollBoxService", 0xfdafc904));
  109. }
  110. static void Reflect(AZ::ReflectContext* context);
  111. protected: //types
  112. // The types of elements that the list may contain
  113. enum ElementType
  114. {
  115. SectionHeader, // element that appears at the start of each section
  116. Item, // all other elements
  117. NumElementTypes
  118. };
  119. // The list can be divided into sections that have a header followed by a list of items
  120. struct Section
  121. {
  122. int m_index; // the section index
  123. int m_numItems; // the number of items in this section
  124. int m_headerElementIndex; // the element index of the section header
  125. };
  126. // Used when the list is divided into sections
  127. struct ElementIndexInfo
  128. {
  129. int m_sectionIndex; // the section index that the element belongs to. -1 if the list is not divided into sections
  130. int m_itemIndexInSection; // the index of the item relative to the section. -1 if the element is a section header
  131. };
  132. // An element that is currently being displayed
  133. struct DisplayedElement
  134. {
  135. AZ::EntityId m_element;
  136. int m_elementIndex; // the absolute index of the element in the list
  137. ElementIndexInfo m_indexInfo; // the index info for the element including section index and item index relative to the section
  138. ElementType m_type;
  139. };
  140. // Used when elements vary in size
  141. struct CachedElementInfo
  142. {
  143. CachedElementInfo();
  144. float m_size;
  145. float m_accumulatedSize;
  146. };
  147. protected: // member functions
  148. // AZ::Component
  149. void Activate() override;
  150. void Deactivate() override;
  151. // ~AZ::Component
  152. AZ_DISABLE_COPY_MOVE(UiDynamicScrollBoxComponent);
  153. // Initialization
  154. void PrepareListForDisplay();
  155. // Get the content entity of the scrollbox
  156. AZ::Entity* GetContentEntity() const;
  157. // Clone a prototype element. The parent defaults to the content entity
  158. AZ::EntityId ClonePrototypeElement(ElementType elementType, AZ::EntityId parentEntityId = AZ::EntityId()) const;
  159. // Check if the specified entity is a prototype element
  160. bool IsPrototypeElement(AZ::EntityId entityId) const;
  161. // Check if all needed prototype elements are valid
  162. bool AllPrototypeElementsValid() const;
  163. // Return whether any of the prototype elements are navigable
  164. bool AnyPrototypeElementsNavigable() const;
  165. // Return whether any element types vary in size
  166. bool AnyElementTypesHaveVariableSize() const;
  167. // Return whether any element types have an estimated size that's greater than 0
  168. bool AnyElementTypesHaveEstimatedSizes() const;
  169. // Return whether all element types have an estimated size that's greater than 0
  170. bool AllElementTypesHaveEstimatedSizes() const;
  171. // Return whether headers should stick to the top or left of the visible area
  172. bool StickyHeadersEnabled() const;
  173. // Resize the content entity to fit all the elements
  174. void ResizeContentToFitElements();
  175. // Resize the content entity to the specified height or width
  176. void ResizeContentElement(float newSize) const;
  177. // Adjust the size of the content entity and the scroll offset by the specified amount.
  178. // This is used after adding, removing or resizing elements
  179. void AdjustContentSizeAndScrollOffsetByDelta(float sizeDelta, float scrollDelta) const;
  180. // Calculate and cache the size of the element at the specified index
  181. float CalculateVariableElementSize(int index);
  182. // Get the size of the element at the specified index.
  183. // If the size is in the cache, return that size.
  184. // Otherwise, if there is an estimated size, return the estimated size.
  185. // Otherwise, calculate and cache the size
  186. float GetAndCacheVariableElementSize(int index);
  187. // Get the current size of the element at the specified index.
  188. // The returned size matches the size used to determine the content size.
  189. // Should only be called when the size is already cached or the element has an estimated size
  190. float GetVariableElementSize(int index) const;
  191. // Get the last element index, before the specified element index, with a known accumulated size
  192. int GetLastKnownAccumulatedSizeIndex(int index, int numElementsWithUnknownSizeOut[ElementType::NumElementTypes]) const;
  193. // Get the offset of the element at the specified index
  194. float GetElementOffsetAtIndex(int index) const;
  195. // Get the offset of the element at the specified index when all element types have a fixed size
  196. float GetFixedSizeElementOffset(int index) const;
  197. // Get the offset of the element at the specified index when some element types have variable size
  198. float GetVariableSizeElementOffset(int index) const;
  199. // Calculate the average element size
  200. void UpdateAverageElementSize(int numAddedElements, float sizeDelta);
  201. // Mark all elements as not displayed
  202. void ClearDisplayedElements();
  203. // Find the displayed element with a matching index
  204. AZ::EntityId FindDisplayedElementWithIndex(int index) const;
  205. // Get the size of the content's parent (visible area)
  206. float GetVisibleAreaSize() const;
  207. // Check if any elements are visible and set the visible content bounds if they are
  208. bool AreAnyElementsVisible(AZ::Vector2& visibleContentBoundsOut) const;
  209. // Update which elements are visible, and set up for display
  210. void UpdateElementVisibility(bool keepAtEndIfWasAtEnd = false);
  211. // Calculate the first and last visible element indices. Elements that have come into view will
  212. // get their real size calculated if only their estimated size was known.
  213. // Returns the total change in element size, and the scroll delta needed to keep the previously
  214. // visible elements at the same position after the content size is changed to accomodate the new
  215. // element sizes
  216. void CalculateVisibleElementIndices(bool keepAtEndIfWasAtEnd,
  217. const AZ::Vector2& visibleContentBounds,
  218. int& firstVisibleElementIndexOut,
  219. int& lastVisibleElementIndexOut,
  220. int& firstDisplayedElementIndexOut,
  221. int& lastDisplayedElementIndexOut,
  222. int& firstDisplayedElementIndexWithSizeChangeOut,
  223. float& totalElementSizeChangeOut,
  224. float& scrollChangeOut);
  225. // Update the header that is currently sticky
  226. void UpdateStickyHeader(int firstVisibleElementIndex, int lastVisibleElementIndex, float visibleContentBeginning);
  227. // Find the first visible header element index excluding the specified header element index
  228. int FindFirstVisibleHeaderIndex(int firstVisibleElementIndex, int lastVisibleElementIndex, int excludeIndex);
  229. // Find the first and last visible element indices when all element types have a fixed size
  230. void FindVisibleElementIndicesForFixedSizes(const AZ::Vector2& visibleContentBounds,
  231. int& firstVisibleElementIndexOut,
  232. int& lastVisibleElementIndexOut) const;
  233. // Find the visible element index that should remain in the same position after calculating
  234. // visible element indices for lists that may not know the sizes of all their elements
  235. int FindVisibleElementIndexToRemainInPlace(const AZ::Vector2& visibleContentBounds) const;
  236. // Add extra elements to the beginning and end for keyboard/gamepad navigation
  237. void AddExtraElementsForNavigation(int& firstDisplayedElementIndexOut, int& lastDisplayedElementIndexOut) const;
  238. // Estimate the index of the first visible element. This is used when elements can vary in size
  239. int EstimateFirstVisibleElementIndex(const AZ::Vector2& visibleContentBounds) const;
  240. // Find the real first visible element index based on an estimated first visible element index.
  241. // Also returns the bottom or right of the first visible element
  242. int FindFirstVisibleElementIndex(int estimatedIndex, const AZ::Vector2& visibleContentBounds, float& firstVisibleElementEndOut) const;
  243. // Calculate the visible space available before and after the specified visible element index
  244. void CalculateVisibleSpaceBeforeAndAfterElement(int visibleElementIndex,
  245. bool keepAtEnd,
  246. float visibleAreaBeginning,
  247. float& spaceLeftBeforeOut,
  248. float& spaceLeftAfterOut) const;
  249. // Calculate the first and last visible element indices based on a known visible element index.
  250. // Elements that have come into view will get their real size calculated if only their estimated
  251. // size was known. Returns the total change in element size, and the scroll delta needed to keep
  252. // the top or left of the passed in visible element index at the same position after the content
  253. // size is changed to accomodate the new element sizes
  254. void CalculateVisibleElementIndicesFromVisibleElementIndex(int visibleElementIndex,
  255. const AZ::Vector2& visibleContentBound,
  256. bool keepAtEnd,
  257. int& firstVisibleElementIndexOut,
  258. int& lastVisibleElementIndexOut,
  259. int& firstDisplayedElementIndexOut,
  260. int& lastDisplayedElementIndexOut,
  261. int& firstDisplayedElementIndexWithSizeChangeOut,
  262. float& totalElementSizeChangeOut,
  263. float& scrollChangeOut);
  264. // Calculate the change in the content's top or left when resizing content with the specified delta.
  265. // Used to get the scroll delta needed to keep the beginning (top or left) of the content element
  266. // at the same position after a content size change
  267. float CalculateContentBeginningDeltaAfterSizeChange(float contentSizeDelta) const;
  268. // Calculate the change in the content's bottom or right when resizing content with the specified delta.
  269. // Used to get the scroll delta needed to keep the end (bottom or right) of the content element
  270. // at the same position after a content size change
  271. float CalculateContentEndDeltaAfterSizeChange(float contentSizeDelta) const;
  272. // Return whether the list is scrolled to the end
  273. bool IsScrolledToEnd() const;
  274. // Return whether the element at the specified index is being displayed
  275. bool IsElementDisplayedAtIndex(int index) const;
  276. // Get a recycled entity or a new entity for display
  277. AZ::EntityId GetElementForDisplay(ElementType elementType);
  278. // Get an entity to use to auto calculate sizes
  279. AZ::EntityId GetElementForAutoSizeCalculation(ElementType elementType);
  280. // Disable entities used to auto calculate sizes
  281. void DisableElementsForAutoSizeCalculation() const;
  282. // Calculate an element size with the layout cell interface
  283. float AutoCalculateElementSize(AZ::EntityId elementForAutoSizeCalculation) const;
  284. // Set an element's size based on index
  285. void SizeVariableElementAtIndex(AZ::EntityId element, int index) const;
  286. // Set an element's position based on index
  287. void PositionElementAtIndex(AZ::EntityId element, int index) const;
  288. // Set an element's anchors to the top or left
  289. void SetElementAnchors(AZ::EntityId element) const;
  290. // Set an element's offsets based on the specified offset
  291. void SetElementOffsets(AZ::EntityId element, float offset) const;
  292. // Get element type at the specified element index
  293. ElementType GetElementTypeAtIndex(int index) const;
  294. // Get index info of an element from the index of the element in the list
  295. ElementIndexInfo GetElementIndexInfoFromIndex(int index) const;
  296. // Get an index of an element in the list from the index info of that element
  297. int GetIndexFromElementIndexInfo(const ElementIndexInfo& elementIndexInfo) const;
  298. // Get the child of the content entity that contains the specified descendant
  299. AZ::EntityId GetImmediateContentChildFromDescendant(AZ::EntityId childElement) const;
  300. // Used for visibility attribute in reflection
  301. bool HeadersHaveVariableSizes() const;
  302. // Used to check if our item/header prototype elements contain this scrollbox which spawns it.
  303. // Prototype elements are spawned dynamically, and we need to avoid recursively spawning elements
  304. bool IsValidPrototype(AZ::EntityId entityId) const;
  305. protected: // data
  306. //! Whether the list should refresh automatically on post activate
  307. bool m_autoRefreshOnPostActivate;
  308. //! Number of elements by default. Overridden by UiDynamicListDataBus::GetNumElements
  309. int m_defaultNumElements;
  310. //! The prototype element for the items in the list.
  311. //! Used when sections are enabled
  312. AZ::EntityId m_itemPrototypeElement;
  313. //! Whether items in the list can vary in size (height for vertical lists and width for horizontal lists)
  314. //! If false, the item size is fixed and is determined by the prototype element
  315. bool m_variableItemElementSize;
  316. //! Whether item sizes should be auto calculated or whether they should be requested.
  317. //! Applies when items can vary in size
  318. bool m_autoCalculateItemElementSize;
  319. //! An estimated height or width for the item size. If set to greater than 0, this size will be used
  320. //! until an item goes into view and its real size is calculated.
  321. //! Applies when items can vary in size
  322. float m_estimatedItemElementSize;
  323. //! Whether the list is divided into sections with headers
  324. bool m_hasSections;
  325. //! Number of sections by default. Overridden by UiDynamicListDataBus::GetNumSections
  326. int m_defaultNumSections;
  327. //! The prototype element for the headers in the list.
  328. //! Used when sections are enabled
  329. AZ::EntityId m_headerPrototypeElement;
  330. //! Whether headers should stick to the beginning of the visible area
  331. //! Used when sections are enabled
  332. bool m_stickyHeaders;
  333. //! Whether headers in the list can vary in size (height for vertical lists and width for horizontal lists)
  334. //! If false, the header size is fixed and is determined by the prototype element
  335. bool m_variableHeaderElementSize;
  336. //! Whether header sizes should be auto calculated or whether they should be requested.
  337. //! Applies when headers can vary in size
  338. bool m_autoCalculateHeaderElementSize;
  339. //! An estimated height or width for the header size. If set to greater than 0, this size will be used
  340. //! until a header goes into view and its real size is calculated.
  341. //! Applies when headers can vary in size
  342. float m_estimatedHeaderElementSize;
  343. //! The entity Ids of the prototype elements
  344. AZ::EntityId m_prototypeElement[ElementType::NumElementTypes];
  345. //! Stores the size of the prototype elements before they are removed from the content element's
  346. //! child list. Used to calculate the content size and element offsets when there is no variation in element size
  347. float m_prototypeElementSize[ElementType::NumElementTypes];
  348. //! Whether elements in the list can vary in size (height for vertical lists and width for horizontal lists)
  349. //! If false, the element size is fixed and is determined by the prototype element
  350. bool m_variableElementSize[ElementType::NumElementTypes];
  351. //! Whether element sizes should be auto calculated or whether they should be requested.
  352. //! Applies when elements can vary in size
  353. bool m_autoCalculateElementSize[ElementType::NumElementTypes];
  354. //! An estimated height or width for the element size. If set to greater than 0, this size will be used
  355. //! until an element goes into view and its real size is calculated.
  356. //! Applies when elements can vary in size
  357. float m_estimatedElementSize[ElementType::NumElementTypes];
  358. //! Whether elements in the list are navigable
  359. bool m_isPrototypeElementNavigable[ElementType::NumElementTypes];
  360. //! Average element size of the elements with known sizes
  361. float m_averageElementSize;
  362. //! The number of elements used to calculate the current average
  363. int m_numElementsUsedForAverage;
  364. //! The last calculated offset from the top or left of the content's parent (the visible area) to the top or left of the content.
  365. //! Used to estimate a first visible element index
  366. float m_lastCalculatedVisibleContentOffset;
  367. //! Whether the list is vertical or horizontal. Determined by the scroll box
  368. bool m_isVertical;
  369. //! A list of currently displayed elements
  370. AZStd::list<DisplayedElement> m_displayedElements;
  371. //! A list of unused entities
  372. AZStd::list<AZ::EntityId> m_recycledElements[ElementType::NumElementTypes];
  373. //! Cloned entities used to auto calculate sizes
  374. AZ::EntityId m_clonedElementForAutoSizeCalculation[ElementType::NumElementTypes];
  375. //! The header that is currently sticky at the top or left of the visible area
  376. DisplayedElement m_currentStickyHeader;
  377. //! First and last element indices that are being displayed (Not necessarily visible in viewport since we add an extra element for gamepad/keyboard navigation purposes)
  378. int m_firstDisplayedElementIndex;
  379. int m_lastDisplayedElementIndex;
  380. //! First and last element indices that are visible in the viewport
  381. int m_firstVisibleElementIndex;
  382. int m_lastVisibleElementIndex;
  383. //! Cached element sizes. Used when elements can vary in size
  384. AZStd::vector<CachedElementInfo> m_cachedElementInfo;
  385. //! The virtual number of elements in the list. Includes both headers and items
  386. int m_numElements;
  387. //! List of the sections in the list
  388. AZStd::vector<Section> m_sections;
  389. //! Whether the list has been set up for display
  390. bool m_listPreparedForDisplay;
  391. };