3
0

UiDynamicScrollBoxComponent.cpp 114 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010
  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. #include "UiDynamicScrollBoxComponent.h"
  9. #include "UiElementComponent.h"
  10. #include "UiNavigationHelpers.h"
  11. #include "UiLayoutHelpers.h"
  12. #include <AzCore/Serialization/SerializeContext.h>
  13. #include <AzCore/Serialization/EditContext.h>
  14. #include <AzCore/RTTI/BehaviorContext.h>
  15. #include <AzCore/Component/ComponentApplicationBus.h>
  16. #include <LyShine/Bus/UiTransform2dBus.h>
  17. #include <LyShine/Bus/UiCanvasBus.h>
  18. #include <LyShine/Bus/UiLayoutCellBus.h>
  19. #include <LyShine/Bus/UiLayoutCellDefaultBus.h>
  20. ////////////////////////////////////////////////////////////////////////////////////////////////////
  21. //! UiDynamicScrollBoxDataBus Behavior context handler class
  22. class BehaviorUiDynamicScrollBoxDataBusHandler
  23. : public UiDynamicScrollBoxDataBus::Handler
  24. , public AZ::BehaviorEBusHandler
  25. {
  26. public:
  27. AZ_EBUS_BEHAVIOR_BINDER(BehaviorUiDynamicScrollBoxDataBusHandler, "{74FA95AB-D4C2-40B8-8568-1B174BF577C0}", AZ::SystemAllocator,
  28. GetNumElements, GetElementWidth, GetElementHeight, GetNumSections, GetNumElementsInSection,
  29. GetElementInSectionWidth, GetElementInSectionHeight, GetSectionHeaderWidth, GetSectionHeaderHeight);
  30. int GetNumElements() override
  31. {
  32. int numElements = 0;
  33. CallResult(numElements, FN_GetNumElements);
  34. return numElements;
  35. }
  36. float GetElementWidth(int index) override
  37. {
  38. float width = 0.0f;
  39. CallResult(width, FN_GetElementWidth, index);
  40. return width;
  41. }
  42. float GetElementHeight(int index) override
  43. {
  44. float height = 0.0f;
  45. CallResult(height, FN_GetElementHeight, index);
  46. return height;
  47. }
  48. int GetNumSections() override
  49. {
  50. int numSections = 0;
  51. CallResult(numSections, FN_GetNumSections);
  52. return numSections;
  53. }
  54. int GetNumElementsInSection(int sectionIndex) override
  55. {
  56. int numElementsInSection = 0;
  57. CallResult(numElementsInSection, FN_GetNumElementsInSection, sectionIndex);
  58. return numElementsInSection;
  59. }
  60. float GetElementInSectionWidth(int sectionIndex, int index) override
  61. {
  62. float width = 0.0f;
  63. CallResult(width, FN_GetElementInSectionWidth, sectionIndex, index);
  64. return width;
  65. }
  66. float GetElementInSectionHeight(int sectionIndex, int index) override
  67. {
  68. float height = 0.0f;
  69. CallResult(height, FN_GetElementInSectionHeight, sectionIndex, index);
  70. return height;
  71. }
  72. float GetSectionHeaderWidth(int sectionIndex) override
  73. {
  74. float width = 0.0f;
  75. CallResult(width, FN_GetSectionHeaderWidth, sectionIndex);
  76. return width;
  77. }
  78. float GetSectionHeaderHeight(int sectionIndex) override
  79. {
  80. float height = 0.0f;
  81. CallResult(height, FN_GetSectionHeaderHeight, sectionIndex);
  82. return height;
  83. }
  84. };
  85. ////////////////////////////////////////////////////////////////////////////////////////////////////
  86. //! UiDynamicScrollBoxElementNotificationBus Behavior context handler class
  87. class BehaviorUiDynamicScrollBoxElementNotificationBusHandler
  88. : public UiDynamicScrollBoxElementNotificationBus::Handler
  89. , public AZ::BehaviorEBusHandler
  90. {
  91. public:
  92. AZ_EBUS_BEHAVIOR_BINDER(BehaviorUiDynamicScrollBoxElementNotificationBusHandler, "{4D166273-4D12-45A4-BC42-A7FF59A2092E}", AZ::SystemAllocator,
  93. OnElementBecomingVisible, OnPrepareElementForSizeCalculation,
  94. OnElementInSectionBecomingVisible, OnPrepareElementInSectionForSizeCalculation,
  95. OnSectionHeaderBecomingVisible, OnPrepareSectionHeaderForSizeCalculation);
  96. void OnElementBecomingVisible(AZ::EntityId entityId, int index) override
  97. {
  98. Call(FN_OnElementBecomingVisible, entityId, index);
  99. }
  100. void OnPrepareElementForSizeCalculation(AZ::EntityId entityId, int index) override
  101. {
  102. Call(FN_OnPrepareElementForSizeCalculation, entityId, index);
  103. }
  104. void OnElementInSectionBecomingVisible(AZ::EntityId entityId, int sectionIndex, int index) override
  105. {
  106. Call(FN_OnElementInSectionBecomingVisible, entityId, sectionIndex, index);
  107. }
  108. void OnPrepareElementInSectionForSizeCalculation(AZ::EntityId entityId, int sectionIndex, int index) override
  109. {
  110. Call(FN_OnPrepareElementInSectionForSizeCalculation, entityId, sectionIndex, index);
  111. }
  112. void OnSectionHeaderBecomingVisible(AZ::EntityId entityId, int sectionIndex) override
  113. {
  114. Call(FN_OnSectionHeaderBecomingVisible, entityId, sectionIndex);
  115. }
  116. void OnPrepareSectionHeaderForSizeCalculation(AZ::EntityId entityId, int sectionIndex) override
  117. {
  118. Call(FN_OnPrepareSectionHeaderForSizeCalculation, entityId, sectionIndex);
  119. }
  120. };
  121. ////////////////////////////////////////////////////////////////////////////////////////////////////
  122. // PUBLIC MEMBER FUNCTIONS
  123. ////////////////////////////////////////////////////////////////////////////////////////////////////
  124. ////////////////////////////////////////////////////////////////////////////////////////////////////
  125. UiDynamicScrollBoxComponent::CachedElementInfo::CachedElementInfo()
  126. : m_size(-1.0f)
  127. , m_accumulatedSize(-1.0f)
  128. {
  129. }
  130. ////////////////////////////////////////////////////////////////////////////////////////////////////
  131. UiDynamicScrollBoxComponent::UiDynamicScrollBoxComponent()
  132. : m_autoRefreshOnPostActivate(true)
  133. , m_defaultNumElements(0)
  134. , m_variableItemElementSize(false)
  135. , m_autoCalculateItemElementSize(true)
  136. , m_estimatedItemElementSize(0.0f)
  137. , m_hasSections(false)
  138. , m_defaultNumSections(1)
  139. , m_stickyHeaders(false)
  140. , m_variableHeaderElementSize(false)
  141. , m_autoCalculateHeaderElementSize(true)
  142. , m_estimatedHeaderElementSize(0.0f)
  143. , m_averageElementSize(0.0f)
  144. , m_numElementsUsedForAverage(0)
  145. , m_lastCalculatedVisibleContentOffset(0.0f)
  146. , m_isVertical(true)
  147. , m_firstDisplayedElementIndex(-1)
  148. , m_lastDisplayedElementIndex(-1)
  149. , m_firstVisibleElementIndex(-1)
  150. , m_lastVisibleElementIndex(-1)
  151. , m_numElements(0)
  152. , m_listPreparedForDisplay(false)
  153. {
  154. }
  155. ////////////////////////////////////////////////////////////////////////////////////////////////////
  156. UiDynamicScrollBoxComponent::~UiDynamicScrollBoxComponent()
  157. {
  158. }
  159. ////////////////////////////////////////////////////////////////////////////////////////////////////
  160. void UiDynamicScrollBoxComponent::RefreshContent()
  161. {
  162. if (!m_listPreparedForDisplay)
  163. {
  164. PrepareListForDisplay();
  165. }
  166. ResizeContentToFitElements();
  167. ClearDisplayedElements();
  168. bool keepAtEndIfWasAtEnd = false;
  169. if (AnyElementTypesHaveEstimatedSizes())
  170. {
  171. // Check if the content's pivot is at the end (bottom or right)
  172. AZ::EntityId contentEntityId;
  173. UiScrollBoxBus::EventResult(contentEntityId, GetEntityId(), &UiScrollBoxBus::Events::GetContentEntity);
  174. if (contentEntityId.IsValid())
  175. {
  176. AZ::Vector2 pivot(0.0f, 0.0f);
  177. UiTransformBus::EventResult(pivot, contentEntityId, &UiTransformBus::Events::GetPivot);
  178. if (m_isVertical)
  179. {
  180. keepAtEndIfWasAtEnd = pivot.GetY() == 1.0f;
  181. }
  182. else
  183. {
  184. keepAtEndIfWasAtEnd = pivot.GetX() == 1.0f;
  185. }
  186. }
  187. }
  188. UpdateElementVisibility(keepAtEndIfWasAtEnd);
  189. }
  190. ////////////////////////////////////////////////////////////////////////////////////////////////////
  191. void UiDynamicScrollBoxComponent::AddElementsToEnd(int numElementsToAdd, bool scrollToEndIfWasAtEnd)
  192. {
  193. AZ_Warning("UiDynamicScrollBoxComponent", m_listPreparedForDisplay, "AddElementsToEnd() is only supported after the first content refresh");
  194. if (!m_listPreparedForDisplay)
  195. {
  196. return;
  197. }
  198. AZ_Warning("UiDynamicScrollBoxComponent", !m_hasSections, "AddElementsToEnd() can only be used on lists that are not divided into sections");
  199. if (numElementsToAdd > 0 && !m_hasSections)
  200. {
  201. m_numElements += numElementsToAdd;
  202. // Calculate new content size
  203. float sizeDiff = 0.0f;
  204. if (!m_variableElementSize[ElementType::Item])
  205. {
  206. sizeDiff = numElementsToAdd * m_prototypeElementSize[ElementType::Item];
  207. }
  208. else
  209. {
  210. // Add cache entries for the new elements
  211. m_cachedElementInfo.insert(m_cachedElementInfo.end(), numElementsToAdd, CachedElementInfo());
  212. for (int i = m_numElements - numElementsToAdd; i < m_numElements; i++)
  213. {
  214. sizeDiff += GetAndCacheVariableElementSize(i);
  215. }
  216. if (m_autoCalculateElementSize[ElementType::Item])
  217. {
  218. DisableElementsForAutoSizeCalculation();
  219. }
  220. UpdateAverageElementSize(numElementsToAdd, sizeDiff);
  221. }
  222. bool scrollToEnd = scrollToEndIfWasAtEnd && IsScrolledToEnd();
  223. if (scrollToEnd)
  224. {
  225. float scrollDiff = CalculateContentEndDeltaAfterSizeChange(sizeDiff);
  226. AdjustContentSizeAndScrollOffsetByDelta(sizeDiff, scrollDiff);
  227. if (!IsScrolledToEnd())
  228. {
  229. ScrollToEnd();
  230. }
  231. else
  232. {
  233. UpdateElementVisibility(true);
  234. }
  235. }
  236. else
  237. {
  238. float scrollDiff = CalculateContentBeginningDeltaAfterSizeChange(sizeDiff);
  239. AdjustContentSizeAndScrollOffsetByDelta(sizeDiff, scrollDiff);
  240. UpdateElementVisibility();
  241. }
  242. }
  243. }
  244. ////////////////////////////////////////////////////////////////////////////////////////////////////
  245. void UiDynamicScrollBoxComponent::RemoveElementsFromFront(int numElementsToRemove)
  246. {
  247. AZ_Warning("UiDynamicScrollBoxComponent", m_listPreparedForDisplay, "RemoveElementsFromFront() is only supported after the first content refresh");
  248. if (!m_listPreparedForDisplay)
  249. {
  250. return;
  251. }
  252. AZ_Warning("UiDynamicScrollBoxComponent", !m_hasSections, "RemoveElementsFromFront() can only be used on lists that are not divided into sections");
  253. if (numElementsToRemove > 0 && !m_hasSections)
  254. {
  255. AZ_Warning("UiDynamicScrollBoxComponent", numElementsToRemove <= m_numElements, "attempting to remove more elements than are in the list");
  256. numElementsToRemove = AZ::GetClamp(numElementsToRemove, 0, m_numElements);
  257. float sizeDiff = 0.0f;
  258. if (!m_variableElementSize[ElementType::Item])
  259. {
  260. sizeDiff = numElementsToRemove * m_prototypeElementSize[ElementType::Item];
  261. }
  262. else
  263. {
  264. // Get accumulated size being removed
  265. sizeDiff = GetVariableSizeElementOffset(numElementsToRemove - 1) + GetVariableElementSize(numElementsToRemove - 1);
  266. // Update cached element info
  267. m_cachedElementInfo.erase(m_cachedElementInfo.begin(), m_cachedElementInfo.begin() + numElementsToRemove);
  268. // Update accumulated sizes
  269. int newElementCount = m_numElements - numElementsToRemove;
  270. for (int i = 0; i < newElementCount; i++)
  271. {
  272. if (m_cachedElementInfo[i].m_accumulatedSize >= 0.0f)
  273. {
  274. m_cachedElementInfo[i].m_accumulatedSize -= sizeDiff;
  275. }
  276. }
  277. }
  278. sizeDiff = -sizeDiff;
  279. m_numElements -= numElementsToRemove;
  280. if (numElementsToRemove > 0)
  281. {
  282. ClearDisplayedElements();
  283. float scrollDiff = CalculateContentBeginningDeltaAfterSizeChange(sizeDiff) - sizeDiff;
  284. AdjustContentSizeAndScrollOffsetByDelta(sizeDiff, scrollDiff);
  285. UpdateElementVisibility();
  286. }
  287. }
  288. }
  289. ////////////////////////////////////////////////////////////////////////////////////////////////////
  290. void UiDynamicScrollBoxComponent::ScrollToEnd()
  291. {
  292. AZ_Warning("UiDynamicScrollBoxComponent", m_listPreparedForDisplay, "ScrollToEnd() is only supported after the first content refresh");
  293. if (!m_listPreparedForDisplay)
  294. {
  295. return;
  296. }
  297. // Find the content element
  298. AZ::EntityId contentEntityId;
  299. UiScrollBoxBus::EventResult(contentEntityId, GetEntityId(), &UiScrollBoxBus::Events::GetContentEntity);
  300. if (!contentEntityId.IsValid())
  301. {
  302. return;
  303. }
  304. // Get content's parent
  305. AZ::EntityId contentParentEntityId;
  306. UiElementBus::EventResult(contentParentEntityId, contentEntityId, &UiElementBus::Events::GetParentEntityId);
  307. if (!contentParentEntityId.IsValid())
  308. {
  309. return;
  310. }
  311. // Get content's rect in canvas space
  312. UiTransformInterface::Rect contentRect;
  313. UiTransformBus::Event(contentEntityId, &UiTransformBus::Events::GetCanvasSpaceRectNoScaleRotate, contentRect);
  314. // Get content parent's rect in canvas space
  315. UiTransformInterface::Rect parentRect;
  316. UiTransformBus::Event(contentParentEntityId, &UiTransformBus::Events::GetCanvasSpaceRectNoScaleRotate, parentRect);
  317. float scrollDelta = 0.0f;
  318. if (m_isVertical)
  319. {
  320. if (contentRect.bottom > parentRect.bottom)
  321. {
  322. scrollDelta = parentRect.bottom - contentRect.bottom;
  323. }
  324. }
  325. else
  326. {
  327. if (contentRect.right > parentRect.right)
  328. {
  329. scrollDelta = parentRect.right - contentRect.right;
  330. }
  331. }
  332. if (scrollDelta != 0.0f)
  333. {
  334. AdjustContentSizeAndScrollOffsetByDelta(0.0f, scrollDelta);
  335. UpdateElementVisibility(true);
  336. }
  337. }
  338. ////////////////////////////////////////////////////////////////////////////////////////////////////
  339. int UiDynamicScrollBoxComponent::GetElementIndexOfChild(AZ::EntityId childElement)
  340. {
  341. AZ::EntityId immediateChild = GetImmediateContentChildFromDescendant(childElement);
  342. for (const auto& e : m_displayedElements)
  343. {
  344. if (e.m_element == immediateChild)
  345. {
  346. if (!m_hasSections)
  347. {
  348. return e.m_elementIndex;
  349. }
  350. else
  351. {
  352. return e.m_indexInfo.m_itemIndexInSection;
  353. }
  354. }
  355. }
  356. return -1;
  357. }
  358. ////////////////////////////////////////////////////////////////////////////////////////////////////
  359. int UiDynamicScrollBoxComponent::GetSectionIndexOfChild(AZ::EntityId childElement)
  360. {
  361. AZ_Warning("UiDynamicScrollBoxComponent", m_hasSections, "GetSectionIndexOfChild() can only be used on lists that are divided into sections");
  362. if (m_hasSections)
  363. {
  364. AZ::EntityId immediateChild = GetImmediateContentChildFromDescendant(childElement);
  365. for (const auto& e : m_displayedElements)
  366. {
  367. if (e.m_element == immediateChild)
  368. {
  369. return e.m_indexInfo.m_sectionIndex;
  370. }
  371. }
  372. }
  373. return -1;
  374. }
  375. ////////////////////////////////////////////////////////////////////////////////////////////////////
  376. AZ::EntityId UiDynamicScrollBoxComponent::GetChildAtElementIndex(int index)
  377. {
  378. AZ::EntityId elementId;
  379. AZ_Warning("UiDynamicScrollBoxComponent", !m_hasSections, "GetChildAtElementIndex() can only be used on lists that are not divided into sections");
  380. if (!m_hasSections)
  381. {
  382. elementId = FindDisplayedElementWithIndex(index);
  383. }
  384. return elementId;
  385. }
  386. ////////////////////////////////////////////////////////////////////////////////////////////////////
  387. AZ::EntityId UiDynamicScrollBoxComponent::GetChildAtSectionAndElementIndex(int sectionIndex, int index)
  388. {
  389. AZ::EntityId elementId;
  390. AZ_Warning("UiDynamicScrollBoxComponent", m_hasSections, "GetChildElementAtSectionAndLocationIndex() can only be used on lists that are divided into sections");
  391. if (m_hasSections)
  392. {
  393. for (const auto& e : m_displayedElements)
  394. {
  395. if (e.m_indexInfo.m_sectionIndex == sectionIndex && e.m_indexInfo.m_itemIndexInSection == index)
  396. {
  397. elementId = e.m_element;
  398. break;
  399. }
  400. }
  401. }
  402. return elementId;
  403. }
  404. ////////////////////////////////////////////////////////////////////////////////////////////////////
  405. bool UiDynamicScrollBoxComponent::GetAutoRefreshOnPostActivate()
  406. {
  407. return m_autoRefreshOnPostActivate;
  408. }
  409. ////////////////////////////////////////////////////////////////////////////////////////////////////
  410. void UiDynamicScrollBoxComponent::SetAutoRefreshOnPostActivate(bool autoRefresh)
  411. {
  412. m_autoRefreshOnPostActivate = autoRefresh;
  413. }
  414. ////////////////////////////////////////////////////////////////////////////////////////////////////
  415. AZ::EntityId UiDynamicScrollBoxComponent::GetPrototypeElement()
  416. {
  417. return m_itemPrototypeElement;
  418. }
  419. ////////////////////////////////////////////////////////////////////////////////////////////////////
  420. void UiDynamicScrollBoxComponent::SetPrototypeElement(AZ::EntityId prototypeElement)
  421. {
  422. AZ_Warning("UiDynamicScrollBoxComponent", !m_listPreparedForDisplay, "Changing properties is only supported before the first content refresh");
  423. if (!m_listPreparedForDisplay)
  424. {
  425. m_itemPrototypeElement = prototypeElement;
  426. }
  427. }
  428. ////////////////////////////////////////////////////////////////////////////////////////////////////
  429. bool UiDynamicScrollBoxComponent::GetElementsVaryInSize()
  430. {
  431. return m_variableItemElementSize;
  432. }
  433. ////////////////////////////////////////////////////////////////////////////////////////////////////
  434. void UiDynamicScrollBoxComponent::SetElementsVaryInSize(bool varyInSize)
  435. {
  436. AZ_Warning("UiDynamicScrollBoxComponent", !m_listPreparedForDisplay, "Changing properties is only supported before the first content refresh");
  437. if (!m_listPreparedForDisplay)
  438. {
  439. m_variableItemElementSize = varyInSize;
  440. }
  441. }
  442. ////////////////////////////////////////////////////////////////////////////////////////////////////
  443. bool UiDynamicScrollBoxComponent::GetAutoCalculateVariableElementSize()
  444. {
  445. return m_autoCalculateItemElementSize;
  446. }
  447. ////////////////////////////////////////////////////////////////////////////////////////////////////
  448. void UiDynamicScrollBoxComponent::SetAutoCalculateVariableElementSize(bool autoCalculateSize)
  449. {
  450. AZ_Warning("UiDynamicScrollBoxComponent", !m_listPreparedForDisplay, "Changing properties is only supported before the first content refresh");
  451. if (!m_listPreparedForDisplay)
  452. {
  453. m_autoCalculateItemElementSize = autoCalculateSize;
  454. }
  455. }
  456. ////////////////////////////////////////////////////////////////////////////////////////////////////
  457. float UiDynamicScrollBoxComponent::GetEstimatedVariableElementSize()
  458. {
  459. return m_estimatedItemElementSize;
  460. }
  461. ////////////////////////////////////////////////////////////////////////////////////////////////////
  462. void UiDynamicScrollBoxComponent::SetEstimatedVariableElementSize(float estimatedSize)
  463. {
  464. AZ_Warning("UiDynamicScrollBoxComponent", !m_listPreparedForDisplay, "Changing properties is only supported before the first content refresh");
  465. if (!m_listPreparedForDisplay)
  466. {
  467. m_estimatedItemElementSize = AZ::GetMax(estimatedSize, 0.0f);
  468. }
  469. }
  470. ////////////////////////////////////////////////////////////////////////////////////////////////////
  471. bool UiDynamicScrollBoxComponent::GetSectionsEnabled()
  472. {
  473. return m_hasSections;
  474. }
  475. ////////////////////////////////////////////////////////////////////////////////////////////////////
  476. void UiDynamicScrollBoxComponent::SetSectionsEnabled(bool sectionsEnabled)
  477. {
  478. AZ_Warning("UiDynamicScrollBoxComponent", !m_listPreparedForDisplay, "Changing properties is only supported before the first content refresh");
  479. if (!m_listPreparedForDisplay)
  480. {
  481. m_hasSections = sectionsEnabled;
  482. }
  483. }
  484. ////////////////////////////////////////////////////////////////////////////////////////////////////
  485. AZ::EntityId UiDynamicScrollBoxComponent::GetPrototypeHeader()
  486. {
  487. return m_headerPrototypeElement;
  488. }
  489. ////////////////////////////////////////////////////////////////////////////////////////////////////
  490. void UiDynamicScrollBoxComponent::SetPrototypeHeader(AZ::EntityId prototypeHeader)
  491. {
  492. AZ_Warning("UiDynamicScrollBoxComponent", !m_listPreparedForDisplay, "Changing properties is only supported before the first content refresh");
  493. if (!m_listPreparedForDisplay)
  494. {
  495. m_headerPrototypeElement = prototypeHeader;
  496. }
  497. }
  498. ////////////////////////////////////////////////////////////////////////////////////////////////////
  499. bool UiDynamicScrollBoxComponent::GetHeadersSticky()
  500. {
  501. return m_stickyHeaders;
  502. }
  503. ////////////////////////////////////////////////////////////////////////////////////////////////////
  504. void UiDynamicScrollBoxComponent::SetHeadersSticky(bool stickyHeaders)
  505. {
  506. AZ_Warning("UiDynamicScrollBoxComponent", !m_listPreparedForDisplay, "Changing properties is only supported before the first content refresh");
  507. if (!m_listPreparedForDisplay)
  508. {
  509. m_stickyHeaders = stickyHeaders;
  510. }
  511. }
  512. ////////////////////////////////////////////////////////////////////////////////////////////////////
  513. bool UiDynamicScrollBoxComponent::GetHeadersVaryInSize()
  514. {
  515. return m_variableHeaderElementSize;
  516. }
  517. ////////////////////////////////////////////////////////////////////////////////////////////////////
  518. void UiDynamicScrollBoxComponent::SetHeadersVaryInSize(bool varyInSize)
  519. {
  520. AZ_Warning("UiDynamicScrollBoxComponent", !m_listPreparedForDisplay, "Changing properties is only supported before the first content refresh");
  521. if (!m_listPreparedForDisplay)
  522. {
  523. m_variableHeaderElementSize = varyInSize;
  524. }
  525. }
  526. ////////////////////////////////////////////////////////////////////////////////////////////////////
  527. bool UiDynamicScrollBoxComponent::GetAutoCalculateVariableHeaderSize()
  528. {
  529. return m_autoCalculateHeaderElementSize;
  530. }
  531. ////////////////////////////////////////////////////////////////////////////////////////////////////
  532. void UiDynamicScrollBoxComponent::SetAutoCalculateVariableHeaderSize(bool autoCalculateSize)
  533. {
  534. AZ_Warning("UiDynamicScrollBoxComponent", !m_listPreparedForDisplay, "Changing properties is only supported before the first content refresh");
  535. if (!m_listPreparedForDisplay)
  536. {
  537. m_autoCalculateHeaderElementSize = autoCalculateSize;
  538. }
  539. }
  540. ////////////////////////////////////////////////////////////////////////////////////////////////////
  541. float UiDynamicScrollBoxComponent::GetEstimatedVariableHeaderSize()
  542. {
  543. return m_estimatedHeaderElementSize;
  544. }
  545. ////////////////////////////////////////////////////////////////////////////////////////////////////
  546. void UiDynamicScrollBoxComponent::SetEstimatedVariableHeaderSize(float estimatedSize)
  547. {
  548. AZ_Warning("UiDynamicScrollBoxComponent", !m_listPreparedForDisplay, "Changing properties is only supported before the first content refresh");
  549. if (!m_listPreparedForDisplay)
  550. {
  551. m_estimatedHeaderElementSize = AZ::GetMax(estimatedSize, 0.0f);
  552. }
  553. }
  554. ////////////////////////////////////////////////////////////////////////////////////////////////////
  555. void UiDynamicScrollBoxComponent::OnScrollOffsetChanging([[maybe_unused]] AZ::Vector2 newScrollOffset)
  556. {
  557. UpdateElementVisibility();
  558. }
  559. ////////////////////////////////////////////////////////////////////////////////////////////////////
  560. void UiDynamicScrollBoxComponent::OnScrollOffsetChanged([[maybe_unused]] AZ::Vector2 newScrollOffset)
  561. {
  562. UpdateElementVisibility();
  563. }
  564. ////////////////////////////////////////////////////////////////////////////////////////////////////
  565. void UiDynamicScrollBoxComponent::InGamePostActivate()
  566. {
  567. if (m_autoRefreshOnPostActivate)
  568. {
  569. RefreshContent();
  570. }
  571. }
  572. ////////////////////////////////////////////////////////////////////////////////////////////////////
  573. void UiDynamicScrollBoxComponent::OnCanvasSpaceRectChanged([[maybe_unused]] AZ::EntityId entityId, const UiTransformInterface::Rect& oldRect, const UiTransformInterface::Rect& newRect)
  574. {
  575. // If old rect equals new rect, size changed due to initialization
  576. bool sizeChanged = (oldRect == newRect) || (!oldRect.GetSize().IsClose(newRect.GetSize(), 0.05f));
  577. if (sizeChanged)
  578. {
  579. UpdateElementVisibility();
  580. }
  581. }
  582. ////////////////////////////////////////////////////////////////////////////////////////////////////
  583. void UiDynamicScrollBoxComponent::OnUiElementBeingDestroyed()
  584. {
  585. for (int i = 0; i < ElementType::NumElementTypes; i++)
  586. {
  587. if (m_prototypeElement[i].IsValid())
  588. {
  589. UiElementBus::Event(m_prototypeElement[i], &UiElementBus::Events::DestroyElement);
  590. m_prototypeElement[i].SetInvalid();
  591. }
  592. }
  593. }
  594. ////////////////////////////////////////////////////////////////////////////////////////////////////
  595. // PUBLIC STATIC MEMBER FUNCTIONS
  596. ////////////////////////////////////////////////////////////////////////////////////////////////////
  597. void UiDynamicScrollBoxComponent::Reflect(AZ::ReflectContext* context)
  598. {
  599. AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
  600. if (serializeContext)
  601. {
  602. serializeContext->Class<UiDynamicScrollBoxComponent, AZ::Component>()
  603. ->Version(1)
  604. ->Field("AutoRefreshOnPostActivate", &UiDynamicScrollBoxComponent::m_autoRefreshOnPostActivate)
  605. ->Field("PrototypeElement", &UiDynamicScrollBoxComponent::m_itemPrototypeElement)
  606. ->Field("VariableElementSize", &UiDynamicScrollBoxComponent::m_variableItemElementSize)
  607. ->Field("AutoCalcElementSize", &UiDynamicScrollBoxComponent::m_autoCalculateItemElementSize)
  608. ->Field("EstimatedElementSize", &UiDynamicScrollBoxComponent::m_estimatedItemElementSize)
  609. ->Field("DefaultNumElements", &UiDynamicScrollBoxComponent::m_defaultNumElements)
  610. ->Field("HasSections", &UiDynamicScrollBoxComponent::m_hasSections)
  611. ->Field("HeaderPrototypeElement", &UiDynamicScrollBoxComponent::m_headerPrototypeElement)
  612. ->Field("StickyHeaders", &UiDynamicScrollBoxComponent::m_stickyHeaders)
  613. ->Field("VariableHeaderSize", &UiDynamicScrollBoxComponent::m_variableHeaderElementSize)
  614. ->Field("AutoCalcHeaderSize", &UiDynamicScrollBoxComponent::m_autoCalculateHeaderElementSize)
  615. ->Field("EstimatedHeaderSize", &UiDynamicScrollBoxComponent::m_estimatedHeaderElementSize)
  616. ->Field("DefaultNumSections", &UiDynamicScrollBoxComponent::m_defaultNumSections);
  617. AZ::EditContext* ec = serializeContext->GetEditContext();
  618. if (ec)
  619. {
  620. auto editInfo = ec->Class<UiDynamicScrollBoxComponent>("DynamicScrollBox",
  621. "A component that dynamically sets up scroll box content as a horizontal or vertical list of elements that\n"
  622. "are cloned from a prototype element. Only the minimum number of elements are created for efficient scrolling.\n"
  623. "The scroll box's content element's first child acts as the prototype element.");
  624. editInfo->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  625. ->Attribute(AZ::Edit::Attributes::Category, "UI")
  626. ->Attribute(AZ::Edit::Attributes::Icon, "Editor/Icons/Components/UiDynamicScrollBox.png")
  627. ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Editor/Icons/Components/Viewport/UiDynamicScrollBox.png")
  628. ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("UI", 0x27ff46b0))
  629. ->Attribute(AZ::Edit::Attributes::AutoExpand, true);
  630. editInfo->DataElement(0, &UiDynamicScrollBoxComponent::m_autoRefreshOnPostActivate, "Refresh on activate",
  631. "Whether the list should automatically prepare and refresh its content post activation.");
  632. editInfo->DataElement(0, &UiDynamicScrollBoxComponent::m_itemPrototypeElement, "Prototype element",
  633. "The prototype element to be used for the elements in the list. If empty, the prototype element will default to the first child of the content element.");
  634. editInfo->DataElement(0, &UiDynamicScrollBoxComponent::m_variableItemElementSize, "Variable element size",
  635. "Whether elements in the list can vary in size. If not, the element size is fixed and is determined by the prototype element.")
  636. ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ_CRC("RefreshEntireTree", 0xefbc823c));
  637. editInfo->DataElement(0, &UiDynamicScrollBoxComponent::m_autoCalculateItemElementSize, "Auto calc element size",
  638. "Whether element sizes should be auto calculated or whether they should be requested.")
  639. ->Attribute(AZ::Edit::Attributes::Visibility, &UiDynamicScrollBoxComponent::m_variableItemElementSize);
  640. editInfo->DataElement(0, &UiDynamicScrollBoxComponent::m_estimatedItemElementSize, "Estimated element size",
  641. "The element size to use as an estimate before the element appears in the view. If set to 0, sizes will be calculated up front.")
  642. ->Attribute(AZ::Edit::Attributes::Visibility, &UiDynamicScrollBoxComponent::m_variableItemElementSize)
  643. ->Attribute(AZ::Edit::Attributes::Min, 0.0f);
  644. editInfo->DataElement(AZ::Edit::UIHandlers::SpinBox, &UiDynamicScrollBoxComponent::m_defaultNumElements, "Default num elements",
  645. "The default number of elements in the list.")
  646. ->Attribute(AZ::Edit::Attributes::Min, 0);
  647. editInfo->ClassElement(AZ::Edit::ClassElements::Group, "Sections")
  648. ->Attribute(AZ::Edit::Attributes::AutoExpand, true);
  649. editInfo->DataElement(0, &UiDynamicScrollBoxComponent::m_hasSections, "Enabled",
  650. "Whether the list should be divided into sections with headers.")
  651. ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ_CRC("RefreshEntireTree", 0xefbc823c));
  652. editInfo->DataElement(0, &UiDynamicScrollBoxComponent::m_headerPrototypeElement, "Prototype header",
  653. "The prototype element to be used for the section headers in the list.")
  654. ->Attribute(AZ::Edit::Attributes::Visibility, &UiDynamicScrollBoxComponent::m_hasSections);
  655. editInfo->DataElement(0, &UiDynamicScrollBoxComponent::m_stickyHeaders, "Sticky headers",
  656. "Whether headers should stick to the beginning of the visible list area.")
  657. ->Attribute(AZ::Edit::Attributes::Visibility, &UiDynamicScrollBoxComponent::m_hasSections);
  658. editInfo->DataElement(0, &UiDynamicScrollBoxComponent::m_variableHeaderElementSize, "Variable header size",
  659. "Whether headers in the list can vary in size. If not, the header size is fixed and is determined by the prototype element.")
  660. ->Attribute(AZ::Edit::Attributes::Visibility, &UiDynamicScrollBoxComponent::m_hasSections)
  661. ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ_CRC("RefreshEntireTree", 0xefbc823c));
  662. editInfo->DataElement(0, &UiDynamicScrollBoxComponent::m_autoCalculateHeaderElementSize, "Auto calc header size",
  663. "Whether header sizes should be auto calculated or whether they should be requested.")
  664. ->Attribute(AZ::Edit::Attributes::Visibility, &UiDynamicScrollBoxComponent::HeadersHaveVariableSizes);
  665. editInfo->DataElement(0, &UiDynamicScrollBoxComponent::m_estimatedHeaderElementSize, "Estimated header size",
  666. "The header size to use as an estimate before the header appears in the view. If set to 0, sizes will be calculated up front.")
  667. ->Attribute(AZ::Edit::Attributes::Visibility, &UiDynamicScrollBoxComponent::HeadersHaveVariableSizes)
  668. ->Attribute(AZ::Edit::Attributes::Min, 0.0f);
  669. editInfo->DataElement(AZ::Edit::UIHandlers::SpinBox, &UiDynamicScrollBoxComponent::m_defaultNumSections, "Default num sections",
  670. "The default number of sections in the list.")
  671. ->Attribute(AZ::Edit::Attributes::Visibility, &UiDynamicScrollBoxComponent::m_hasSections)
  672. ->Attribute(AZ::Edit::Attributes::Min, 1);
  673. }
  674. }
  675. AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context);
  676. if (behaviorContext)
  677. {
  678. behaviorContext->EBus<UiDynamicScrollBoxBus>("UiDynamicScrollBoxBus")
  679. ->Event("RefreshContent", &UiDynamicScrollBoxBus::Events::RefreshContent)
  680. ->Event("AddElementsToEnd", &UiDynamicScrollBoxBus::Events::AddElementsToEnd)
  681. ->Event("RemoveElementsFromFront", &UiDynamicScrollBoxBus::Events::RemoveElementsFromFront)
  682. ->Event("ScrollToEnd", &UiDynamicScrollBoxBus::Events::ScrollToEnd)
  683. ->Event("GetElementIndexOfChild", &UiDynamicScrollBoxBus::Events::GetElementIndexOfChild)
  684. ->Event("GetSectionIndexOfChild", &UiDynamicScrollBoxBus::Events::GetSectionIndexOfChild)
  685. ->Event("GetChildAtElementIndex", &UiDynamicScrollBoxBus::Events::GetChildAtElementIndex)
  686. ->Event("GetChildAtSectionAndElementIndex", &UiDynamicScrollBoxBus::Events::GetChildAtSectionAndElementIndex)
  687. ->Event("GetAutoRefreshOnPostActivate", &UiDynamicScrollBoxBus::Events::GetAutoRefreshOnPostActivate)
  688. ->Event("SetAutoRefreshOnPostActivate", &UiDynamicScrollBoxBus::Events::SetAutoRefreshOnPostActivate)
  689. ->Event("GetPrototypeElement", &UiDynamicScrollBoxBus::Events::GetPrototypeElement)
  690. ->Event("SetPrototypeElement", &UiDynamicScrollBoxBus::Events::SetPrototypeElement)
  691. ->Event("GetElementsVaryInSize", &UiDynamicScrollBoxBus::Events::GetElementsVaryInSize)
  692. ->Event("SetElementsVaryInSize", &UiDynamicScrollBoxBus::Events::SetElementsVaryInSize)
  693. ->Event("GetAutoCalculateVariableElementSize", &UiDynamicScrollBoxBus::Events::GetAutoCalculateVariableElementSize)
  694. ->Event("SetAutoCalculateVariableElementSize", &UiDynamicScrollBoxBus::Events::SetAutoCalculateVariableElementSize)
  695. ->Event("GetEstimatedVariableElementSize", &UiDynamicScrollBoxBus::Events::GetEstimatedVariableElementSize)
  696. ->Event("SetEstimatedVariableElementSize", &UiDynamicScrollBoxBus::Events::SetEstimatedVariableElementSize)
  697. ->Event("GetSectionsEnabled", &UiDynamicScrollBoxBus::Events::GetSectionsEnabled)
  698. ->Event("SetSectionsEnabled", &UiDynamicScrollBoxBus::Events::SetSectionsEnabled)
  699. ->Event("GetPrototypeHeader", &UiDynamicScrollBoxBus::Events::GetPrototypeHeader)
  700. ->Event("SetPrototypeHeader", &UiDynamicScrollBoxBus::Events::SetPrototypeHeader)
  701. ->Event("GetHeadersSticky", &UiDynamicScrollBoxBus::Events::GetHeadersSticky)
  702. ->Event("SetHeadersSticky", &UiDynamicScrollBoxBus::Events::SetHeadersSticky)
  703. ->Event("GetHeadersVaryInSize", &UiDynamicScrollBoxBus::Events::GetHeadersVaryInSize)
  704. ->Event("SetHeadersVaryInSize", &UiDynamicScrollBoxBus::Events::SetHeadersVaryInSize)
  705. ->Event("GetAutoCalculateVariableHeaderSize", &UiDynamicScrollBoxBus::Events::GetAutoCalculateVariableHeaderSize)
  706. ->Event("SetAutoCalculateVariableHeaderSize", &UiDynamicScrollBoxBus::Events::SetAutoCalculateVariableHeaderSize)
  707. ->Event("GetEstimatedVariableHeaderSize", &UiDynamicScrollBoxBus::Events::GetEstimatedVariableHeaderSize)
  708. ->Event("SetEstimatedVariableHeaderSize", &UiDynamicScrollBoxBus::Events::SetEstimatedVariableHeaderSize)
  709. ;
  710. behaviorContext->EBus<UiDynamicScrollBoxDataBus>("UiDynamicScrollBoxDataBus")
  711. ->Handler<BehaviorUiDynamicScrollBoxDataBusHandler>();
  712. behaviorContext->EBus<UiDynamicScrollBoxElementNotificationBus>("UiDynamicScrollBoxElementNotificationBus")
  713. ->Handler<BehaviorUiDynamicScrollBoxElementNotificationBusHandler>();
  714. }
  715. }
  716. ////////////////////////////////////////////////////////////////////////////////////////////////////
  717. // PROTECTED MEMBER FUNCTIONS
  718. ////////////////////////////////////////////////////////////////////////////////////////////////////
  719. ////////////////////////////////////////////////////////////////////////////////////////////////////
  720. void UiDynamicScrollBoxComponent::Activate()
  721. {
  722. UiDynamicScrollBoxBus::Handler::BusConnect(GetEntityId());
  723. UiInitializationBus::Handler::BusConnect(GetEntityId());
  724. UiElementNotificationBus::Handler::BusConnect(GetEntityId());
  725. }
  726. ////////////////////////////////////////////////////////////////////////////////////////////////////
  727. void UiDynamicScrollBoxComponent::Deactivate()
  728. {
  729. UiDynamicScrollBoxBus::Handler::BusDisconnect();
  730. UiInitializationBus::Handler::BusDisconnect();
  731. if (UiTransformChangeNotificationBus::Handler::BusIsConnected())
  732. {
  733. UiTransformChangeNotificationBus::Handler::BusDisconnect();
  734. }
  735. if (UiScrollBoxNotificationBus::Handler::BusIsConnected())
  736. {
  737. UiScrollBoxNotificationBus::Handler::BusDisconnect();
  738. }
  739. UiElementNotificationBus::Handler::BusDisconnect();
  740. }
  741. ////////////////////////////////////////////////////////////////////////////////////////////////////
  742. void UiDynamicScrollBoxComponent::PrepareListForDisplay()
  743. {
  744. if (m_listPreparedForDisplay)
  745. {
  746. return;
  747. }
  748. // Set whether list is vertical or horizontal
  749. m_isVertical = true;
  750. UiScrollBoxBus::EventResult(m_isVertical, GetEntityId(), &UiScrollBoxBus::Events::GetIsVerticalScrollingEnabled);
  751. m_variableElementSize[ElementType::Item] = m_variableItemElementSize;
  752. m_autoCalculateElementSize[ElementType::Item] = m_variableItemElementSize ? m_autoCalculateItemElementSize : false;
  753. m_estimatedElementSize[ElementType::Item] = m_variableItemElementSize ? m_estimatedItemElementSize : 0.0f;
  754. m_variableElementSize[ElementType::SectionHeader] = m_hasSections ? m_variableHeaderElementSize : false;
  755. m_autoCalculateElementSize[ElementType::SectionHeader] = (m_hasSections && m_variableHeaderElementSize) ? m_autoCalculateHeaderElementSize : false;
  756. m_estimatedElementSize[ElementType::SectionHeader] = (m_hasSections && m_variableHeaderElementSize) ? m_estimatedHeaderElementSize : 0.0f;
  757. for (int i = 0; i < ElementType::NumElementTypes; i++)
  758. {
  759. m_prototypeElement[i].SetInvalid();
  760. }
  761. // Find the content element
  762. AZ::EntityId contentEntityId;
  763. UiScrollBoxBus::EventResult(contentEntityId, GetEntityId(), &UiScrollBoxBus::Events::GetContentEntity);
  764. int numChildren = 0;
  765. UiElementBus::EventResult(numChildren, contentEntityId, &UiElementBus::Events::GetNumChildElements);
  766. // Make sure the item prototype element isn't pointing to itself (the dynamic scroll box) or an ancestor,
  767. // otherwise this scroll box will spawn scroll boxes recursively ad infinitum.
  768. if (IsValidPrototype(m_itemPrototypeElement))
  769. {
  770. m_prototypeElement[ElementType::Item] = m_itemPrototypeElement;
  771. }
  772. else
  773. {
  774. if (m_itemPrototypeElement.IsValid())
  775. {
  776. AZ_Warning("UiDynamicScrollBoxComponent", false,
  777. "The prototype element is not safe for cloning. "
  778. "This scroll box's prototype element contains the scroll box itself which can result in recursively spawning scroll boxes. "
  779. "Please change the prototype element to a nonancestral entity.");
  780. }
  781. // Find the prototype element as the first child of the content element
  782. if (numChildren > 0)
  783. {
  784. AZ::EntityId prototypeEntityId;
  785. UiElementBus::EventResult(prototypeEntityId, contentEntityId, &UiElementBus::Events::GetChildEntityId, 0);
  786. m_prototypeElement[ElementType::Item] = prototypeEntityId;
  787. }
  788. }
  789. if (m_hasSections)
  790. {
  791. if (IsValidPrototype(m_headerPrototypeElement))
  792. {
  793. // Prototype header element is defined in properties
  794. m_prototypeElement[ElementType::SectionHeader] = m_headerPrototypeElement;
  795. }
  796. else if(m_headerPrototypeElement.IsValid())
  797. {
  798. AZ_Warning("UiDynamicScrollBoxComponent", false,
  799. "The selected prototype header is not safe for cloning. "
  800. "This scroll box's prototype header contains the scroll box itself which can result in recursively spawning scroll boxes. "
  801. "Please change the header to a nonancestral entity.");
  802. }
  803. }
  804. for (int i = 0; i < ElementType::NumElementTypes; i++)
  805. {
  806. m_isPrototypeElementNavigable[i] = false;
  807. m_prototypeElementSize[i] = 0.0f;
  808. if (m_prototypeElement[i].IsValid())
  809. {
  810. m_isPrototypeElementNavigable[i] = UiNavigationHelpers::IsElementInteractableAndNavigable(m_prototypeElement[i]);
  811. // Store the size of the item prototype element for future content element size calculations
  812. AZ::Vector2 prototypeElementSize(0.0f, 0.0f);
  813. UiTransformBus::EventResult(
  814. prototypeElementSize, m_prototypeElement[i], &UiTransformBus::Events::GetCanvasSpaceSizeNoScaleRotate);
  815. m_prototypeElementSize[i] = (m_isVertical ? prototypeElementSize.GetY() : prototypeElementSize.GetX());
  816. // Set anchors to top or left
  817. SetElementAnchors(m_prototypeElement[i]);
  818. }
  819. }
  820. AZ::Entity* contentEntity = GetContentEntity();
  821. if (contentEntity)
  822. {
  823. // Get the content entity's element component
  824. UiElementComponent* elementComponent = contentEntity->FindComponent<UiElementComponent>();
  825. AZ_Assert(elementComponent, "entity has no UiElementComponent");
  826. if (elementComponent)
  827. {
  828. // Remove any extra elements
  829. for (int i = numChildren - 1; i >= 0; i--)
  830. {
  831. AZ::EntityId entityId;
  832. UiElementBus::EventResult(entityId, contentEntityId, &UiElementBus::Events::GetChildEntityId, i);
  833. // Remove the child element
  834. elementComponent->RemoveChild(entityId);
  835. if (!IsPrototypeElement(entityId))
  836. {
  837. UiElementBus::Event(entityId, &UiElementBus::Events::DestroyElement);
  838. }
  839. }
  840. }
  841. // Get the content's parent
  842. AZ::EntityId contentParentEntityId;
  843. UiElementBus::EventResult(contentParentEntityId, contentEntityId, &UiElementBus::Events::GetParentEntityId);
  844. // Create an entity that will be used as the sticky header
  845. m_currentStickyHeader.m_elementIndex = -1;
  846. m_currentStickyHeader.m_indexInfo.m_sectionIndex = -1;
  847. m_currentStickyHeader.m_indexInfo.m_itemIndexInSection = -1;
  848. m_currentStickyHeader.m_type = ElementType::SectionHeader;
  849. if (m_hasSections && m_stickyHeaders && contentParentEntityId.IsValid())
  850. {
  851. m_currentStickyHeader.m_element = ClonePrototypeElement(ElementType::SectionHeader, contentParentEntityId);
  852. UiElementBus::Event(m_currentStickyHeader.m_element, &UiElementBus::Events::SetIsEnabled, false);
  853. }
  854. // Listen for canvas space rect changes of the content's parent
  855. if (contentParentEntityId.IsValid())
  856. {
  857. UiTransformChangeNotificationBus::Handler::BusConnect(contentParentEntityId);
  858. }
  859. // Listen to scrollbox scrolling events
  860. UiScrollBoxNotificationBus::Handler::BusConnect(GetEntityId());
  861. }
  862. m_listPreparedForDisplay = true;
  863. }
  864. ////////////////////////////////////////////////////////////////////////////////////////////////////
  865. AZ::Entity* UiDynamicScrollBoxComponent::GetContentEntity() const
  866. {
  867. AZ::Entity* contentEntity = nullptr;
  868. // Find the content element
  869. AZ::EntityId contentEntityId;
  870. UiScrollBoxBus::EventResult(contentEntityId, GetEntityId(), &UiScrollBoxBus::Events::GetContentEntity);
  871. if (contentEntityId.IsValid())
  872. {
  873. AZ::ComponentApplicationBus::BroadcastResult(contentEntity, &AZ::ComponentApplicationBus::Events::FindEntity, contentEntityId);
  874. }
  875. return contentEntity;
  876. }
  877. ////////////////////////////////////////////////////////////////////////////////////////////////////
  878. AZ::EntityId UiDynamicScrollBoxComponent::ClonePrototypeElement(ElementType elementType, AZ::EntityId parentEntityId) const
  879. {
  880. AZ::EntityId element;
  881. // Clone the prototype element and add it as a child of the specified parent (defaults to content entity)
  882. AZ::Entity* prototypeEntity = nullptr;
  883. AZ::ComponentApplicationBus::BroadcastResult(
  884. prototypeEntity, &AZ::ComponentApplicationBus::Events::FindEntity, m_prototypeElement[elementType]);
  885. if (prototypeEntity)
  886. {
  887. if (!parentEntityId.IsValid())
  888. {
  889. AZ::EntityId contentEntityId;
  890. UiScrollBoxBus::EventResult(contentEntityId, GetEntityId(), &UiScrollBoxBus::Events::GetContentEntity);
  891. parentEntityId = contentEntityId;
  892. }
  893. // Find the parent entity
  894. AZ::Entity* parentEntity = nullptr;
  895. AZ::ComponentApplicationBus::BroadcastResult(parentEntity, &AZ::ComponentApplicationBus::Events::FindEntity, parentEntityId);
  896. if (parentEntity)
  897. {
  898. AZ::EntityId canvasEntityId;
  899. UiElementBus::EventResult(canvasEntityId, GetEntityId(), &UiElementBus::Events::GetCanvasEntityId);
  900. AZ::Entity* clonedElement = nullptr;
  901. UiCanvasBus::EventResult(clonedElement, canvasEntityId, &UiCanvasBus::Events::CloneElement, prototypeEntity, parentEntity);
  902. if (clonedElement)
  903. {
  904. element = clonedElement->GetId();
  905. }
  906. }
  907. }
  908. return element;
  909. }
  910. ////////////////////////////////////////////////////////////////////////////////////////////////////
  911. bool UiDynamicScrollBoxComponent::IsPrototypeElement(AZ::EntityId entityId) const
  912. {
  913. for (int i = 0; i < ElementType::NumElementTypes; i++)
  914. {
  915. if (m_prototypeElement[i] == entityId)
  916. {
  917. return true;
  918. }
  919. }
  920. return false;
  921. }
  922. ////////////////////////////////////////////////////////////////////////////////////////////////////
  923. bool UiDynamicScrollBoxComponent::AllPrototypeElementsValid() const
  924. {
  925. return (m_prototypeElement[ElementType::Item].IsValid() && (!m_hasSections || m_prototypeElement[ElementType::SectionHeader].IsValid()));
  926. }
  927. ////////////////////////////////////////////////////////////////////////////////////////////////////
  928. bool UiDynamicScrollBoxComponent::AnyPrototypeElementsNavigable() const
  929. {
  930. return (m_isPrototypeElementNavigable[ElementType::Item] || (m_hasSections && m_isPrototypeElementNavigable[ElementType::SectionHeader]));
  931. }
  932. ////////////////////////////////////////////////////////////////////////////////////////////////////
  933. bool UiDynamicScrollBoxComponent::AnyElementTypesHaveVariableSize() const
  934. {
  935. return (m_variableElementSize[ElementType::Item] || (m_hasSections && m_variableElementSize[ElementType::SectionHeader]));
  936. }
  937. ////////////////////////////////////////////////////////////////////////////////////////////////////
  938. bool UiDynamicScrollBoxComponent::AnyElementTypesHaveEstimatedSizes() const
  939. {
  940. return (m_estimatedElementSize[ElementType::Item] > 0.0f || (m_hasSections && m_estimatedElementSize[ElementType::SectionHeader] > 0.0f));
  941. }
  942. ////////////////////////////////////////////////////////////////////////////////////////////////////
  943. bool UiDynamicScrollBoxComponent::AllElementTypesHaveEstimatedSizes() const
  944. {
  945. return (m_estimatedElementSize[ElementType::Item] > 0.0f && (!m_hasSections || m_estimatedElementSize[ElementType::SectionHeader] > 0.0f));
  946. }
  947. ////////////////////////////////////////////////////////////////////////////////////////////////////
  948. bool UiDynamicScrollBoxComponent::StickyHeadersEnabled() const
  949. {
  950. return (m_hasSections && m_stickyHeaders && m_currentStickyHeader.m_element.IsValid());
  951. }
  952. ////////////////////////////////////////////////////////////////////////////////////////////////////
  953. void UiDynamicScrollBoxComponent::ResizeContentToFitElements()
  954. {
  955. if (!AllPrototypeElementsValid())
  956. {
  957. return;
  958. }
  959. // Get the number of elements in the list
  960. if (!m_hasSections)
  961. {
  962. m_sections.clear();
  963. m_numElements = m_defaultNumElements;
  964. UiDynamicScrollBoxDataBus::EventResult(m_numElements, GetEntityId(), &UiDynamicScrollBoxDataBus::Events::GetNumElements);
  965. }
  966. else
  967. {
  968. int numSections = m_defaultNumSections;
  969. UiDynamicScrollBoxDataBus::EventResult(numSections, GetEntityId(), &UiDynamicScrollBoxDataBus::Events::GetNumSections);
  970. numSections = AZ::GetMax(numSections, 1);
  971. m_sections.clear();
  972. m_sections.reserve(numSections);
  973. m_numElements = 0;
  974. for (int i = 0; i < numSections; i++)
  975. {
  976. int numItems = m_defaultNumElements;
  977. UiDynamicScrollBoxDataBus::EventResult(numItems, GetEntityId(), &UiDynamicScrollBoxDataBus::Events::GetNumElementsInSection, i);
  978. Section section;
  979. section.m_index = i;
  980. section.m_numItems = numItems;
  981. section.m_headerElementIndex = m_numElements;
  982. m_sections.push_back(section);
  983. m_numElements += 1 + section.m_numItems;
  984. }
  985. }
  986. // Calculate new content size
  987. float newSize = 0.0f;
  988. if (!AnyElementTypesHaveVariableSize())
  989. {
  990. if (!m_hasSections)
  991. {
  992. newSize = m_numElements * m_prototypeElementSize[ElementType::Item];
  993. }
  994. else
  995. {
  996. int numHeaders = static_cast<int>(m_sections.size());
  997. int numItems = m_numElements - numHeaders;
  998. newSize = numHeaders * m_prototypeElementSize[ElementType::SectionHeader] + numItems * m_prototypeElementSize[ElementType::Item];
  999. }
  1000. }
  1001. else
  1002. {
  1003. // Some element types have variable element sizes
  1004. // Reset cached element info
  1005. m_cachedElementInfo.clear();
  1006. m_cachedElementInfo.reserve(m_numElements);
  1007. m_cachedElementInfo.insert(m_cachedElementInfo.end(), m_numElements, CachedElementInfo());
  1008. if (AllElementTypesHaveEstimatedSizes())
  1009. {
  1010. if (!m_hasSections)
  1011. {
  1012. newSize = m_numElements * m_estimatedElementSize[ElementType::Item];
  1013. }
  1014. else
  1015. {
  1016. int numHeaders = static_cast<int>(m_sections.size());
  1017. int numItems = m_numElements - numHeaders;
  1018. newSize = numHeaders * m_estimatedElementSize[ElementType::SectionHeader] + numItems * m_estimatedElementSize[ElementType::Item];
  1019. }
  1020. }
  1021. else
  1022. {
  1023. for (int i = 0; i < m_numElements; i++)
  1024. {
  1025. newSize += GetAndCacheVariableElementSize(i);
  1026. }
  1027. DisableElementsForAutoSizeCalculation();
  1028. }
  1029. m_averageElementSize = 0.0f;
  1030. m_numElementsUsedForAverage = 0;
  1031. UpdateAverageElementSize(m_numElements, newSize);
  1032. }
  1033. // Resize content element
  1034. ResizeContentElement(newSize);
  1035. }
  1036. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1037. void UiDynamicScrollBoxComponent::ResizeContentElement(float newSize) const
  1038. {
  1039. // Find the content element
  1040. AZ::EntityId contentEntityId;
  1041. UiScrollBoxBus::EventResult(contentEntityId, GetEntityId(), &UiScrollBoxBus::Events::GetContentEntity);
  1042. if (!contentEntityId.IsValid())
  1043. {
  1044. return;
  1045. }
  1046. // Get current content size
  1047. AZ::Vector2 curContentSize(0.0f, 0.0f);
  1048. UiTransformBus::EventResult(curContentSize, contentEntityId, &UiTransformBus::Events::GetCanvasSpaceSizeNoScaleRotate);
  1049. float curSize = m_isVertical ? curContentSize.GetY() : curContentSize.GetX();
  1050. if (newSize != curSize)
  1051. {
  1052. // Resize content element
  1053. UiTransform2dInterface::Offsets offsets;
  1054. UiTransform2dBus::EventResult(offsets, contentEntityId, &UiTransform2dBus::Events::GetOffsets);
  1055. AZ::Vector2 pivot;
  1056. UiTransformBus::EventResult(pivot, contentEntityId, &UiTransformBus::Events::GetPivot);
  1057. float sizeDiff = newSize - curSize;
  1058. if (m_isVertical)
  1059. {
  1060. offsets.m_top -= sizeDiff * pivot.GetY();
  1061. offsets.m_bottom += sizeDiff * (1.0f - pivot.GetY());
  1062. }
  1063. else
  1064. {
  1065. offsets.m_left -= sizeDiff * pivot.GetX();
  1066. offsets.m_right += sizeDiff * (1.0f - pivot.GetX());
  1067. }
  1068. UiTransform2dBus::Event(contentEntityId, &UiTransform2dBus::Events::SetOffsets, offsets);
  1069. }
  1070. }
  1071. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1072. void UiDynamicScrollBoxComponent::AdjustContentSizeAndScrollOffsetByDelta(float sizeDelta, float scrollDelta) const
  1073. {
  1074. // Find the content element
  1075. AZ::EntityId contentEntityId;
  1076. UiScrollBoxBus::EventResult(contentEntityId, GetEntityId(), &UiScrollBoxBus::Events::GetContentEntity);
  1077. if (!contentEntityId.IsValid())
  1078. {
  1079. return;
  1080. }
  1081. // Get content size
  1082. AZ::Vector2 contentSize(0.0f, 0.0f);
  1083. UiTransformBus::EventResult(contentSize, contentEntityId, &UiTransformBus::Events::GetCanvasSpaceSizeNoScaleRotate);
  1084. if (sizeDelta != 0.0f)
  1085. {
  1086. if (m_isVertical)
  1087. {
  1088. contentSize.SetY(contentSize.GetY() + sizeDelta);
  1089. }
  1090. else
  1091. {
  1092. contentSize.SetX(contentSize.GetX() + sizeDelta);
  1093. }
  1094. }
  1095. // Get scroll offset
  1096. AZ::Vector2 scrollOffset(0.0f, 0.0f);
  1097. UiScrollBoxBus::EventResult(scrollOffset, GetEntityId(), &UiScrollBoxBus::Events::GetScrollOffset);
  1098. if (scrollDelta != 0.0f)
  1099. {
  1100. if (m_isVertical)
  1101. {
  1102. scrollOffset.SetY(scrollOffset.GetY() + scrollDelta);
  1103. }
  1104. else
  1105. {
  1106. scrollOffset.SetX(scrollOffset.GetX() + scrollDelta);
  1107. }
  1108. }
  1109. UiScrollBoxBus::Event(GetEntityId(), &UiScrollBoxBus::Events::ChangeContentSizeAndScrollOffset, contentSize, scrollOffset);
  1110. }
  1111. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1112. float UiDynamicScrollBoxComponent::CalculateVariableElementSize(int index)
  1113. {
  1114. float size = 0.0f;
  1115. AZ_Assert(index >= 0 && index < m_numElements, "index %d out of range", index);
  1116. if (index < 0 || index >= m_numElements)
  1117. {
  1118. return size;
  1119. }
  1120. ElementType elementType = GetElementTypeAtIndex(index);
  1121. if (!m_autoCalculateElementSize[elementType])
  1122. {
  1123. if (m_isVertical)
  1124. {
  1125. if (!m_hasSections)
  1126. {
  1127. UiDynamicScrollBoxDataBus::EventResult(size, GetEntityId(), &UiDynamicScrollBoxDataBus::Events::GetElementHeight, index);
  1128. }
  1129. else
  1130. {
  1131. ElementIndexInfo elementIndexInfo = GetElementIndexInfoFromIndex(index);
  1132. if (elementType == ElementType::Item)
  1133. {
  1134. UiDynamicScrollBoxDataBus::EventResult(
  1135. size,
  1136. GetEntityId(),
  1137. &UiDynamicScrollBoxDataBus::Events::GetElementInSectionHeight,
  1138. elementIndexInfo.m_sectionIndex,
  1139. elementIndexInfo.m_itemIndexInSection);
  1140. }
  1141. else if (elementType == ElementType::SectionHeader)
  1142. {
  1143. UiDynamicScrollBoxDataBus::EventResult(
  1144. size, GetEntityId(), &UiDynamicScrollBoxDataBus::Events::GetSectionHeaderHeight, elementIndexInfo.m_sectionIndex);
  1145. }
  1146. else
  1147. {
  1148. AZ_Assert(false, "unknown element type");
  1149. }
  1150. }
  1151. }
  1152. else
  1153. {
  1154. if (!m_hasSections)
  1155. {
  1156. UiDynamicScrollBoxDataBus::EventResult(size, GetEntityId(), &UiDynamicScrollBoxDataBus::Events::GetElementWidth, index);
  1157. }
  1158. else
  1159. {
  1160. ElementIndexInfo elementIndexInfo = GetElementIndexInfoFromIndex(index);
  1161. if (elementType == ElementType::Item)
  1162. {
  1163. UiDynamicScrollBoxDataBus::EventResult(
  1164. size,
  1165. GetEntityId(),
  1166. &UiDynamicScrollBoxDataBus::Events::GetElementInSectionWidth,
  1167. elementIndexInfo.m_sectionIndex,
  1168. elementIndexInfo.m_itemIndexInSection);
  1169. }
  1170. else if (elementType == ElementType::SectionHeader)
  1171. {
  1172. UiDynamicScrollBoxDataBus::EventResult(
  1173. size, GetEntityId(), &UiDynamicScrollBoxDataBus::Events::GetSectionHeaderWidth, elementIndexInfo.m_sectionIndex);
  1174. }
  1175. else
  1176. {
  1177. AZ_Assert(false, "unknown element type");
  1178. }
  1179. }
  1180. }
  1181. }
  1182. else
  1183. {
  1184. AZ::EntityId elementForAutoSizeCalculation = GetElementForAutoSizeCalculation(elementType);
  1185. // Auto calculate the size of the element
  1186. AZ_Assert(elementForAutoSizeCalculation.IsValid(), "elementForAutoSizeCalculation is invalid");
  1187. // Notify listeners to setup this element for auto calculation
  1188. if (!m_hasSections)
  1189. {
  1190. UiDynamicScrollBoxElementNotificationBus::Event(
  1191. GetEntityId(),
  1192. &UiDynamicScrollBoxElementNotificationBus::Events::OnPrepareElementForSizeCalculation,
  1193. elementForAutoSizeCalculation,
  1194. index);
  1195. }
  1196. else
  1197. {
  1198. ElementIndexInfo elementIndexInfo = GetElementIndexInfoFromIndex(index);
  1199. if (elementType == ElementType::Item)
  1200. {
  1201. UiDynamicScrollBoxElementNotificationBus::Event(
  1202. GetEntityId(),
  1203. &UiDynamicScrollBoxElementNotificationBus::Events::OnPrepareElementInSectionForSizeCalculation,
  1204. elementForAutoSizeCalculation,
  1205. elementIndexInfo.m_sectionIndex,
  1206. elementIndexInfo.m_itemIndexInSection);
  1207. }
  1208. else if (elementType == ElementType::SectionHeader)
  1209. {
  1210. UiDynamicScrollBoxElementNotificationBus::Event(
  1211. GetEntityId(),
  1212. &UiDynamicScrollBoxElementNotificationBus::Events::OnPrepareSectionHeaderForSizeCalculation,
  1213. elementForAutoSizeCalculation,
  1214. elementIndexInfo.m_sectionIndex);
  1215. }
  1216. else
  1217. {
  1218. AZ_Assert(false, "unknown element type");
  1219. }
  1220. }
  1221. size = AutoCalculateElementSize(elementForAutoSizeCalculation);
  1222. }
  1223. // Cache the calculated size
  1224. m_cachedElementInfo[index].m_size = size;
  1225. return size;
  1226. }
  1227. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1228. float UiDynamicScrollBoxComponent::GetAndCacheVariableElementSize(int index)
  1229. {
  1230. float size = 0.0f;
  1231. AZ_Assert(index >= 0 && index < m_numElements, "index %d out of range", index);
  1232. if (index < 0 || index >= m_numElements)
  1233. {
  1234. return size;
  1235. }
  1236. if (m_cachedElementInfo[index].m_size >= 0.0f)
  1237. {
  1238. // Use the cached size
  1239. size = m_cachedElementInfo[index].m_size;
  1240. }
  1241. else
  1242. {
  1243. ElementType elementType = GetElementTypeAtIndex(index);
  1244. if (!m_variableElementSize[elementType])
  1245. {
  1246. // Use the prototype element size
  1247. size = m_prototypeElementSize[elementType];
  1248. // Cache the calculated size
  1249. m_cachedElementInfo[index].m_size = size;
  1250. // Cache the accumulated size
  1251. m_cachedElementInfo[index].m_accumulatedSize = GetVariableSizeElementOffset(index) + size;
  1252. }
  1253. else if (m_estimatedElementSize[elementType] > 0.0f)
  1254. {
  1255. // Uses the estimated element size
  1256. size = m_estimatedElementSize[elementType];
  1257. }
  1258. else
  1259. {
  1260. size = CalculateVariableElementSize(index);
  1261. // Cache the accumulated size
  1262. m_cachedElementInfo[index].m_accumulatedSize = GetVariableSizeElementOffset(index) + size;
  1263. }
  1264. }
  1265. return size;
  1266. }
  1267. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1268. float UiDynamicScrollBoxComponent::GetVariableElementSize(int index) const
  1269. {
  1270. float size = 0.0f;
  1271. AZ_Assert(index >= 0 && index < m_numElements, "index %d out of range", index);
  1272. if (index < 0 || index >= m_numElements)
  1273. {
  1274. return size;
  1275. }
  1276. if (m_cachedElementInfo[index].m_size >= 0.0f)
  1277. {
  1278. // Use the cached size
  1279. size = m_cachedElementInfo[index].m_size;
  1280. }
  1281. else
  1282. {
  1283. ElementType elementType = GetElementTypeAtIndex(index);
  1284. if (m_estimatedElementSize[elementType] > 0.0f)
  1285. {
  1286. // Uses the estimated element size
  1287. size = m_estimatedElementSize[elementType];
  1288. }
  1289. else
  1290. {
  1291. AZ_Assert(false, "GetVariableElementSize is being called before size is known");
  1292. }
  1293. }
  1294. return size;
  1295. }
  1296. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1297. int UiDynamicScrollBoxComponent::GetLastKnownAccumulatedSizeIndex(int index, int numElementsWithUnknownSizeOut[ElementType::NumElementTypes]) const
  1298. {
  1299. for (int i = 0; i < ElementType::NumElementTypes; i++)
  1300. {
  1301. numElementsWithUnknownSizeOut[i] = 0;
  1302. }
  1303. for (int i = index - 1; i >= 0; i--)
  1304. {
  1305. if (m_cachedElementInfo[i].m_accumulatedSize >= 0.0f)
  1306. {
  1307. return i;
  1308. }
  1309. ElementType elementType = GetElementTypeAtIndex(i);
  1310. ++numElementsWithUnknownSizeOut[elementType];
  1311. }
  1312. return -1;
  1313. }
  1314. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1315. float UiDynamicScrollBoxComponent::GetElementOffsetAtIndex(int index) const
  1316. {
  1317. float offset = 0.0f;
  1318. if (!AnyElementTypesHaveVariableSize())
  1319. {
  1320. offset = GetFixedSizeElementOffset(index);
  1321. }
  1322. else
  1323. {
  1324. offset = GetVariableSizeElementOffset(index);
  1325. }
  1326. return offset;
  1327. }
  1328. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1329. float UiDynamicScrollBoxComponent::GetFixedSizeElementOffset(int index) const
  1330. {
  1331. float offset = 0.0f;
  1332. if (!m_hasSections)
  1333. {
  1334. offset = m_prototypeElementSize[ElementType::Item] * index;
  1335. }
  1336. else
  1337. {
  1338. int numHeaders = 0;
  1339. int numItems = 0;
  1340. int numSections = static_cast<int>(m_sections.size());
  1341. if (numSections > 0)
  1342. {
  1343. if (index > m_sections[numSections - 1].m_headerElementIndex)
  1344. {
  1345. numHeaders = numSections;
  1346. }
  1347. else
  1348. {
  1349. for (int i = 0; i < numSections; i++)
  1350. {
  1351. if (index <= m_sections[i].m_headerElementIndex)
  1352. {
  1353. numHeaders = i;
  1354. break;
  1355. }
  1356. }
  1357. }
  1358. numItems = index - numHeaders;
  1359. }
  1360. offset = numHeaders * m_prototypeElementSize[ElementType::SectionHeader] + numItems * m_prototypeElementSize[ElementType::Item];
  1361. }
  1362. return offset;
  1363. }
  1364. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1365. float UiDynamicScrollBoxComponent::GetVariableSizeElementOffset(int index) const
  1366. {
  1367. float offset = 0.0f;
  1368. AZ_Assert(index >= 0 && index < m_numElements, "index %d out of range", index);
  1369. if (index < 0 || index >= m_numElements)
  1370. {
  1371. return offset;
  1372. }
  1373. if (index > 0)
  1374. {
  1375. if (m_cachedElementInfo[index - 1].m_accumulatedSize >= 0.0f)
  1376. {
  1377. offset = m_cachedElementInfo[index - 1].m_accumulatedSize;
  1378. }
  1379. else
  1380. {
  1381. // Calculate the accumulated size
  1382. int numElementsWithUnknownSizeOut[ElementType::NumElementTypes];
  1383. int lastKnownIndex = GetLastKnownAccumulatedSizeIndex(index, numElementsWithUnknownSizeOut);
  1384. offset = lastKnownIndex >= 0 ? m_cachedElementInfo[lastKnownIndex].m_accumulatedSize : 0.0f;
  1385. for (int i = 0; i < ElementType::NumElementTypes; i++)
  1386. {
  1387. offset += numElementsWithUnknownSizeOut[i] * m_estimatedElementSize[i];
  1388. }
  1389. }
  1390. }
  1391. return offset;
  1392. }
  1393. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1394. void UiDynamicScrollBoxComponent::UpdateAverageElementSize(int numAddedElements, float sizeDelta)
  1395. {
  1396. float curTotalSize = m_averageElementSize * m_numElementsUsedForAverage;
  1397. m_numElementsUsedForAverage += numAddedElements;
  1398. m_averageElementSize = (m_numElementsUsedForAverage > 0) ? (AZ::GetMax(curTotalSize + sizeDelta, 0.0f) / m_numElementsUsedForAverage) : 0.0f;
  1399. }
  1400. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1401. void UiDynamicScrollBoxComponent::ClearDisplayedElements()
  1402. {
  1403. for (const auto& e : m_displayedElements)
  1404. {
  1405. ElementType elementType = e.m_type;
  1406. m_recycledElements[elementType].push_front(e.m_element);
  1407. // Disable element
  1408. UiElementBus::Event(e.m_element, &UiElementBus::Events::SetIsEnabled, false);
  1409. }
  1410. m_displayedElements.clear();
  1411. m_firstDisplayedElementIndex = -1;
  1412. m_lastDisplayedElementIndex = -1;
  1413. m_firstVisibleElementIndex = -1;
  1414. m_lastVisibleElementIndex = -1;
  1415. }
  1416. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1417. AZ::EntityId UiDynamicScrollBoxComponent::FindDisplayedElementWithIndex(int index) const
  1418. {
  1419. AZ::EntityId elementId;
  1420. for (const auto& e : m_displayedElements)
  1421. {
  1422. if (e.m_elementIndex == index)
  1423. {
  1424. elementId = e.m_element;
  1425. break;
  1426. }
  1427. }
  1428. return elementId;
  1429. }
  1430. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1431. float UiDynamicScrollBoxComponent::GetVisibleAreaSize() const
  1432. {
  1433. float visibleAreaSize = 0.0f;
  1434. // Find the content element
  1435. AZ::EntityId contentEntityId;
  1436. UiScrollBoxBus::EventResult(contentEntityId, GetEntityId(), &UiScrollBoxBus::Events::GetContentEntity);
  1437. if (!contentEntityId.IsValid())
  1438. {
  1439. return visibleAreaSize;
  1440. }
  1441. // Get content's parent
  1442. AZ::EntityId contentParentEntityId;
  1443. UiElementBus::EventResult(contentParentEntityId, contentEntityId, &UiElementBus::Events::GetParentEntityId);
  1444. if (!contentParentEntityId.IsValid())
  1445. {
  1446. return visibleAreaSize;
  1447. }
  1448. // Get content parent's size in canvas space
  1449. AZ::Vector2 contentParentSize(0.0f, 0.0f);
  1450. UiTransformBus::EventResult(contentParentSize, contentParentEntityId, &UiTransformBus::Events::GetCanvasSpaceSizeNoScaleRotate);
  1451. if (m_isVertical)
  1452. {
  1453. visibleAreaSize = contentParentSize.GetY();
  1454. }
  1455. else
  1456. {
  1457. visibleAreaSize = contentParentSize.GetX();
  1458. }
  1459. return visibleAreaSize;
  1460. }
  1461. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1462. bool UiDynamicScrollBoxComponent::AreAnyElementsVisible(AZ::Vector2& visibleContentBoundsOut) const
  1463. {
  1464. if (m_numElements == 0)
  1465. {
  1466. return false;
  1467. }
  1468. // Find the content element
  1469. AZ::EntityId contentEntityId;
  1470. UiScrollBoxBus::EventResult(contentEntityId, GetEntityId(), &UiScrollBoxBus::Events::GetContentEntity);
  1471. if (!contentEntityId.IsValid())
  1472. {
  1473. return false;
  1474. }
  1475. // Get content's parent
  1476. AZ::EntityId contentParentEntityId;
  1477. UiElementBus::EventResult(contentParentEntityId, contentEntityId, &UiElementBus::Events::GetParentEntityId);
  1478. if (!contentParentEntityId.IsValid())
  1479. {
  1480. return false;
  1481. }
  1482. // Get content's rect in canvas space
  1483. UiTransformInterface::Rect contentRect;
  1484. UiTransformBus::Event(contentEntityId, &UiTransformBus::Events::GetCanvasSpaceRectNoScaleRotate, contentRect);
  1485. // Get content parent's rect in canvas space
  1486. UiTransformInterface::Rect parentRect;
  1487. UiTransformBus::Event(contentParentEntityId, &UiTransformBus::Events::GetCanvasSpaceRectNoScaleRotate, parentRect);
  1488. // Check if any items are visible
  1489. AZ::Vector2 minA(contentRect.left, contentRect.top);
  1490. AZ::Vector2 maxA(contentRect.right, contentRect.bottom);
  1491. AZ::Vector2 minB(parentRect.left, parentRect.top);
  1492. AZ::Vector2 maxB(parentRect.right, parentRect.bottom);
  1493. bool boxesIntersect = true;
  1494. if (maxA.GetX() < minB.GetX() || // a is left of b
  1495. minA.GetX() > maxB.GetX() || // a is right of b
  1496. maxA.GetY() < minB.GetY() || // a is above b
  1497. minA.GetY() > maxB.GetY()) // a is below b
  1498. {
  1499. boxesIntersect = false; // no overlap
  1500. }
  1501. if (boxesIntersect)
  1502. {
  1503. // Set visible content bounds
  1504. if (m_isVertical)
  1505. {
  1506. // Set top offset
  1507. visibleContentBoundsOut.SetX(AZ::GetMax(parentRect.top - contentRect.top, 0.0f));
  1508. // Set bottom offset
  1509. visibleContentBoundsOut.SetY(AZ::GetMin(parentRect.bottom, contentRect.bottom) - contentRect.top);
  1510. }
  1511. else
  1512. {
  1513. // Set left offset
  1514. visibleContentBoundsOut.SetX(AZ::GetMax(parentRect.left - contentRect.left, 0.0f));
  1515. // Set right offset
  1516. visibleContentBoundsOut.SetY(AZ::GetMin(parentRect.right, contentRect.right) - contentRect.left);
  1517. }
  1518. }
  1519. return boxesIntersect;
  1520. }
  1521. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1522. void UiDynamicScrollBoxComponent::UpdateElementVisibility(bool keepAtEndIfWasAtEnd)
  1523. {
  1524. // Calculate which elements are visible
  1525. int firstVisibleElementIndex = -1;
  1526. int lastVisibleElementIndex = -1;
  1527. int firstDisplayedElementIndex = -1;
  1528. int lastDisplayedElementIndex = -1;
  1529. int firstDisplayedElementIndexWithSizeChange = -1;
  1530. float totalElementSizeChange = 0.0f;
  1531. float scrollChange = 0.0f;
  1532. AZ::Vector2 visibleContentBounds(0.0f, 0.0f);
  1533. bool elementsVisible = AreAnyElementsVisible(visibleContentBounds);
  1534. if (elementsVisible)
  1535. {
  1536. CalculateVisibleElementIndices(keepAtEndIfWasAtEnd,
  1537. visibleContentBounds,
  1538. firstVisibleElementIndex,
  1539. lastVisibleElementIndex,
  1540. firstDisplayedElementIndex,
  1541. lastDisplayedElementIndex,
  1542. firstDisplayedElementIndexWithSizeChange,
  1543. totalElementSizeChange,
  1544. scrollChange);
  1545. }
  1546. m_lastCalculatedVisibleContentOffset = visibleContentBounds.GetX();
  1547. if (totalElementSizeChange != 0.0f)
  1548. {
  1549. m_lastCalculatedVisibleContentOffset += CalculateContentBeginningDeltaAfterSizeChange(totalElementSizeChange);
  1550. }
  1551. if (StickyHeadersEnabled())
  1552. {
  1553. UpdateStickyHeader(firstVisibleElementIndex, lastVisibleElementIndex, m_lastCalculatedVisibleContentOffset);
  1554. }
  1555. // Remove the elements that are no longer being displayed
  1556. m_displayedElements.remove_if(
  1557. [this, firstDisplayedElementIndex, lastDisplayedElementIndex](const DisplayedElement& e)
  1558. {
  1559. if ((firstDisplayedElementIndex < 0) || (e.m_elementIndex < firstDisplayedElementIndex) || (e.m_elementIndex > lastDisplayedElementIndex))
  1560. {
  1561. // This element is no longer being displayed, move it to the recycled elements list
  1562. m_recycledElements[e.m_type].push_front(e.m_element);
  1563. // Disable element
  1564. UiElementBus::Event(e.m_element, &UiElementBus::Events::SetIsEnabled, false);
  1565. // Remove element from the displayed element list
  1566. return true;
  1567. }
  1568. else
  1569. {
  1570. return false;
  1571. }
  1572. }
  1573. );
  1574. // Add the newly displayed elements
  1575. if (firstDisplayedElementIndex >= 0)
  1576. {
  1577. for (int i = firstDisplayedElementIndex; i <= lastDisplayedElementIndex; i++)
  1578. {
  1579. if (!IsElementDisplayedAtIndex(i))
  1580. {
  1581. ElementType elementType = GetElementTypeAtIndex(i);
  1582. ElementIndexInfo elementIndexInfo = GetElementIndexInfoFromIndex(i);
  1583. AZ::EntityId element = GetElementForDisplay(elementType);
  1584. DisplayedElement elementEntry;
  1585. elementEntry.m_element = element;
  1586. elementEntry.m_elementIndex = i;
  1587. elementEntry.m_indexInfo = elementIndexInfo;
  1588. elementEntry.m_type = elementType;
  1589. m_displayedElements.push_front(elementEntry);
  1590. if (m_variableElementSize[elementType])
  1591. {
  1592. SizeVariableElementAtIndex(element, i);
  1593. }
  1594. PositionElementAtIndex(element, i);
  1595. // Notify listeners that this element is about to be displayed
  1596. if (!m_hasSections)
  1597. {
  1598. UiDynamicScrollBoxElementNotificationBus::Event(
  1599. GetEntityId(), &UiDynamicScrollBoxElementNotificationBus::Events::OnElementBecomingVisible, element, i);
  1600. }
  1601. else
  1602. {
  1603. if (elementType == ElementType::Item)
  1604. {
  1605. UiDynamicScrollBoxElementNotificationBus::Event(
  1606. GetEntityId(),
  1607. &UiDynamicScrollBoxElementNotificationBus::Events::OnElementInSectionBecomingVisible,
  1608. element,
  1609. elementIndexInfo.m_sectionIndex,
  1610. elementIndexInfo.m_itemIndexInSection);
  1611. }
  1612. else if (elementType == ElementType::SectionHeader)
  1613. {
  1614. UiDynamicScrollBoxElementNotificationBus::Event(
  1615. GetEntityId(),
  1616. &UiDynamicScrollBoxElementNotificationBus::Events::OnSectionHeaderBecomingVisible,
  1617. element,
  1618. elementIndexInfo.m_sectionIndex);
  1619. }
  1620. else
  1621. {
  1622. AZ_Assert(false, "unknown element type");
  1623. }
  1624. }
  1625. }
  1626. else
  1627. {
  1628. if (firstDisplayedElementIndexWithSizeChange >= 0 && firstDisplayedElementIndexWithSizeChange <= i)
  1629. {
  1630. AZ::EntityId element = FindDisplayedElementWithIndex(i);
  1631. PositionElementAtIndex(element, i);
  1632. }
  1633. }
  1634. }
  1635. }
  1636. m_firstVisibleElementIndex = firstVisibleElementIndex;
  1637. m_lastVisibleElementIndex = lastVisibleElementIndex;
  1638. m_firstDisplayedElementIndex = firstDisplayedElementIndex;
  1639. m_lastDisplayedElementIndex = lastDisplayedElementIndex;
  1640. if (totalElementSizeChange != 0.0f || scrollChange != 0.0f)
  1641. {
  1642. AdjustContentSizeAndScrollOffsetByDelta(totalElementSizeChange, scrollChange);
  1643. }
  1644. }
  1645. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1646. void UiDynamicScrollBoxComponent::CalculateVisibleElementIndices(bool keepAtEndIfWasAtEnd,
  1647. const AZ::Vector2& visibleContentBounds,
  1648. int& firstVisibleElementIndexOut,
  1649. int& lastVisibleElementIndexOut,
  1650. int& firstDisplayedElementIndexOut,
  1651. int& lastDisplayedElementIndexOut,
  1652. int& firstDisplayedElementIndexWithSizeChangeOut,
  1653. float& totalElementSizeChangeOut,
  1654. float& scrollChangeOut)
  1655. {
  1656. firstVisibleElementIndexOut = -1;
  1657. lastVisibleElementIndexOut = -1;
  1658. firstDisplayedElementIndexOut = -1;
  1659. lastDisplayedElementIndexOut = -1;
  1660. firstDisplayedElementIndexWithSizeChangeOut = -1;
  1661. totalElementSizeChangeOut = 0.0f;
  1662. scrollChangeOut = 0.0f;
  1663. if (!AllPrototypeElementsValid())
  1664. {
  1665. return;
  1666. }
  1667. bool addedExtraElementsForNavigation = false;
  1668. if (!AnyElementTypesHaveVariableSize())
  1669. {
  1670. // All elements are the same size
  1671. FindVisibleElementIndicesForFixedSizes(visibleContentBounds, firstVisibleElementIndexOut, lastVisibleElementIndexOut);
  1672. }
  1673. else
  1674. {
  1675. // Elements vary in size
  1676. if (AnyElementTypesHaveEstimatedSizes())
  1677. {
  1678. // We may not have the real sizes of all the elements yet
  1679. // Find the first elment index that's visible and that will remain in the same position
  1680. int visibleElementIndex = -1;
  1681. bool keepAtEnd = keepAtEndIfWasAtEnd && IsScrolledToEnd();
  1682. if (keepAtEnd)
  1683. {
  1684. visibleElementIndex = m_numElements - 1;
  1685. }
  1686. else
  1687. {
  1688. visibleElementIndex = FindVisibleElementIndexToRemainInPlace(visibleContentBounds);
  1689. }
  1690. // Calculate the first and last visible elements without moving the beginning (top or left) of the specified visible element index
  1691. CalculateVisibleElementIndicesFromVisibleElementIndex(visibleElementIndex,
  1692. visibleContentBounds,
  1693. keepAtEnd,
  1694. firstVisibleElementIndexOut,
  1695. lastVisibleElementIndexOut,
  1696. firstDisplayedElementIndexOut,
  1697. lastDisplayedElementIndexOut,
  1698. firstDisplayedElementIndexWithSizeChangeOut,
  1699. totalElementSizeChangeOut,
  1700. scrollChangeOut);
  1701. addedExtraElementsForNavigation = true;
  1702. }
  1703. else
  1704. {
  1705. // We have the real sizes of all the elements
  1706. // Estimate a first visible element index
  1707. int estimatedFirstVisibleElementIndex = EstimateFirstVisibleElementIndex(visibleContentBounds);
  1708. // Look for the real new first visible element index
  1709. float curElementEnd = 0.0f;
  1710. firstVisibleElementIndexOut = FindFirstVisibleElementIndex(estimatedFirstVisibleElementIndex, visibleContentBounds, curElementEnd);
  1711. // Now find the last visible element index
  1712. lastVisibleElementIndexOut = firstVisibleElementIndexOut;
  1713. while (curElementEnd < visibleContentBounds.GetY() && lastVisibleElementIndexOut < m_numElements - 1)
  1714. {
  1715. ++lastVisibleElementIndexOut;
  1716. curElementEnd += GetVariableElementSize(lastVisibleElementIndexOut);
  1717. }
  1718. }
  1719. }
  1720. if (!addedExtraElementsForNavigation)
  1721. {
  1722. firstDisplayedElementIndexOut = firstVisibleElementIndexOut;
  1723. lastDisplayedElementIndexOut = lastVisibleElementIndexOut;
  1724. AddExtraElementsForNavigation(firstDisplayedElementIndexOut, lastDisplayedElementIndexOut);
  1725. }
  1726. }
  1727. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1728. void UiDynamicScrollBoxComponent::UpdateStickyHeader(int firstVisibleElementIndex, int lastVisibleElementIndex, float visibleContentBeginning)
  1729. {
  1730. // Find which header should currently be sticky
  1731. if (firstVisibleElementIndex >= 0)
  1732. {
  1733. ElementIndexInfo firstVisibleElementIndexInfo = GetElementIndexInfoFromIndex(firstVisibleElementIndex);
  1734. int newStickyHeaderElementIndex = m_sections[firstVisibleElementIndexInfo.m_sectionIndex].m_headerElementIndex;
  1735. if (newStickyHeaderElementIndex != m_currentStickyHeader.m_elementIndex)
  1736. {
  1737. if (m_currentStickyHeader.m_elementIndex < 0)
  1738. {
  1739. UiElementBus::Event(m_currentStickyHeader.m_element, &UiElementBus::Events::SetIsEnabled, true);
  1740. }
  1741. m_currentStickyHeader.m_elementIndex = newStickyHeaderElementIndex;
  1742. m_currentStickyHeader.m_indexInfo.m_sectionIndex = firstVisibleElementIndexInfo.m_sectionIndex;
  1743. if (m_variableElementSize[ElementType::SectionHeader])
  1744. {
  1745. SizeVariableElementAtIndex(m_currentStickyHeader.m_element, m_currentStickyHeader.m_elementIndex);
  1746. }
  1747. UiDynamicScrollBoxElementNotificationBus::Event(
  1748. GetEntityId(),
  1749. &UiDynamicScrollBoxElementNotificationBus::Events::OnSectionHeaderBecomingVisible,
  1750. m_currentStickyHeader.m_element,
  1751. m_currentStickyHeader.m_indexInfo.m_sectionIndex);
  1752. }
  1753. float stickyHeaderOffset = 0.0f;
  1754. // Check if the current sticky header is being pushed out of the way by another visible header
  1755. int firstVisibleHeaderIndex = FindFirstVisibleHeaderIndex(firstVisibleElementIndex, lastVisibleElementIndex, m_currentStickyHeader.m_elementIndex);
  1756. if (firstVisibleHeaderIndex >= 0)
  1757. {
  1758. // Get the beginning of the first visible header
  1759. float firstVisibleHeaderBeginning = GetElementOffsetAtIndex(firstVisibleHeaderIndex);
  1760. // Get the end of the current sticky header
  1761. float stickyHeaderSize = !m_variableElementSize[ElementType::SectionHeader] ? m_prototypeElementSize[ElementType::SectionHeader] : GetVariableElementSize(m_currentStickyHeader.m_elementIndex);
  1762. float stickyHeaderEnd = visibleContentBeginning + stickyHeaderSize;
  1763. // Adjust sticky header offset
  1764. if (firstVisibleHeaderBeginning < stickyHeaderEnd)
  1765. {
  1766. stickyHeaderOffset = firstVisibleHeaderBeginning - stickyHeaderEnd;
  1767. }
  1768. }
  1769. SetElementOffsets(m_currentStickyHeader.m_element, stickyHeaderOffset);
  1770. }
  1771. else
  1772. {
  1773. m_currentStickyHeader.m_elementIndex = -1;
  1774. m_currentStickyHeader.m_indexInfo.m_sectionIndex = -1;
  1775. // Hide the sticky header
  1776. UiElementBus::Event(m_currentStickyHeader.m_element, &UiElementBus::Events::SetIsEnabled, false);
  1777. }
  1778. }
  1779. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1780. int UiDynamicScrollBoxComponent::FindFirstVisibleHeaderIndex(int firstVisibleElementIndex, int lastVisibleElementIndex, int excludeIndex)
  1781. {
  1782. int firstVisibleHeaderIndex = -1;
  1783. for (int i = firstVisibleElementIndex; i <= lastVisibleElementIndex; i++)
  1784. {
  1785. if (i != excludeIndex && GetElementTypeAtIndex(i) == ElementType::SectionHeader)
  1786. {
  1787. firstVisibleHeaderIndex = i;
  1788. break;
  1789. }
  1790. }
  1791. return firstVisibleHeaderIndex;
  1792. }
  1793. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1794. void UiDynamicScrollBoxComponent::FindVisibleElementIndicesForFixedSizes(const AZ::Vector2& visibleContentBounds,
  1795. int& firstVisibleElementIndexOut,
  1796. int& lastVisibleElementIndexOut) const
  1797. {
  1798. float itemSize = m_prototypeElementSize[ElementType::Item];
  1799. float beginningVisibleOffset = visibleContentBounds.GetX();
  1800. float endVisibleOffset = visibleContentBounds.GetY();
  1801. if (!m_hasSections)
  1802. {
  1803. if (itemSize > 0.0f)
  1804. {
  1805. // Calculate first visible element index
  1806. firstVisibleElementIndexOut = max(static_cast<int>(ceil(beginningVisibleOffset / itemSize)) - 1, 0);
  1807. // Calculate last visible element index
  1808. lastVisibleElementIndexOut = static_cast<int>(ceil(endVisibleOffset / itemSize)) - 1;
  1809. int lastElementIndex = max(m_numElements - 1, 0);
  1810. Limit(lastVisibleElementIndexOut, 0, lastElementIndex);
  1811. }
  1812. }
  1813. else
  1814. {
  1815. float headerSize = m_prototypeElementSize[ElementType::SectionHeader];
  1816. if (itemSize > 0.0f || headerSize > 0.0f)
  1817. {
  1818. // Calculate first and last visible element indices
  1819. float curElementOffset = 0.0f;
  1820. int curSectionIndex = 0;
  1821. for (int i = 0; i < 2; i++)
  1822. {
  1823. int& visibleElementIndex = (i == 0) ? firstVisibleElementIndexOut : lastVisibleElementIndexOut;
  1824. float visibleOffset = (i == 0) ? beginningVisibleOffset : endVisibleOffset;
  1825. for (; curSectionIndex < m_sections.size(); curSectionIndex++)
  1826. {
  1827. float headerElementEnd = curElementOffset + headerSize;
  1828. if (headerElementEnd >= visibleOffset)
  1829. {
  1830. visibleElementIndex = m_sections[curSectionIndex].m_headerElementIndex;
  1831. break;
  1832. }
  1833. else
  1834. {
  1835. float sectionEnd = headerElementEnd + itemSize * m_sections[curSectionIndex].m_numItems;
  1836. if (sectionEnd >= visibleOffset)
  1837. {
  1838. int numItems = 0;
  1839. if (itemSize > 0.0f)
  1840. {
  1841. numItems = static_cast<int>(ceil((visibleOffset - headerElementEnd) / itemSize));
  1842. }
  1843. visibleElementIndex = m_sections[curSectionIndex].m_headerElementIndex + numItems;
  1844. break;
  1845. }
  1846. else if (curSectionIndex == m_sections.size() - 1)
  1847. {
  1848. visibleElementIndex = m_sections[curSectionIndex].m_headerElementIndex + m_sections[curSectionIndex].m_numItems;
  1849. break;
  1850. }
  1851. curElementOffset = sectionEnd;
  1852. }
  1853. }
  1854. }
  1855. }
  1856. }
  1857. }
  1858. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1859. int UiDynamicScrollBoxComponent::FindVisibleElementIndexToRemainInPlace(const AZ::Vector2& visibleContentBounds) const
  1860. {
  1861. int visibleElementIndex = -1;
  1862. // Try and find the first previously visible element that's still visible
  1863. int firstPrevVisibleIndexStillVisible = -1;
  1864. if (m_firstVisibleElementIndex >= 0)
  1865. {
  1866. // Check if any of the previously visible elements are still visible
  1867. float prevFirstVisibleBeginning = GetVariableSizeElementOffset(m_firstVisibleElementIndex);
  1868. float prevLastVisibleEnd = GetVariableSizeElementOffset(m_lastVisibleElementIndex) + GetVariableElementSize(m_lastVisibleElementIndex);
  1869. if (!(prevFirstVisibleBeginning > visibleContentBounds.GetY() || prevLastVisibleEnd < visibleContentBounds.GetX()))
  1870. {
  1871. // Find the first previously visible element that's still visible
  1872. for (int index = m_firstVisibleElementIndex; index <= m_lastVisibleElementIndex; index++)
  1873. {
  1874. if (GetVariableSizeElementOffset(index) + GetVariableElementSize(index) >= visibleContentBounds.GetX())
  1875. {
  1876. firstPrevVisibleIndexStillVisible = index;
  1877. break;
  1878. }
  1879. }
  1880. }
  1881. }
  1882. if (firstPrevVisibleIndexStillVisible >= 0)
  1883. {
  1884. visibleElementIndex = firstPrevVisibleIndexStillVisible;
  1885. }
  1886. else
  1887. {
  1888. // No previously visible elements are still visible, so find the first element that's about to become visible
  1889. // Estimate a first visible element index
  1890. int estimatedFirstVisibleElementIndex = EstimateFirstVisibleElementIndex(visibleContentBounds);
  1891. // Look for the real new first visible element index
  1892. float firstVisibleElementEnd = 0.0f;
  1893. visibleElementIndex = FindFirstVisibleElementIndex(estimatedFirstVisibleElementIndex, visibleContentBounds, firstVisibleElementEnd);
  1894. // We actually want the first visible element who's beginning (top or left) is visible if we don't know the first visible element's real size.
  1895. // This is so that we don't end up having to calculate the size of more elements if the real size of the first visible
  1896. // element ends up being smaller than the estimated size
  1897. if (m_cachedElementInfo[visibleElementIndex].m_size < 0.0f && visibleElementIndex < m_numElements - 1)
  1898. {
  1899. float firstVisibleElementBeginning = firstVisibleElementEnd - GetVariableElementSize(visibleElementIndex);
  1900. if (firstVisibleElementBeginning < visibleContentBounds.GetX() && firstVisibleElementEnd < visibleContentBounds.GetY())
  1901. {
  1902. ++visibleElementIndex;
  1903. }
  1904. }
  1905. }
  1906. return visibleElementIndex;
  1907. }
  1908. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1909. void UiDynamicScrollBoxComponent::AddExtraElementsForNavigation(int& firstDisplayedElementIndexOut, int& lastDisplayedElementIndexOut) const
  1910. {
  1911. if (AnyPrototypeElementsNavigable())
  1912. {
  1913. if (firstDisplayedElementIndexOut > 0)
  1914. {
  1915. --firstDisplayedElementIndexOut;
  1916. if (m_hasSections)
  1917. {
  1918. int newFirstDisplayedElementIndex = firstDisplayedElementIndexOut;
  1919. while (!m_isPrototypeElementNavigable[GetElementTypeAtIndex(newFirstDisplayedElementIndex)] && newFirstDisplayedElementIndex >= 0)
  1920. {
  1921. --newFirstDisplayedElementIndex;
  1922. }
  1923. if (newFirstDisplayedElementIndex >= 0)
  1924. {
  1925. firstDisplayedElementIndexOut = newFirstDisplayedElementIndex;
  1926. }
  1927. }
  1928. }
  1929. if (lastDisplayedElementIndexOut > -1 && lastDisplayedElementIndexOut < m_numElements - 1)
  1930. {
  1931. ++lastDisplayedElementIndexOut;
  1932. if (m_hasSections)
  1933. {
  1934. int newLastDisplayedElementIndex = lastDisplayedElementIndexOut;
  1935. while (!m_isPrototypeElementNavigable[GetElementTypeAtIndex(newLastDisplayedElementIndex)] && newLastDisplayedElementIndex < m_numElements)
  1936. {
  1937. ++newLastDisplayedElementIndex;
  1938. }
  1939. if (newLastDisplayedElementIndex < m_numElements)
  1940. {
  1941. lastDisplayedElementIndexOut = newLastDisplayedElementIndex;
  1942. }
  1943. }
  1944. }
  1945. }
  1946. }
  1947. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1948. int UiDynamicScrollBoxComponent::EstimateFirstVisibleElementIndex(const AZ::Vector2& visibleContentBounds) const
  1949. {
  1950. // Estimate an index size that will be close to the new first visible element index
  1951. int estimatedElementIndex = 0;
  1952. if (m_averageElementSize > 0.0f)
  1953. {
  1954. if (m_firstVisibleElementIndex >= 0)
  1955. {
  1956. // Check how much scrolling has occurred
  1957. float scrollDelta = visibleContentBounds.GetX() - m_lastCalculatedVisibleContentOffset;
  1958. // Estimate the number of elements within the scroll delta
  1959. int estimatedElementIndexOffset = max(static_cast<int>(ceil(fabs(scrollDelta / m_averageElementSize))) - 1, 0);
  1960. estimatedElementIndex = m_firstVisibleElementIndex + (scrollDelta > 0.0f ? estimatedElementIndexOffset : -estimatedElementIndexOffset);
  1961. }
  1962. else
  1963. {
  1964. estimatedElementIndex = max(static_cast<int>(ceil(visibleContentBounds.GetX() / m_averageElementSize)) - 1, 0);
  1965. }
  1966. }
  1967. estimatedElementIndex = AZ::GetClamp(estimatedElementIndex, 0, m_numElements - 1);
  1968. return estimatedElementIndex;
  1969. }
  1970. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1971. int UiDynamicScrollBoxComponent::FindFirstVisibleElementIndex(int estimatedIndex, const AZ::Vector2& visibleContentBounds, float& firstVisibleElementEndOut) const
  1972. {
  1973. int curElementIndex = estimatedIndex;
  1974. float curElementPos = GetVariableSizeElementOffset(curElementIndex);
  1975. if (curElementPos <= visibleContentBounds.GetX())
  1976. {
  1977. // Traverse down to find the real new first visible element index
  1978. curElementPos += GetVariableElementSize(curElementIndex);
  1979. while (curElementPos < visibleContentBounds.GetX() && curElementIndex < m_numElements - 1)
  1980. {
  1981. ++curElementIndex;
  1982. curElementPos += GetVariableElementSize(curElementIndex);
  1983. }
  1984. }
  1985. else
  1986. {
  1987. // Traverse up to find the real new first visible element index
  1988. while (curElementPos > visibleContentBounds.GetX() && curElementIndex > 0)
  1989. {
  1990. --curElementIndex;
  1991. curElementPos -= GetVariableElementSize(curElementIndex);
  1992. }
  1993. curElementPos += GetVariableElementSize(curElementIndex);
  1994. }
  1995. firstVisibleElementEndOut = curElementPos;
  1996. return curElementIndex;
  1997. }
  1998. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1999. void UiDynamicScrollBoxComponent::CalculateVisibleSpaceBeforeAndAfterElement(int visibleElementIndex,
  2000. bool keepAtEnd,
  2001. float visibleAreaBeginning,
  2002. float& spaceLeftBeforeOut,
  2003. float& spaceLeftAfterOut) const
  2004. {
  2005. float visibleAreaSize = GetVisibleAreaSize();
  2006. // Calculate space left in the visible area
  2007. spaceLeftBeforeOut = 0.0f;
  2008. spaceLeftAfterOut = 0.0f;
  2009. if (keepAtEnd)
  2010. {
  2011. spaceLeftAfterOut = 0.0f;
  2012. spaceLeftBeforeOut = AZ::GetMax(visibleAreaSize - GetVariableElementSize(visibleElementIndex), 0.0f);
  2013. }
  2014. else
  2015. {
  2016. float visibleElementBeginning = GetVariableSizeElementOffset(visibleElementIndex);
  2017. float visibleElementEnd = visibleElementBeginning + GetVariableElementSize(visibleElementIndex);
  2018. spaceLeftBeforeOut = AZ::GetMax(visibleElementBeginning - visibleAreaBeginning, 0.0f);
  2019. spaceLeftAfterOut = AZ::GetMax(visibleAreaSize - (visibleElementEnd - visibleAreaBeginning), 0.0f);
  2020. }
  2021. }
  2022. ////////////////////////////////////////////////////////////////////////////////////////////////////
  2023. void UiDynamicScrollBoxComponent::CalculateVisibleElementIndicesFromVisibleElementIndex(int visibleElementIndex,
  2024. const AZ::Vector2& visibleContentBound,
  2025. bool keepAtEnd,
  2026. int& firstVisibleElementIndexOut,
  2027. int& lastVisibleElementIndexOut,
  2028. int& firstDisplayedElementIndexOut,
  2029. int& lastDisplayedElementIndexOut,
  2030. int& firstDisplayedElementIndexWithSizeChangeOut,
  2031. float& totalElementSizechangeOut,
  2032. float& scrollChangeOut)
  2033. {
  2034. // From the current element index that we know is going to be at least partly visible,
  2035. // traverse up and down to find the real first and last visible element indices
  2036. // Track the total change in element size
  2037. float totalSizeChange = 0.0f;
  2038. // Track the total change in size of elements that are positioned before the passed in
  2039. // visible element index who's beginning (top or left) will remain in the same position
  2040. float totalSizeChangeBeforeFixedVisibleElement = 0.0f;
  2041. // Keep track of the index of the first element who's size changed
  2042. firstDisplayedElementIndexWithSizeChangeOut = -1;
  2043. // Check if we need to calculate the real size for the known visible element index
  2044. if (m_cachedElementInfo[visibleElementIndex].m_size < 0.0f)
  2045. {
  2046. float prevSize = GetVariableElementSize(visibleElementIndex);
  2047. float newSize = CalculateVariableElementSize(visibleElementIndex);
  2048. totalSizeChange = newSize - prevSize;
  2049. firstDisplayedElementIndexWithSizeChangeOut = visibleElementIndex;
  2050. }
  2051. // Calculate visible space remaining
  2052. float spaceLeftBefore = 0.0f;
  2053. float spaceLeftAfter = 0.0f;
  2054. CalculateVisibleSpaceBeforeAndAfterElement(visibleElementIndex, keepAtEnd, visibleContentBound.GetX(), spaceLeftBefore, spaceLeftAfter);
  2055. firstVisibleElementIndexOut = visibleElementIndex;
  2056. lastVisibleElementIndexOut = visibleElementIndex;
  2057. firstDisplayedElementIndexOut = firstVisibleElementIndexOut;
  2058. lastDisplayedElementIndexOut = lastVisibleElementIndexOut;
  2059. bool extraElementsNeededForNavigation = AnyPrototypeElementsNavigable();
  2060. // Traverse up or left
  2061. bool hadSpaceLeft = true;
  2062. bool addedExtraElements = false;
  2063. while ((spaceLeftBefore > 0.0f || !addedExtraElements) && firstDisplayedElementIndexOut > 0)
  2064. {
  2065. if (spaceLeftBefore <= 0.0f)
  2066. {
  2067. if (hadSpaceLeft)
  2068. {
  2069. firstVisibleElementIndexOut = firstDisplayedElementIndexOut;
  2070. hadSpaceLeft = false;
  2071. }
  2072. if (!extraElementsNeededForNavigation)
  2073. {
  2074. break;
  2075. }
  2076. addedExtraElements = !m_hasSections || m_isPrototypeElementNavigable[GetElementTypeAtIndex(firstDisplayedElementIndexOut - 1)];
  2077. }
  2078. --firstDisplayedElementIndexOut;
  2079. if (m_cachedElementInfo[firstDisplayedElementIndexOut].m_size >= 0.0f)
  2080. {
  2081. spaceLeftBefore -= m_cachedElementInfo[firstDisplayedElementIndexOut].m_size;
  2082. }
  2083. else
  2084. {
  2085. // Calculate this element's size
  2086. float prevSize = GetVariableElementSize(firstDisplayedElementIndexOut);
  2087. float newSize = CalculateVariableElementSize(firstDisplayedElementIndexOut);
  2088. float sizeChange = newSize - prevSize;
  2089. totalSizeChange += sizeChange;
  2090. if (firstDisplayedElementIndexOut <= visibleElementIndex)
  2091. {
  2092. totalSizeChangeBeforeFixedVisibleElement += sizeChange;
  2093. }
  2094. spaceLeftBefore -= newSize;
  2095. if (firstDisplayedElementIndexWithSizeChangeOut < 0 || firstDisplayedElementIndexOut < firstDisplayedElementIndexWithSizeChangeOut)
  2096. {
  2097. firstDisplayedElementIndexWithSizeChangeOut = firstDisplayedElementIndexOut;
  2098. }
  2099. }
  2100. }
  2101. if (hadSpaceLeft)
  2102. {
  2103. firstVisibleElementIndexOut = firstDisplayedElementIndexOut;
  2104. }
  2105. // Traverse down or right
  2106. hadSpaceLeft = true;
  2107. addedExtraElements = false;
  2108. while ((spaceLeftAfter > 0.0f || !addedExtraElements) && lastDisplayedElementIndexOut < m_numElements - 1)
  2109. {
  2110. if (spaceLeftAfter <= 0.0f)
  2111. {
  2112. if (hadSpaceLeft)
  2113. {
  2114. lastVisibleElementIndexOut = lastDisplayedElementIndexOut;
  2115. hadSpaceLeft = false;
  2116. }
  2117. if (!extraElementsNeededForNavigation)
  2118. {
  2119. break;
  2120. }
  2121. addedExtraElements = !m_hasSections || m_isPrototypeElementNavigable[GetElementTypeAtIndex(lastDisplayedElementIndexOut + 1)];
  2122. }
  2123. ++lastDisplayedElementIndexOut;
  2124. if (m_cachedElementInfo[lastDisplayedElementIndexOut].m_size >= 0.0f)
  2125. {
  2126. spaceLeftAfter -= m_cachedElementInfo[lastDisplayedElementIndexOut].m_size;
  2127. }
  2128. else
  2129. {
  2130. // Calculate this element's size
  2131. float prevSize = GetVariableElementSize(lastDisplayedElementIndexOut);
  2132. float newSize = CalculateVariableElementSize(lastDisplayedElementIndexOut);
  2133. float sizeChange = newSize - prevSize;
  2134. totalSizeChange += sizeChange;
  2135. if (lastDisplayedElementIndexOut <= visibleElementIndex)
  2136. {
  2137. totalSizeChangeBeforeFixedVisibleElement += sizeChange;
  2138. }
  2139. spaceLeftAfter -= newSize;
  2140. if (firstDisplayedElementIndexWithSizeChangeOut < 0 || lastDisplayedElementIndexOut < firstDisplayedElementIndexWithSizeChangeOut)
  2141. {
  2142. firstDisplayedElementIndexWithSizeChangeOut = lastDisplayedElementIndexOut;
  2143. }
  2144. }
  2145. }
  2146. if (hadSpaceLeft)
  2147. {
  2148. lastVisibleElementIndexOut = lastDisplayedElementIndexOut;
  2149. }
  2150. if (StickyHeadersEnabled())
  2151. {
  2152. // Check which header should currently be sticky and calculate its size if needed
  2153. if (firstVisibleElementIndexOut >= 0)
  2154. {
  2155. ElementIndexInfo firstVisibleElementIndexInfo = GetElementIndexInfoFromIndex(firstVisibleElementIndexOut);
  2156. int stickyHeaderElementIndex = m_sections[firstVisibleElementIndexInfo.m_sectionIndex].m_headerElementIndex;
  2157. if (m_cachedElementInfo[stickyHeaderElementIndex].m_size < 0.0f)
  2158. {
  2159. // Calculate this element's size
  2160. float prevSize = GetVariableElementSize(stickyHeaderElementIndex);
  2161. float newSize = CalculateVariableElementSize(stickyHeaderElementIndex);
  2162. float sizeChange = newSize - prevSize;
  2163. totalSizeChange += sizeChange;
  2164. // Cache the accumulated size
  2165. m_cachedElementInfo[stickyHeaderElementIndex].m_accumulatedSize = GetVariableSizeElementOffset(stickyHeaderElementIndex) + newSize;
  2166. // Update accumulated sizes for all elements after the sticky header and before the first displayed element who's size changed.
  2167. // The rest of the cache updates for the displayed elements who's size changed will be handled below
  2168. for (int index = stickyHeaderElementIndex + 1; index < AZ::GetMax(firstDisplayedElementIndexWithSizeChangeOut, firstDisplayedElementIndexOut); index++)
  2169. {
  2170. if (m_cachedElementInfo[index].m_accumulatedSize >= 0.0f)
  2171. {
  2172. m_cachedElementInfo[index].m_accumulatedSize += sizeChange;
  2173. }
  2174. }
  2175. if (stickyHeaderElementIndex <= visibleElementIndex)
  2176. {
  2177. totalSizeChangeBeforeFixedVisibleElement += sizeChange;
  2178. }
  2179. if (firstDisplayedElementIndexWithSizeChangeOut < 0 || stickyHeaderElementIndex < firstDisplayedElementIndexWithSizeChangeOut)
  2180. {
  2181. firstDisplayedElementIndexWithSizeChangeOut = stickyHeaderElementIndex;
  2182. }
  2183. }
  2184. }
  2185. }
  2186. DisableElementsForAutoSizeCalculation();
  2187. // Update the cache info
  2188. if (firstDisplayedElementIndexWithSizeChangeOut >= 0)
  2189. {
  2190. // Cache the accumulated sizes for the displayed elements who's sizes were just calculated and cached
  2191. int startIndex = AZ::GetMax(firstDisplayedElementIndexWithSizeChangeOut, firstDisplayedElementIndexOut);
  2192. float curPos = GetVariableSizeElementOffset(startIndex);
  2193. for (int index = startIndex; index <= lastDisplayedElementIndexOut; index++)
  2194. {
  2195. curPos += m_cachedElementInfo[index].m_size;
  2196. m_cachedElementInfo[index].m_accumulatedSize = curPos;
  2197. }
  2198. // Update accumulated sizes for all elements after the last displayed element
  2199. for (int index = lastDisplayedElementIndexOut + 1; index < m_numElements; index++)
  2200. {
  2201. if (m_cachedElementInfo[index].m_accumulatedSize >= 0.0f)
  2202. {
  2203. m_cachedElementInfo[index].m_accumulatedSize += totalSizeChange;
  2204. }
  2205. }
  2206. }
  2207. UpdateAverageElementSize(0, totalSizeChange);
  2208. scrollChangeOut = 0.0f;
  2209. if (totalSizeChange != 0.0f)
  2210. {
  2211. if (keepAtEnd)
  2212. {
  2213. scrollChangeOut = CalculateContentEndDeltaAfterSizeChange(totalSizeChange);
  2214. }
  2215. else
  2216. {
  2217. scrollChangeOut = CalculateContentBeginningDeltaAfterSizeChange(totalSizeChange);
  2218. }
  2219. }
  2220. if (!keepAtEnd)
  2221. {
  2222. scrollChangeOut -= totalSizeChangeBeforeFixedVisibleElement;
  2223. }
  2224. totalElementSizechangeOut = totalSizeChange;
  2225. }
  2226. ////////////////////////////////////////////////////////////////////////////////////////////////////
  2227. float UiDynamicScrollBoxComponent::CalculateContentBeginningDeltaAfterSizeChange(float contentSizeDelta) const
  2228. {
  2229. // Find the content element
  2230. AZ::EntityId contentEntityId;
  2231. UiScrollBoxBus::EventResult(contentEntityId, GetEntityId(), &UiScrollBoxBus::Events::GetContentEntity);
  2232. if (!contentEntityId.IsValid())
  2233. {
  2234. return 0.0f;
  2235. }
  2236. // Get current content size
  2237. AZ::Vector2 curContentSize(0.0f, 0.0f);
  2238. UiTransformBus::EventResult(curContentSize, contentEntityId, &UiTransformBus::Events::GetCanvasSpaceSizeNoScaleRotate);
  2239. UiTransform2dInterface::Offsets offsets;
  2240. UiTransform2dBus::EventResult(offsets, contentEntityId, &UiTransform2dBus::Events::GetOffsets);
  2241. AZ::Vector2 pivot;
  2242. UiTransformBus::EventResult(pivot, contentEntityId, &UiTransformBus::Events::GetPivot);
  2243. float beginningDelta = 0.0f;
  2244. if (m_isVertical)
  2245. {
  2246. beginningDelta = contentSizeDelta * pivot.GetY();
  2247. }
  2248. else
  2249. {
  2250. beginningDelta = contentSizeDelta * pivot.GetX();
  2251. }
  2252. return beginningDelta;
  2253. }
  2254. ////////////////////////////////////////////////////////////////////////////////////////////////////
  2255. float UiDynamicScrollBoxComponent::CalculateContentEndDeltaAfterSizeChange(float contentSizeDelta) const
  2256. {
  2257. // Find the content element
  2258. AZ::EntityId contentEntityId;
  2259. UiScrollBoxBus::EventResult(contentEntityId, GetEntityId(), &UiScrollBoxBus::Events::GetContentEntity);
  2260. if (!contentEntityId.IsValid())
  2261. {
  2262. return 0.0f;
  2263. }
  2264. // Get current content size
  2265. AZ::Vector2 curContentSize(0.0f, 0.0f);
  2266. UiTransformBus::EventResult(curContentSize, contentEntityId, &UiTransformBus::Events::GetCanvasSpaceSizeNoScaleRotate);
  2267. UiTransform2dInterface::Offsets offsets;
  2268. UiTransform2dBus::EventResult(offsets, contentEntityId, &UiTransform2dBus::Events::GetOffsets);
  2269. AZ::Vector2 pivot;
  2270. UiTransformBus::EventResult(pivot, contentEntityId, &UiTransformBus::Events::GetPivot);
  2271. float endDelta = 0.0f;
  2272. if (m_isVertical)
  2273. {
  2274. // Restore end
  2275. endDelta = -contentSizeDelta * (1.0f - pivot.GetY());
  2276. }
  2277. else
  2278. {
  2279. // Restore end
  2280. endDelta = -contentSizeDelta * (1.0f - pivot.GetX());
  2281. }
  2282. return endDelta;
  2283. }
  2284. ////////////////////////////////////////////////////////////////////////////////////////////////////
  2285. bool UiDynamicScrollBoxComponent::IsScrolledToEnd() const
  2286. {
  2287. // Find the content element
  2288. AZ::EntityId contentEntityId;
  2289. UiScrollBoxBus::EventResult(contentEntityId, GetEntityId(), &UiScrollBoxBus::Events::GetContentEntity);
  2290. if (!contentEntityId.IsValid())
  2291. {
  2292. return false;
  2293. }
  2294. // Get content's parent
  2295. AZ::EntityId contentParentEntityId;
  2296. UiElementBus::EventResult(contentParentEntityId, contentEntityId, &UiElementBus::Events::GetParentEntityId);
  2297. if (!contentParentEntityId.IsValid())
  2298. {
  2299. return false;
  2300. }
  2301. // Get content's rect in canvas space
  2302. UiTransformInterface::Rect contentRect;
  2303. UiTransformBus::Event(contentEntityId, &UiTransformBus::Events::GetCanvasSpaceRectNoScaleRotate, contentRect);
  2304. // Get content parent's rect in canvas space
  2305. UiTransformInterface::Rect parentRect;
  2306. UiTransformBus::Event(contentParentEntityId, &UiTransformBus::Events::GetCanvasSpaceRectNoScaleRotate, parentRect);
  2307. bool scrolledToEnd = false;
  2308. if (m_isVertical)
  2309. {
  2310. scrolledToEnd = parentRect.bottom >= contentRect.bottom;
  2311. }
  2312. else
  2313. {
  2314. scrolledToEnd = parentRect.right >= contentRect.right;
  2315. }
  2316. return scrolledToEnd;
  2317. }
  2318. ////////////////////////////////////////////////////////////////////////////////////////////////////
  2319. bool UiDynamicScrollBoxComponent::IsElementDisplayedAtIndex(int index) const
  2320. {
  2321. if (m_firstDisplayedElementIndex < 0)
  2322. {
  2323. return false;
  2324. }
  2325. return ((index >= m_firstDisplayedElementIndex) && (index <= m_lastDisplayedElementIndex));
  2326. }
  2327. ////////////////////////////////////////////////////////////////////////////////////////////////////
  2328. AZ::EntityId UiDynamicScrollBoxComponent::GetElementForDisplay(ElementType elementType)
  2329. {
  2330. AZ::EntityId element;
  2331. // Check if there is an existing element
  2332. if (!m_recycledElements[elementType].empty())
  2333. {
  2334. element = m_recycledElements[elementType].front();
  2335. m_recycledElements[elementType].pop_front();
  2336. // Enable element
  2337. UiElementBus::Event(element, &UiElementBus::Events::SetIsEnabled, true);
  2338. }
  2339. else
  2340. {
  2341. element = ClonePrototypeElement(elementType);
  2342. }
  2343. return element;
  2344. }
  2345. ////////////////////////////////////////////////////////////////////////////////////////////////////
  2346. AZ::EntityId UiDynamicScrollBoxComponent::GetElementForAutoSizeCalculation(ElementType elementType)
  2347. {
  2348. if (!m_clonedElementForAutoSizeCalculation[elementType].IsValid())
  2349. {
  2350. m_clonedElementForAutoSizeCalculation[elementType] = ClonePrototypeElement(elementType);
  2351. }
  2352. else
  2353. {
  2354. // Enable element
  2355. UiElementBus::Event(m_clonedElementForAutoSizeCalculation[elementType], &UiElementBus::Events::SetIsEnabled, true);
  2356. }
  2357. return m_clonedElementForAutoSizeCalculation[elementType];
  2358. }
  2359. ////////////////////////////////////////////////////////////////////////////////////////////////////
  2360. void UiDynamicScrollBoxComponent::DisableElementsForAutoSizeCalculation() const
  2361. {
  2362. for (int i = 0; i < ElementType::NumElementTypes; i++)
  2363. {
  2364. if (m_clonedElementForAutoSizeCalculation[i].IsValid())
  2365. {
  2366. // Disable element
  2367. UiElementBus::Event(m_clonedElementForAutoSizeCalculation[i], &UiElementBus::Events::SetIsEnabled, false);
  2368. }
  2369. }
  2370. }
  2371. ////////////////////////////////////////////////////////////////////////////////////////////////////
  2372. float UiDynamicScrollBoxComponent::AutoCalculateElementSize(AZ::EntityId elementForAutoSizeCalculation) const
  2373. {
  2374. float size = 0.0f;
  2375. if (m_isVertical)
  2376. {
  2377. size = UiLayoutHelpers::GetLayoutElementTargetHeight(elementForAutoSizeCalculation);
  2378. }
  2379. else
  2380. {
  2381. size = UiLayoutHelpers::GetLayoutElementTargetWidth(elementForAutoSizeCalculation);
  2382. }
  2383. return size;
  2384. }
  2385. ////////////////////////////////////////////////////////////////////////////////////////////////////
  2386. void UiDynamicScrollBoxComponent::SizeVariableElementAtIndex(AZ::EntityId element, int index) const
  2387. {
  2388. // Get current element size
  2389. AZ::Vector2 curElementSize(0.0f, 0.0f);
  2390. UiTransformBus::EventResult(curElementSize, element, &UiTransformBus::Events::GetCanvasSpaceSizeNoScaleRotate);
  2391. float curSize = m_isVertical ? curElementSize.GetY() : curElementSize.GetX();
  2392. // Get new element size
  2393. float newSize = GetVariableElementSize(index);
  2394. if (newSize != curSize)
  2395. {
  2396. // Resize the element
  2397. UiTransform2dInterface::Offsets offsets;
  2398. UiTransform2dBus::EventResult(offsets, element, &UiTransform2dBus::Events::GetOffsets);
  2399. AZ::Vector2 pivot;
  2400. UiTransformBus::EventResult(pivot, element, &UiTransformBus::Events::GetPivot);
  2401. float sizeDiff = newSize - curSize;
  2402. if (m_isVertical)
  2403. {
  2404. offsets.m_top -= sizeDiff * pivot.GetY();
  2405. offsets.m_bottom += sizeDiff * (1.0f - pivot.GetY());
  2406. }
  2407. else
  2408. {
  2409. offsets.m_left -= sizeDiff * pivot.GetX();
  2410. offsets.m_right += sizeDiff * (1.0f - pivot.GetX());
  2411. }
  2412. UiTransform2dBus::Event(element, &UiTransform2dBus::Events::SetOffsets, offsets);
  2413. }
  2414. }
  2415. ////////////////////////////////////////////////////////////////////////////////////////////////////
  2416. void UiDynamicScrollBoxComponent::PositionElementAtIndex(AZ::EntityId element, int index) const
  2417. {
  2418. // Position offsets based on index
  2419. float offset = GetElementOffsetAtIndex(index);
  2420. SetElementOffsets(element, offset);
  2421. }
  2422. ////////////////////////////////////////////////////////////////////////////////////////////////////
  2423. void UiDynamicScrollBoxComponent::SetElementAnchors(AZ::EntityId element) const
  2424. {
  2425. // Get the element anchors
  2426. UiTransform2dInterface::Anchors anchors;
  2427. UiTransform2dBus::EventResult(anchors, element, &UiTransform2dBus::Events::GetAnchors);
  2428. if (m_isVertical)
  2429. {
  2430. // Set anchors to top of parent
  2431. anchors.m_top = 0.0f;
  2432. anchors.m_bottom = 0.0f;
  2433. }
  2434. else
  2435. {
  2436. // Set anchors to left of parent
  2437. anchors.m_left = 0.0f;
  2438. anchors.m_right = 0.0f;
  2439. }
  2440. UiTransform2dBus::Event(element, &UiTransform2dBus::Events::SetAnchors, anchors, false, false);
  2441. }
  2442. ////////////////////////////////////////////////////////////////////////////////////////////////////
  2443. void UiDynamicScrollBoxComponent::SetElementOffsets(AZ::EntityId element, float offset) const
  2444. {
  2445. // Get the element offsets
  2446. UiTransform2dInterface::Offsets offsets;
  2447. UiTransform2dBus::EventResult(offsets, element, &UiTransform2dBus::Events::GetOffsets);
  2448. if ((m_isVertical && offsets.m_top != offset) || (!m_isVertical && offsets.m_left != offset))
  2449. {
  2450. if (m_isVertical)
  2451. {
  2452. float height = offsets.m_bottom - offsets.m_top;
  2453. offsets.m_top = offset;
  2454. offsets.m_bottom = offsets.m_top + height;
  2455. }
  2456. else
  2457. {
  2458. float width = offsets.m_right - offsets.m_left;
  2459. offsets.m_left = offset;
  2460. offsets.m_right = offsets.m_left + width;
  2461. }
  2462. UiTransform2dBus::Event(element, &UiTransform2dBus::Events::SetOffsets, offsets);
  2463. }
  2464. }
  2465. ////////////////////////////////////////////////////////////////////////////////////////////////////
  2466. UiDynamicScrollBoxComponent::ElementType UiDynamicScrollBoxComponent::GetElementTypeAtIndex(int index) const
  2467. {
  2468. ElementType elementType = ElementType::Item;
  2469. if (m_hasSections)
  2470. {
  2471. for (int i = 0; i < m_sections.size(); i++)
  2472. {
  2473. if (m_sections[i].m_headerElementIndex == index)
  2474. {
  2475. elementType = ElementType::SectionHeader;
  2476. break;
  2477. }
  2478. }
  2479. }
  2480. return elementType;
  2481. }
  2482. ////////////////////////////////////////////////////////////////////////////////////////////////////
  2483. UiDynamicScrollBoxComponent::ElementIndexInfo UiDynamicScrollBoxComponent::GetElementIndexInfoFromIndex(int index) const
  2484. {
  2485. ElementIndexInfo elementIndexInfo;
  2486. elementIndexInfo.m_sectionIndex = -1;
  2487. elementIndexInfo.m_itemIndexInSection = index;
  2488. if (m_hasSections)
  2489. {
  2490. for (int i = 0; i < m_sections.size(); i++)
  2491. {
  2492. if (index <= m_sections[i].m_headerElementIndex + m_sections[i].m_numItems)
  2493. {
  2494. elementIndexInfo.m_sectionIndex = i;
  2495. elementIndexInfo.m_itemIndexInSection = (index - m_sections[i].m_headerElementIndex) - 1; // for headers, this will be set to -1
  2496. break;
  2497. }
  2498. }
  2499. }
  2500. return elementIndexInfo;
  2501. }
  2502. ////////////////////////////////////////////////////////////////////////////////////////////////////
  2503. int UiDynamicScrollBoxComponent::GetIndexFromElementIndexInfo(const ElementIndexInfo& elementIndexInfo) const
  2504. {
  2505. int index = elementIndexInfo.m_itemIndexInSection;
  2506. if (m_hasSections)
  2507. {
  2508. index += m_sections[elementIndexInfo.m_sectionIndex].m_headerElementIndex + 1;
  2509. }
  2510. return index;
  2511. }
  2512. ////////////////////////////////////////////////////////////////////////////////////////////////////
  2513. AZ::EntityId UiDynamicScrollBoxComponent::GetImmediateContentChildFromDescendant(AZ::EntityId childElement) const
  2514. {
  2515. AZ::EntityId immediateChild;
  2516. AZ::Entity* contentEntity = GetContentEntity();
  2517. if (contentEntity)
  2518. {
  2519. immediateChild = childElement;
  2520. AZ::Entity* parent = nullptr;
  2521. UiElementBus::EventResult(parent, immediateChild, &UiElementBus::Events::GetParent);
  2522. while (parent && parent != contentEntity)
  2523. {
  2524. immediateChild = parent->GetId();
  2525. UiElementBus::EventResult(parent, immediateChild, &UiElementBus::Events::GetParent);
  2526. }
  2527. if (parent != contentEntity)
  2528. {
  2529. immediateChild.SetInvalid();
  2530. }
  2531. }
  2532. return immediateChild;
  2533. }
  2534. ////////////////////////////////////////////////////////////////////////////////////////////////////
  2535. bool UiDynamicScrollBoxComponent::HeadersHaveVariableSizes() const
  2536. {
  2537. return m_hasSections && m_variableHeaderElementSize;
  2538. }
  2539. ////////////////////////////////////////////////////////////////////////////////////////////////////
  2540. bool UiDynamicScrollBoxComponent::IsValidPrototype(AZ::EntityId entityId) const
  2541. {
  2542. // Entities containing the scroll box itself are not safe to clone as they will respawn this
  2543. // scroll box and result in infinite recursive spawning.
  2544. if (!entityId.IsValid() || entityId == GetEntityId())
  2545. {
  2546. return false;
  2547. }
  2548. bool isEntityAncestor;
  2549. UiElementBus::EventResult(isEntityAncestor, GetEntityId(), &UiElementBus::Events::IsAncestor, entityId);
  2550. return !isEntityAncestor;
  2551. }