3
0

UiTransform2dComponent.cpp 79 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978
  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 "UiTransform2dComponent.h"
  9. #include <AzCore/Math/Crc.h>
  10. #include <AzCore/Serialization/SerializeContext.h>
  11. #include <AzCore/Serialization/EditContext.h>
  12. #include <AzCore/RTTI/BehaviorContext.h>
  13. #include <LyShine/IDraw2d.h>
  14. #include <LyShine/Bus/UiCanvasBus.h>
  15. #include <LyShine/Bus/UiElementBus.h>
  16. #include <LyShine/Bus/UiLayoutBus.h>
  17. #include "UiSerialize.h"
  18. #include "UiElementComponent.h"
  19. #include "UiCanvasComponent.h"
  20. #include <set>
  21. #include <list>
  22. namespace
  23. {
  24. bool AxisAlignedBoxesIntersect(const AZ::Vector2& minA, const AZ::Vector2& maxA, const AZ::Vector2& minB, const AZ::Vector2& maxB)
  25. {
  26. bool boxesIntersect = true;
  27. if (maxA.GetX() < minB.GetX() || // a is left of b
  28. minA.GetX() > maxB.GetX() || // a is right of b
  29. maxA.GetY() < minB.GetY() || // a is above b
  30. minA.GetY() > maxB.GetY()) // a is below b
  31. {
  32. boxesIntersect = false; // no overlap
  33. }
  34. return boxesIntersect;
  35. }
  36. void GetInverseTransform(const AZ::Vector2& pivot, const AZ::Vector2& scale, float rotation, AZ::Matrix4x4& mat)
  37. {
  38. AZ::Vector3 pivot3(pivot.GetX(), pivot.GetY(), 0);
  39. float rotRad = DEG2RAD(-rotation); // inverse rotation
  40. // Avoid a divide by zero. We could compare with 0.0f here and that would avoid a divide
  41. // by zero. However comparing with FLT_EPSILON also avoids the rare case of an overflow.
  42. // FLT_EPSILON is small enough to be considered equivalent to zero in this application.
  43. float inverseScaleX = (fabsf(scale.GetX()) > FLT_EPSILON) ? 1.0f / scale.GetX() : 1;
  44. float inverseScaleY = (fabsf(scale.GetY()) > FLT_EPSILON) ? 1.0f / scale.GetY() : 1;
  45. AZ::Vector3 scale3(inverseScaleX, inverseScaleY, 1); // inverse scale
  46. AZ::Matrix4x4 moveToPivotSpaceMat = AZ::Matrix4x4::CreateTranslation(-pivot3);
  47. AZ::Matrix4x4 scaleMat = AZ::Matrix4x4::CreateScale(scale3);
  48. AZ::Matrix4x4 rotMat = AZ::Matrix4x4::CreateRotationZ(rotRad);
  49. AZ::Matrix4x4 moveFromPivotSpaceMat = AZ::Matrix4x4::CreateTranslation(pivot3);
  50. mat = moveFromPivotSpaceMat * scaleMat * rotMat * moveToPivotSpaceMat;
  51. }
  52. ////////////////////////////////////////////////////////////////////////////////////////////////
  53. // Helper function to VersionConverter to convert a bool field to an int for ScaleToDevice
  54. inline bool ConvertScaleToDeviceFromBoolToEnum(
  55. AZ::SerializeContext& context,
  56. AZ::SerializeContext::DataElementNode& classElement)
  57. {
  58. // Note that the name of the new element has to be the same as the name of the old element
  59. // because we have no version conversion for data patches. The bool to enum conversion happens
  60. // to work out for the data patches because the bool value of 1 maps to the correct int value.
  61. const char* scaleToDeviceName = "ScaleToDevice";
  62. int index = classElement.FindElement(AZ_CRC(scaleToDeviceName));
  63. if (index != -1)
  64. {
  65. AZ::SerializeContext::DataElementNode& elementNode = classElement.GetSubElement(index);
  66. bool oldData;
  67. if (!elementNode.GetData(oldData))
  68. {
  69. // Error, old subElement was not a bool or not valid
  70. AZ_Error("Serialization", false, "Cannot get bool data for element %s.", scaleToDeviceName);
  71. return false;
  72. }
  73. // Remove old version.
  74. classElement.RemoveElement(index);
  75. // Add a new element for the new data.
  76. int newElementIndex = classElement.AddElement<int>(context, scaleToDeviceName);
  77. if (newElementIndex == -1)
  78. {
  79. // Error adding the new sub element
  80. AZ_Error("Serialization", false, "AddElement failed for converted element %s", scaleToDeviceName);
  81. return false;
  82. }
  83. int newData = (oldData) ?
  84. static_cast<int>(UiTransformInterface::ScaleToDeviceMode::UniformScaleToFit) :
  85. static_cast<int>(UiTransformInterface::ScaleToDeviceMode::None);
  86. classElement.GetSubElement(newElementIndex).SetData(context, newData);
  87. }
  88. // if the field did not exist then we do not report an error
  89. return true;
  90. }
  91. };
  92. ////////////////////////////////////////////////////////////////////////////////////////////////////
  93. // PUBLIC MEMBER FUNCTIONS
  94. ////////////////////////////////////////////////////////////////////////////////////////////////////
  95. ////////////////////////////////////////////////////////////////////////////////////////////////////
  96. UiTransform2dComponent::UiTransform2dComponent()
  97. : m_pivot(AZ::Vector2(0.5f, 0.5f))
  98. , m_rotation(0.0f)
  99. , m_scale(AZ::Vector2(1.0f, 1.0f))
  100. , m_isFlooringOffsets(false)
  101. , m_scaleToDeviceMode(ScaleToDeviceMode::None)
  102. , m_recomputeTransformToViewport(true)
  103. , m_recomputeTransformToCanvasSpace(true)
  104. , m_recomputeCanvasSpaceRect(true)
  105. , m_rectInitialized(false)
  106. , m_rectChangedByInitialization(false)
  107. {
  108. }
  109. ////////////////////////////////////////////////////////////////////////////////////////////////////
  110. UiTransform2dComponent::~UiTransform2dComponent()
  111. {
  112. }
  113. ////////////////////////////////////////////////////////////////////////////////////////////////////
  114. float UiTransform2dComponent::GetZRotation()
  115. {
  116. return m_rotation;
  117. }
  118. ////////////////////////////////////////////////////////////////////////////////////////////////////
  119. void UiTransform2dComponent::SetZRotation(float rotation)
  120. {
  121. if (m_rotation != rotation)
  122. {
  123. m_rotation = rotation;
  124. SetRecomputeFlags(UiTransformInterface::Recompute::TransformOnly);
  125. }
  126. }
  127. ////////////////////////////////////////////////////////////////////////////////////////////////////
  128. AZ::Vector2 UiTransform2dComponent::GetScale()
  129. {
  130. return m_scale;
  131. }
  132. ////////////////////////////////////////////////////////////////////////////////////////////////////
  133. void UiTransform2dComponent::SetScale(AZ::Vector2 scale)
  134. {
  135. if (m_scale != scale)
  136. {
  137. m_scale = scale;
  138. SetRecomputeFlags(UiTransformInterface::Recompute::TransformOnly);
  139. }
  140. }
  141. ////////////////////////////////////////////////////////////////////////////////////////////////////
  142. float UiTransform2dComponent::GetScaleX()
  143. {
  144. return m_scale.GetX();
  145. }
  146. ////////////////////////////////////////////////////////////////////////////////////////////////////
  147. void UiTransform2dComponent::SetScaleX(float scale)
  148. {
  149. return SetScale(AZ::Vector2(scale, m_scale.GetY()));
  150. }
  151. ////////////////////////////////////////////////////////////////////////////////////////////////////
  152. float UiTransform2dComponent::GetScaleY()
  153. {
  154. return m_scale.GetY();
  155. }
  156. ////////////////////////////////////////////////////////////////////////////////////////////////////
  157. void UiTransform2dComponent::SetScaleY(float scale)
  158. {
  159. return SetScale(AZ::Vector2(m_scale.GetX(), scale));
  160. }
  161. ////////////////////////////////////////////////////////////////////////////////////////////////////
  162. AZ::Vector2 UiTransform2dComponent::GetPivot()
  163. {
  164. return m_pivot;
  165. }
  166. ////////////////////////////////////////////////////////////////////////////////////////////////////
  167. void UiTransform2dComponent::SetPivot(AZ::Vector2 pivot)
  168. {
  169. if (m_pivot != pivot)
  170. {
  171. m_pivot = pivot;
  172. // changing the pivot does not change the rect, but if there is scale or rotation it does affect the transform.
  173. // However, we do want to notify other components if the pivot changes (for example the ImageComponent in
  174. // fixed mode is affected). So we recompute regardless of whether there is a scale or rotation.
  175. SetRecomputeFlags(UiTransformInterface::Recompute::TransformOnly);
  176. }
  177. }
  178. ////////////////////////////////////////////////////////////////////////////////////////////////////
  179. float UiTransform2dComponent::GetPivotX()
  180. {
  181. return m_pivot.GetX();
  182. }
  183. ////////////////////////////////////////////////////////////////////////////////////////////////////
  184. void UiTransform2dComponent::SetPivotX(float pivot)
  185. {
  186. return SetPivot(AZ::Vector2(pivot, m_pivot.GetY()));
  187. }
  188. ////////////////////////////////////////////////////////////////////////////////////////////////////
  189. float UiTransform2dComponent::GetPivotY()
  190. {
  191. return m_pivot.GetY();
  192. }
  193. ////////////////////////////////////////////////////////////////////////////////////////////////////
  194. void UiTransform2dComponent::SetPivotY(float pivot)
  195. {
  196. return SetPivot(AZ::Vector2(m_pivot.GetX(), pivot));
  197. }
  198. ////////////////////////////////////////////////////////////////////////////////////////////////////
  199. bool UiTransform2dComponent::GetIsFlooringOffsets()
  200. {
  201. return m_isFlooringOffsets;
  202. }
  203. ////////////////////////////////////////////////////////////////////////////////////////////////////
  204. void UiTransform2dComponent::SetIsFlooringOffsets(bool isFlooringOffsets)
  205. {
  206. if (m_isFlooringOffsets != isFlooringOffsets)
  207. {
  208. m_isFlooringOffsets = isFlooringOffsets;
  209. SetRecomputeFlags(UiTransformInterface::Recompute::RectOnly);
  210. }
  211. }
  212. ////////////////////////////////////////////////////////////////////////////////////////////////////
  213. UiTransform2dComponent::ScaleToDeviceMode UiTransform2dComponent::GetScaleToDeviceMode()
  214. {
  215. return m_scaleToDeviceMode;
  216. }
  217. ////////////////////////////////////////////////////////////////////////////////////////////////////
  218. void UiTransform2dComponent::SetScaleToDeviceMode(ScaleToDeviceMode scaleToDeviceMode)
  219. {
  220. if (m_scaleToDeviceMode != scaleToDeviceMode)
  221. {
  222. m_scaleToDeviceMode = scaleToDeviceMode;
  223. SetRecomputeFlags(UiTransformInterface::Recompute::TransformOnly);
  224. }
  225. }
  226. ////////////////////////////////////////////////////////////////////////////////////////////////////
  227. void UiTransform2dComponent::GetViewportSpacePoints(RectPoints& points)
  228. {
  229. GetCanvasSpacePointsNoScaleRotate(points);
  230. RotateAndScalePoints(points);
  231. }
  232. ////////////////////////////////////////////////////////////////////////////////////////////////////
  233. AZ::Vector2 UiTransform2dComponent::GetViewportSpacePivot()
  234. {
  235. // this function is primarily used for drawing the pivot in the editor. Since we snap the pivot
  236. // icon to the nearest pixel, if the X position is something like 20.5 it will snap different ways
  237. // depending on rounding errors. We don't want this to happen while rotating an element. So, make
  238. // sure the ViewportSpacePivot is calculated in a way that is independent of this element's
  239. // scale and rotation.
  240. AZ::Vector2 canvasSpacePivot = GetCanvasSpacePivotNoScaleRotate();
  241. AZ::Vector3 point3(canvasSpacePivot.GetX(), canvasSpacePivot.GetY(), 0.0f);
  242. UiTransform2dComponent* parentTransformComponent = GetParentTransformComponent();
  243. if (parentTransformComponent)
  244. {
  245. AZ::Matrix4x4 transform;
  246. parentTransformComponent->GetTransformToViewport(transform);
  247. point3 = transform * point3;
  248. }
  249. return AZ::Vector2(point3.GetX(), point3.GetY());
  250. }
  251. ////////////////////////////////////////////////////////////////////////////////////////////////////
  252. void UiTransform2dComponent::GetTransformToViewport(AZ::Matrix4x4& mat)
  253. {
  254. RecomputeTransformToViewportIfNeeded();
  255. mat = m_transformToViewport;
  256. }
  257. ////////////////////////////////////////////////////////////////////////////////////////////////////
  258. void UiTransform2dComponent::GetTransformFromViewport(AZ::Matrix4x4& mat)
  259. {
  260. // first get the transform from canvas space
  261. GetTransformFromCanvasSpace(mat);
  262. // then get the transform from viewport to canvas space
  263. AZ::Matrix4x4 viewportToCanvasMatrix;
  264. if (IsFullyInitialized())
  265. {
  266. GetCanvasComponent()->GetViewportToCanvasMatrix(viewportToCanvasMatrix);
  267. }
  268. else
  269. {
  270. EmitNotInitializedWarning();
  271. viewportToCanvasMatrix = AZ::Matrix4x4::CreateIdentity();
  272. }
  273. // add the transform from viewport space to canvas space to the transform matrix
  274. mat = mat * viewportToCanvasMatrix;
  275. }
  276. ////////////////////////////////////////////////////////////////////////////////////////////////////
  277. void UiTransform2dComponent::RotateAndScalePoints(RectPoints& points)
  278. {
  279. if (IsFullyInitialized() && GetElementComponent()->GetParent())
  280. {
  281. AZ::Matrix4x4 transform;
  282. GetTransformToViewport(transform);
  283. points = points.Transform(transform);
  284. }
  285. }
  286. ////////////////////////////////////////////////////////////////////////////////////////////////////
  287. void UiTransform2dComponent::GetCanvasSpacePoints(RectPoints& points)
  288. {
  289. GetCanvasSpacePointsNoScaleRotate(points);
  290. // apply the transform to canvas space
  291. if (IsFullyInitialized() && GetElementComponent()->GetParent())
  292. {
  293. AZ::Matrix4x4 transform;
  294. GetTransformToCanvasSpace(transform);
  295. points = points.Transform(transform);
  296. }
  297. }
  298. ////////////////////////////////////////////////////////////////////////////////////////////////////
  299. AZ::Vector2 UiTransform2dComponent::GetCanvasSpacePivot()
  300. {
  301. AZ::Vector2 canvasSpacePivot = GetCanvasSpacePivotNoScaleRotate();
  302. AZ::Vector3 point3(canvasSpacePivot.GetX(), canvasSpacePivot.GetY(), 0.0f);
  303. UiTransform2dComponent* parentTransformComponent = GetParentTransformComponent();
  304. if (parentTransformComponent)
  305. {
  306. AZ::Matrix4x4 transform;
  307. parentTransformComponent->GetTransformToCanvasSpace(transform);
  308. point3 = transform * point3;
  309. }
  310. return AZ::Vector2(point3.GetX(), point3.GetY());
  311. }
  312. ////////////////////////////////////////////////////////////////////////////////////////////////////
  313. void UiTransform2dComponent::GetTransformToCanvasSpace(AZ::Matrix4x4& mat)
  314. {
  315. RecomputeTransformToCanvasSpaceIfNeeded();
  316. mat = m_transformToCanvasSpace;
  317. }
  318. ////////////////////////////////////////////////////////////////////////////////////////////////////
  319. void UiTransform2dComponent::GetTransformFromCanvasSpace(AZ::Matrix4x4& mat)
  320. {
  321. // this takes a matrix and builds the concatenation of this elements rotate and scale about the pivot
  322. // with the transforms for all parent elements into one 3x4 matrix.
  323. // The result is an inverse transform that can be used to map from transformed space to non-transformed
  324. // space
  325. UiTransform2dComponent* parentTransformComponent = GetParentTransformComponent();
  326. if (parentTransformComponent)
  327. {
  328. parentTransformComponent->GetTransformFromCanvasSpace(mat);
  329. AZ::Matrix4x4 transformFromParent;
  330. GetLocalInverseTransform(transformFromParent);
  331. mat = transformFromParent * mat;
  332. }
  333. else
  334. {
  335. mat = AZ::Matrix4x4::CreateIdentity();
  336. }
  337. }
  338. ////////////////////////////////////////////////////////////////////////////////////////////////////
  339. void UiTransform2dComponent::GetCanvasSpaceRectNoScaleRotate(Rect& rect)
  340. {
  341. CalculateCanvasSpaceRect();
  342. rect = m_rect;
  343. }
  344. ////////////////////////////////////////////////////////////////////////////////////////////////////
  345. void UiTransform2dComponent::GetCanvasSpacePointsNoScaleRotate(RectPoints& points)
  346. {
  347. Rect rect;
  348. GetCanvasSpaceRectNoScaleRotate(rect);
  349. points.SetAxisAligned(rect.left, rect.right, rect.top, rect.bottom);
  350. }
  351. ////////////////////////////////////////////////////////////////////////////////////////////////////
  352. AZ::Vector2 UiTransform2dComponent::GetCanvasSpaceSizeNoScaleRotate()
  353. {
  354. Rect rect;
  355. GetCanvasSpaceRectNoScaleRotate(rect);
  356. return rect.GetSize();
  357. }
  358. ////////////////////////////////////////////////////////////////////////////////////////////////////
  359. AZ::Vector2 UiTransform2dComponent::GetCanvasSpacePivotNoScaleRotate()
  360. {
  361. Rect rect;
  362. GetCanvasSpaceRectNoScaleRotate(rect);
  363. AZ::Vector2 size = rect.GetSize();
  364. float x = rect.left + size.GetX() * m_pivot.GetX();
  365. float y = rect.top + size.GetY() * m_pivot.GetY();
  366. return AZ::Vector2(x, y);
  367. }
  368. ////////////////////////////////////////////////////////////////////////////////////////////////////
  369. void UiTransform2dComponent::GetLocalTransform(AZ::Matrix4x4& mat)
  370. {
  371. if (HasScaleOrRotation())
  372. {
  373. // this takes a matrix and builds the concatenation of this element's rotate and scale about the pivot
  374. AZ::Vector2 pivot = GetCanvasSpacePivotNoScaleRotate();
  375. AZ::Vector3 pivot3(pivot.GetX(), pivot.GetY(), 0);
  376. float rotRad = DEG2RAD(m_rotation); // rotation
  377. AZ::Vector2 scale = GetScaleAdjustedForDevice();
  378. AZ::Vector3 scale3(scale.GetX(), scale.GetY(), 1); // scale
  379. AZ::Matrix4x4 moveToPivotSpaceMat = AZ::Matrix4x4::CreateTranslation(-pivot3);
  380. AZ::Matrix4x4 scaleMat = AZ::Matrix4x4::CreateScale(scale3);
  381. AZ::Matrix4x4 rotMat = AZ::Matrix4x4::CreateRotationZ(rotRad);
  382. AZ::Matrix4x4 moveFromPivotSpaceMat = AZ::Matrix4x4::CreateTranslation(pivot3);
  383. mat = moveFromPivotSpaceMat * rotMat * scaleMat * moveToPivotSpaceMat;
  384. }
  385. else
  386. {
  387. mat = AZ::Matrix4x4::CreateIdentity();
  388. }
  389. }
  390. ////////////////////////////////////////////////////////////////////////////////////////////////////
  391. void UiTransform2dComponent::GetLocalInverseTransform(AZ::Matrix4x4& mat)
  392. {
  393. if (HasScaleOrRotation())
  394. {
  395. // this takes a matrix and builds the concatenation of this element's rotate and scale about the pivot
  396. // The result is an inverse transform that can be used to map from parent space to non-transformed
  397. // space
  398. AZ::Vector2 pivot = GetCanvasSpacePivotNoScaleRotate();
  399. AZ::Vector2 scale = GetScaleAdjustedForDevice();
  400. GetInverseTransform(pivot, scale, m_rotation, mat);
  401. }
  402. else
  403. {
  404. mat = AZ::Matrix4x4::CreateIdentity();
  405. }
  406. }
  407. ////////////////////////////////////////////////////////////////////////////////////////////////////
  408. bool UiTransform2dComponent::HasScaleOrRotation()
  409. {
  410. return (m_scaleToDeviceMode != ScaleToDeviceMode::None || m_scale.GetX() != 1.0f || m_scale.GetY() != 1.0f || m_rotation != 0.0f);
  411. }
  412. ////////////////////////////////////////////////////////////////////////////////////////////////////
  413. AZ::Vector2 UiTransform2dComponent::GetViewportPosition()
  414. {
  415. return GetViewportSpacePivot();
  416. }
  417. ////////////////////////////////////////////////////////////////////////////////////////////////////
  418. void UiTransform2dComponent::SetViewportPosition(const AZ::Vector2& position)
  419. {
  420. UiTransform2dComponent* parentTransformComponent = GetParentTransformComponent();
  421. if (!parentTransformComponent)
  422. {
  423. return; // this is the root element
  424. }
  425. AZ::Vector2 curCanvasSpacePosition = GetCanvasSpacePivotNoScaleRotate();
  426. AZ::Matrix4x4 transform;
  427. parentTransformComponent->GetTransformFromViewport(transform);
  428. AZ::Vector3 point3(position.GetX(), position.GetY(), 0.0f);
  429. point3 = transform * point3;
  430. AZ::Vector2 canvasSpacePosition(point3.GetX(), point3.GetY());
  431. if (canvasSpacePosition != curCanvasSpacePosition)
  432. {
  433. m_offsets += canvasSpacePosition - curCanvasSpacePosition;
  434. SetRecomputeFlags(UiTransformInterface::Recompute::RectOnly);
  435. }
  436. }
  437. ////////////////////////////////////////////////////////////////////////////////////////////////////
  438. AZ::Vector2 UiTransform2dComponent::GetCanvasPosition()
  439. {
  440. return GetCanvasSpacePivot();
  441. }
  442. ////////////////////////////////////////////////////////////////////////////////////////////////////
  443. void UiTransform2dComponent::SetCanvasPosition(const AZ::Vector2& position)
  444. {
  445. UiTransform2dComponent* parentTransformComponent = GetParentTransformComponent();
  446. if (!parentTransformComponent)
  447. {
  448. return; // this is the root element
  449. }
  450. AZ::Vector2 curCanvasSpacePosition = GetCanvasSpacePivotNoScaleRotate();
  451. AZ::Matrix4x4 transform;
  452. parentTransformComponent->GetTransformFromCanvasSpace(transform);
  453. AZ::Vector3 point3(position.GetX(), position.GetY(), 0.0f);
  454. point3 = transform * point3;
  455. AZ::Vector2 canvasSpacePosition(point3.GetX(), point3.GetY());
  456. if (canvasSpacePosition != curCanvasSpacePosition)
  457. {
  458. m_offsets += canvasSpacePosition - curCanvasSpacePosition;
  459. SetRecomputeFlags(UiTransformInterface::Recompute::RectOnly);
  460. }
  461. }
  462. ////////////////////////////////////////////////////////////////////////////////////////////////////
  463. AZ::Vector2 UiTransform2dComponent::GetLocalPosition()
  464. {
  465. AZ::Vector2 position = GetCanvasSpacePivotNoScaleRotate() - GetCanvasSpaceAnchorsCenterNoScaleRotate();
  466. return position;
  467. }
  468. ////////////////////////////////////////////////////////////////////////////////////////////////////
  469. void UiTransform2dComponent::SetLocalPosition(const AZ::Vector2& position)
  470. {
  471. AZ::Vector2 curPosition = GetLocalPosition();
  472. if (position != curPosition)
  473. {
  474. m_offsets += position - curPosition;
  475. SetRecomputeFlags(UiTransformInterface::Recompute::RectOnly);
  476. }
  477. }
  478. ////////////////////////////////////////////////////////////////////////////////////////////////////
  479. float UiTransform2dComponent::GetLocalPositionX()
  480. {
  481. AZ::Vector2 position = GetCanvasSpacePivotNoScaleRotate() - GetCanvasSpaceAnchorsCenterNoScaleRotate();
  482. return position.GetX();
  483. }
  484. ////////////////////////////////////////////////////////////////////////////////////////////////////
  485. void UiTransform2dComponent::SetLocalPositionX(float position)
  486. {
  487. AZ::Vector2 curPosition = GetLocalPosition();
  488. SetLocalPosition(AZ::Vector2(position, curPosition.GetY()));
  489. }
  490. ////////////////////////////////////////////////////////////////////////////////////////////////////
  491. float UiTransform2dComponent::GetLocalPositionY()
  492. {
  493. AZ::Vector2 position = GetCanvasSpacePivotNoScaleRotate() - GetCanvasSpaceAnchorsCenterNoScaleRotate();
  494. return position.GetY();
  495. }
  496. ////////////////////////////////////////////////////////////////////////////////////////////////////
  497. void UiTransform2dComponent::SetLocalPositionY(float position)
  498. {
  499. AZ::Vector2 curPosition = GetLocalPosition();
  500. SetLocalPosition(AZ::Vector2(curPosition.GetX(), position));
  501. }
  502. ////////////////////////////////////////////////////////////////////////////////////////////////////
  503. void UiTransform2dComponent::MoveViewportPositionBy(const AZ::Vector2& offset)
  504. {
  505. SetViewportPosition(GetViewportPosition() + offset);
  506. }
  507. ////////////////////////////////////////////////////////////////////////////////////////////////////
  508. void UiTransform2dComponent::MoveCanvasPositionBy(const AZ::Vector2& offset)
  509. {
  510. SetCanvasPosition(GetCanvasPosition() + offset);
  511. }
  512. ////////////////////////////////////////////////////////////////////////////////////////////////////
  513. void UiTransform2dComponent::MoveLocalPositionBy(const AZ::Vector2& offset)
  514. {
  515. SetLocalPosition(GetLocalPosition() + offset);
  516. }
  517. ////////////////////////////////////////////////////////////////////////////////////////////////////
  518. bool UiTransform2dComponent::IsPointInRect(AZ::Vector2 point)
  519. {
  520. // get point in the no scale/rotate canvas space for this element
  521. AZ::Matrix4x4 transform;
  522. GetTransformFromViewport(transform);
  523. AZ::Vector3 point3(point.GetX(), point.GetY(), 0.0f);
  524. point3 = transform * point3;
  525. // get the rect for this element in the same space
  526. Rect rect;
  527. GetCanvasSpaceRectNoScaleRotate(rect);
  528. float left = rect.left;
  529. float right = rect.right;
  530. float top = rect.top;
  531. float bottom = rect.bottom;
  532. // allow for "flipped" rects
  533. if (left > right)
  534. {
  535. std::swap(left, right);
  536. }
  537. if (top > bottom)
  538. {
  539. std::swap(top, bottom);
  540. }
  541. // point is in rect if it is within rect or exactly on edge
  542. if (point3.GetX() >= left &&
  543. point3.GetX() <= right &&
  544. point3.GetY() >= top &&
  545. point3.GetY() <= bottom)
  546. {
  547. return true;
  548. }
  549. return false;
  550. }
  551. ////////////////////////////////////////////////////////////////////////////////////////////////////
  552. bool UiTransform2dComponent::BoundsAreOverlappingRect(const AZ::Vector2& bound0, const AZ::Vector2& bound1)
  553. {
  554. // Get the element points in viewport space
  555. RectPoints points;
  556. GetViewportSpacePoints(points);
  557. // If the element is axis aligned we can just do an AAAB to AABB intersection test.
  558. // This is by far the most common case in UI canvases
  559. if (points.TopLeft().GetY() == points.TopRight().GetY() && points.TopLeft().GetX() <= points.TopRight().GetX() &&
  560. points.TopLeft().GetX() == points.BottomLeft().GetX() && points.TopLeft().GetY() <= points.BottomLeft().GetY())
  561. {
  562. // the element has no rotation and is not flipped so use AABB test
  563. return AxisAlignedBoxesIntersect(bound0, bound1, points.TopLeft(), points.BottomRight());
  564. }
  565. // IMPORTANT: This collision detection algorithm is based on the
  566. // Separating Axis Theorem, but is optimized for this context.
  567. // This ISN'T a generalized implementation. We DISCOURAGE using
  568. // this implementation elsewhere.
  569. //
  570. // Reference:
  571. // http://en.wikipedia.org/wiki/Hyperplane_separation_theorem
  572. // Vertices from shape A (input shape, which is axis-aligned).
  573. std::list<AZ::Vector2> vertsA;
  574. {
  575. // bound0
  576. // A----B
  577. // | |
  578. // D----C
  579. // bound1
  580. vertsA.push_back(bound0); // A.
  581. vertsA.push_back(AZ::Vector2(bound1.GetX(), bound0.GetY())); // B.
  582. vertsA.push_back(bound1); // C.
  583. vertsA.push_back(AZ::Vector2(bound0.GetX(), bound1.GetY())); // D.
  584. }
  585. // Vertices from shape B (our shape, which ISN'T axis-aligned).
  586. RectPoints vertsB = points;
  587. // Normals from shape A (input shape, which is axis-aligned).
  588. // IMPORTANT: This ISN'T thread-safe.
  589. static std::list<AZ::Vector2> edgeNormalsA;
  590. if (edgeNormalsA.empty())
  591. {
  592. edgeNormalsA.push_back(AZ::Vector2(0.0f, 1.0f));
  593. edgeNormalsA.push_back(AZ::Vector2(1.0f, 0.0f));
  594. edgeNormalsA.push_back(AZ::Vector2(0.0f, -1.0f));
  595. edgeNormalsA.push_back(AZ::Vector2(-1.0f, 0.0f));
  596. }
  597. // All edge normals.
  598. std::list<AZ::Vector2> edgeNormals(edgeNormalsA);
  599. // Normals from shape B (our rect shape, which ISN'T axis-aligned).
  600. {
  601. // A----B
  602. // | |
  603. // D----C
  604. const AZ::Vector2& A = vertsB.TopLeft();
  605. const AZ::Vector2& B = vertsB.TopRight();
  606. const AZ::Vector2& C = vertsB.BottomRight();
  607. const AZ::Vector2& D = vertsB.BottomLeft();
  608. AZ::Vector2 normAB((B - A).GetNormalized().GetPerpendicular());
  609. AZ::Vector2 normBC((C - B).GetNormalized().GetPerpendicular());
  610. AZ::Vector2 normCD((D - C).GetNormalized().GetPerpendicular());
  611. AZ::Vector2 normDA((A - D).GetNormalized().GetPerpendicular());
  612. edgeNormals.push_back(normAB);
  613. edgeNormals.push_back(normBC);
  614. edgeNormals.push_back(normCD);
  615. edgeNormals.push_back(normDA);
  616. }
  617. // A collision occurs only when we CAN'T find any gaps.
  618. // To find a gap, we project all vertices against all normals.
  619. for (auto && n : edgeNormals)
  620. {
  621. std::set<float> vertsAdot;
  622. std::set<float> vertsBdot;
  623. for (auto && v : vertsA)
  624. {
  625. vertsAdot.insert(n.Dot(v));
  626. }
  627. for (auto && v : vertsB.pt)
  628. {
  629. vertsBdot.insert(n.Dot(v));
  630. }
  631. float minA = *vertsAdot.begin();
  632. float maxA = *vertsAdot.rbegin();
  633. float minB = *vertsBdot.begin();
  634. float maxB = *vertsBdot.rbegin();
  635. // Two intervals overlap if:
  636. //
  637. // ( ( A.min < B.max ) &&
  638. // ( A.max > B.min ) )
  639. //
  640. // Visual reference:
  641. // http://silentmatt.com/rectangle-intersection/
  642. if (!(minA < maxB) && (maxA > minB))
  643. {
  644. // Stop as soon as we find a gap.
  645. return false;
  646. }
  647. }
  648. return true;
  649. }
  650. ////////////////////////////////////////////////////////////////////////////////////////////////////
  651. void UiTransform2dComponent::SetRecomputeFlags(Recompute recompute)
  652. {
  653. if (!IsFullyInitialized())
  654. {
  655. // If not initialized yet then transform will be recomputed after Fixup so no need to emit warning
  656. return;
  657. }
  658. if (m_isFlooringOffsets && (recompute == Recompute::RectOnly || recompute == Recompute::RectAndTransform))
  659. {
  660. m_offsets.m_right = floorf(m_offsets.m_right);
  661. m_offsets.m_left = floorf(m_offsets.m_left);
  662. m_offsets.m_top = floorf(m_offsets.m_top);
  663. m_offsets.m_bottom = floorf(m_offsets.m_bottom);
  664. }
  665. if (recompute == Recompute::RectOnly && HasScaleOrRotation())
  666. {
  667. // if this element has scale or rotation then a rect change will require the transforms to be recomputed
  668. // This is an optimization because, in most canvases, most elements have no scale or rotation
  669. recompute = Recompute::RectAndTransform;
  670. }
  671. int numChildren = GetElementComponent()->GetNumChildElements();
  672. for (int i = 0; i < numChildren; i++)
  673. {
  674. UiTransform2dComponent* childTransformComponent = GetChildTransformComponent(i);
  675. if (childTransformComponent)
  676. {
  677. childTransformComponent->SetRecomputeFlags(recompute);
  678. }
  679. }
  680. switch (recompute)
  681. {
  682. case Recompute::RectOnly:
  683. m_recomputeCanvasSpaceRect = true;
  684. break;
  685. case Recompute::TransformOnly:
  686. m_recomputeTransformToCanvasSpace = true;
  687. m_recomputeTransformToViewport = true;
  688. break;
  689. case Recompute::ViewportTransformOnly:
  690. m_recomputeTransformToViewport = true;
  691. break;
  692. case Recompute::RectAndTransform:
  693. m_recomputeTransformToCanvasSpace = true;
  694. m_recomputeTransformToViewport = true;
  695. m_recomputeCanvasSpaceRect = true;
  696. break;
  697. }
  698. // Tell the canvas that this element needs a recompute
  699. GetCanvasComponent()->ScheduleElementForTransformRecompute(GetElementComponent());
  700. }
  701. ////////////////////////////////////////////////////////////////////////////////////////////////////
  702. bool UiTransform2dComponent::HasCanvasSpaceRectChanged()
  703. {
  704. CalculateCanvasSpaceRect();
  705. return (HasCanvasSpaceRectChangedByInitialization() || m_rect != m_prevRect);
  706. }
  707. ////////////////////////////////////////////////////////////////////////////////////////////////////
  708. bool UiTransform2dComponent::HasCanvasSpaceSizeChanged()
  709. {
  710. if (HasCanvasSpaceRectChanged())
  711. {
  712. static const float sizeChangeTolerance = 0.05f;
  713. // If old rect equals new rect, size changed due to initialization
  714. return (HasCanvasSpaceRectChangedByInitialization() || !m_prevRect.GetSize().IsClose(m_rect.GetSize(), sizeChangeTolerance));
  715. }
  716. return false;
  717. }
  718. ////////////////////////////////////////////////////////////////////////////////////////////////////
  719. bool UiTransform2dComponent::HasCanvasSpaceRectChangedByInitialization()
  720. {
  721. return m_rectChangedByInitialization;
  722. }
  723. ////////////////////////////////////////////////////////////////////////////////////////////////////
  724. void UiTransform2dComponent::NotifyAndResetCanvasSpaceRectChange()
  725. {
  726. if (HasCanvasSpaceRectChanged())
  727. {
  728. // Reset before sending the notification because the notification could trigger a new rect change
  729. Rect prevRect = m_prevRect;
  730. m_prevRect = m_rect;
  731. m_rectChangedByInitialization = false;
  732. UiTransformChangeNotificationBus::Event(
  733. GetEntityId(), &UiTransformChangeNotificationBus::Events::OnCanvasSpaceRectChanged, GetEntityId(), prevRect, m_rect);
  734. }
  735. }
  736. ////////////////////////////////////////////////////////////////////////////////////////////////////
  737. UiTransform2dComponent::Anchors UiTransform2dComponent::GetAnchors()
  738. {
  739. return m_anchors;
  740. }
  741. ////////////////////////////////////////////////////////////////////////////////////////////////////
  742. void UiTransform2dComponent::SetAnchors(Anchors anchors, bool adjustOffsets, bool allowPush)
  743. {
  744. Anchors oldAnchors = m_anchors;
  745. Offsets oldOffsets = m_offsets;
  746. // First adjust the input structure to be valid.
  747. // If either pair of anchors is flipped then set them to be the same.
  748. // To avoid changing one anchor "pushing" the other we check which one changed and correct that
  749. // unless allowPush is set in which case we do the opposite
  750. if (anchors.m_right < anchors.m_left)
  751. {
  752. if (anchors.m_right != m_anchors.m_right)
  753. {
  754. // right anchor changed
  755. if (allowPush)
  756. {
  757. anchors.m_left = anchors.m_right; // push left to match right
  758. }
  759. else
  760. {
  761. anchors.m_right = anchors.m_left; // clamp to right to equal left
  762. }
  763. }
  764. else
  765. {
  766. // left changed or both changed
  767. if (allowPush)
  768. {
  769. anchors.m_right = anchors.m_left; // push right to match left
  770. }
  771. else
  772. {
  773. anchors.m_left = anchors.m_right; // clamp left to equal right
  774. }
  775. }
  776. }
  777. if (anchors.m_bottom < anchors.m_top)
  778. {
  779. if (anchors.m_bottom != m_anchors.m_bottom)
  780. {
  781. // bottom anchor changed
  782. if (allowPush)
  783. {
  784. anchors.m_top = anchors.m_bottom; // push top to match bottom
  785. }
  786. else
  787. {
  788. anchors.m_bottom = anchors.m_top; // clamp bottom to equal top
  789. }
  790. }
  791. else
  792. {
  793. // top changed or both changed
  794. if (allowPush)
  795. {
  796. anchors.m_bottom = anchors.m_top; // push bottom to match top
  797. }
  798. else
  799. {
  800. anchors.m_top = anchors.m_bottom; // clamp top to equal bottom
  801. }
  802. }
  803. }
  804. if (adjustOffsets)
  805. {
  806. // now we need to adjust the offsets
  807. UiTransform2dComponent* parentTransformComponent = GetParentTransformComponent();
  808. if (parentTransformComponent)
  809. {
  810. AZ::Vector2 parentSize = parentTransformComponent->GetCanvasSpaceSizeNoScaleRotate();
  811. m_offsets.m_left -= parentSize.GetX() * (anchors.m_left - m_anchors.m_left);
  812. m_offsets.m_right -= parentSize.GetX() * (anchors.m_right - m_anchors.m_right);
  813. m_offsets.m_top -= parentSize.GetY() * (anchors.m_top - m_anchors.m_top);
  814. m_offsets.m_bottom -= parentSize.GetY() * (anchors.m_bottom - m_anchors.m_bottom);
  815. }
  816. }
  817. // now actually change the anchors
  818. m_anchors = anchors;
  819. // now, if the anchors are the same in a dimension we check that the offsets are not flipped in that dimension
  820. // if they are we set them to be zero apart. This is a rule when the anchors are together in order to prevent
  821. // displaying a negative width or height
  822. if (m_anchors.m_left == m_anchors.m_right && m_offsets.m_left > m_offsets.m_right)
  823. {
  824. // left and right offsets are flipped, set to their midpoint
  825. m_offsets.m_left = m_offsets.m_right = (m_offsets.m_left + m_offsets.m_right) * 0.5f;
  826. }
  827. if (m_anchors.m_top == m_anchors.m_bottom && m_offsets.m_top > m_offsets.m_bottom)
  828. {
  829. // top and bottom offsets are flipped, set to their midpoint
  830. m_offsets.m_top = m_offsets.m_bottom = (m_offsets.m_top + m_offsets.m_bottom) * 0.5f;
  831. }
  832. if (oldAnchors != m_anchors || oldOffsets != m_offsets)
  833. {
  834. SetRecomputeFlags(UiTransformInterface::Recompute::RectOnly);
  835. }
  836. }
  837. ////////////////////////////////////////////////////////////////////////////////////////////////////
  838. UiTransform2dComponent::Offsets UiTransform2dComponent::GetOffsets()
  839. {
  840. return m_offsets;
  841. }
  842. ////////////////////////////////////////////////////////////////////////////////////////////////////
  843. void UiTransform2dComponent::SetOffsets(Offsets offsets)
  844. {
  845. UiTransform2dComponent* parentTransformComponent = GetParentTransformComponent();
  846. if (!parentTransformComponent)
  847. {
  848. return; // cannot set offsets on the root element
  849. }
  850. // first adjust the input structure to be valid
  851. // if either pair of offsets is flipped then set them to be the same
  852. // to avoid changing one offset "pushing" the other we check which one changed and correct that
  853. // NOTE: To see if an offset is flipped we have to take into account all the parents, the calculation
  854. // below is based on the calculation in GetCanvasSpaceRectNoScaleRotate but needs to be able to do
  855. // it in reverse also
  856. // NOTE: if a parent changes size this can cause offsets to flip and this is OK - we treat it as a zero
  857. // rect in that dimension in GetCanvasSpaceRectNoScaleRotate. But if the offsets on this element are
  858. // being changed then we do enforce the "no flipping" rule.
  859. Rect parentRect;
  860. parentTransformComponent->GetCanvasSpaceRectNoScaleRotate(parentRect);
  861. AZ::Vector2 parentSize = parentRect.GetSize();
  862. float left = parentRect.left + parentSize.GetX() * m_anchors.m_left + offsets.m_left;
  863. float right = parentRect.left + parentSize.GetX() * m_anchors.m_right + offsets.m_right;
  864. float top = parentRect.top + parentSize.GetY() * m_anchors.m_top + offsets.m_top;
  865. float bottom = parentRect.top + parentSize.GetY() * m_anchors.m_bottom + offsets.m_bottom;
  866. if (left > right)
  867. {
  868. // left/right offsets are flipped
  869. bool leftChanged = offsets.m_left != m_offsets.m_left;
  870. bool rightChanged = offsets.m_right != m_offsets.m_right;
  871. if (leftChanged && rightChanged)
  872. {
  873. // Both changed. This usually happens when resizing by gizmo, which is about the pivot.
  874. // So rather than taking the midpoint (which the below calculation effectively does for the normal
  875. // case of pivot.GetX() = 0.5f) we take the point between the two values using the pivot as a ratio.
  876. // This makes sense even if not resizing by gizmo. When the width is zero the pivot position
  877. // is always co-incident with the left and right edges. So this calculation moves the two points
  878. // together without moving the pivot position.
  879. float newValue = left * (1.0f - m_pivot.GetX()) + right * m_pivot.GetX();
  880. offsets.m_left = newValue - (parentRect.left + parentSize.GetX() * m_anchors.m_left);
  881. offsets.m_right = newValue - (parentRect.left + parentSize.GetX() * m_anchors.m_right);
  882. }
  883. else if (rightChanged)
  884. {
  885. // the right offset changed, correct that one
  886. offsets.m_right = left - (parentRect.left + parentSize.GetX() * m_anchors.m_right);
  887. }
  888. else if (leftChanged)
  889. {
  890. // the left offset changed, correct that one
  891. offsets.m_left = right - (parentRect.left + parentSize.GetX() * m_anchors.m_left);
  892. }
  893. }
  894. if (top > bottom)
  895. {
  896. // top/bottom offsets are flipped
  897. bool topChanged = offsets.m_top != m_offsets.m_top;
  898. bool bottomChanged = offsets.m_bottom != m_offsets.m_bottom;
  899. if (topChanged && bottomChanged)
  900. {
  901. // Both changed. This usually happens when resizing by gizmo, which is about the pivot.
  902. // So rather than taking the midpoint (which the below calculation effectively does for the normal
  903. // case of pivot.GetY() = 0.5f) we take the point between the two values using the pivot as a ratio.
  904. float newValue = top * (1.0f - m_pivot.GetY()) + bottom * m_pivot.GetY();
  905. offsets.m_top = newValue - (parentRect.top + parentSize.GetY() * m_anchors.m_top);
  906. offsets.m_bottom = newValue - (parentRect.top + parentSize.GetY() * m_anchors.m_bottom);
  907. }
  908. else if (bottomChanged)
  909. {
  910. // the bottom offset changed, correct that one
  911. offsets.m_bottom = top - (parentRect.top + parentSize.GetY() * m_anchors.m_bottom);
  912. }
  913. else if (topChanged)
  914. {
  915. // the top offset changed, correct that one
  916. offsets.m_top = bottom - (parentRect.top + parentSize.GetY() * m_anchors.m_top);
  917. }
  918. }
  919. if (m_offsets != offsets)
  920. {
  921. m_offsets = offsets;
  922. SetRecomputeFlags(UiTransformInterface::Recompute::RectOnly);
  923. }
  924. }
  925. ////////////////////////////////////////////////////////////////////////////////////////////////////
  926. void UiTransform2dComponent::SetPivotAndAdjustOffsets(AZ::Vector2 pivot)
  927. {
  928. if (m_pivot == pivot)
  929. {
  930. return;
  931. }
  932. // if the element has local rotation or scale then we have to modify the offsets to keep the rect from moving
  933. // in transformed space.
  934. if (HasScaleOrRotation())
  935. {
  936. // Get the untransformed canvas space points and rect before we change the pivot
  937. RectPoints oldCanvasSpacePoints;
  938. GetCanvasSpacePointsNoScaleRotate(oldCanvasSpacePoints);
  939. Rect oldCanvasSpaceRect;
  940. GetCanvasSpaceRectNoScaleRotate(oldCanvasSpaceRect);
  941. // apply just this elements rotate and scale (must be done before changing pivot)
  942. // NOTE: this element's pivot only affects the local transformation so there is no need to apply all the
  943. // transforms up the hierarchy.
  944. AZ::Matrix4x4 localTransform;
  945. GetLocalTransform(localTransform);
  946. RectPoints localTransformedPoints = oldCanvasSpacePoints.Transform(localTransform);
  947. // Set the new pivot
  948. SetPivot(pivot);
  949. // Now work out what the canvas space pivot point would have to be to result in the same transformed points
  950. AZ::Vector2 rightVec = localTransformedPoints.TopRight() - localTransformedPoints.TopLeft();
  951. AZ::Vector2 downVec = localTransformedPoints.BottomLeft() - localTransformedPoints.TopLeft();
  952. AZ::Vector2 canvasSpacePivot = localTransformedPoints.TopLeft() + pivot.GetX() * rightVec + pivot.GetY() * downVec;
  953. // We know that changing the pivot will not change the size of the canvas space rect, just its position.
  954. // So from this new canvas space pivot point work out where the top left of the new canvas space rect would be
  955. AZ::Vector2 oldSize = oldCanvasSpaceRect.GetSize();
  956. float newLeft = canvasSpacePivot.GetX() - oldSize.GetX() * pivot.GetX();
  957. float newTop = canvasSpacePivot.GetY() - oldSize.GetY() * pivot.GetY();
  958. // we can then compute how much the rect has moved and just apply that delta to the offsets
  959. float deltaX = newLeft - oldCanvasSpaceRect.left;
  960. float deltaY = newTop - oldCanvasSpaceRect.top;
  961. m_offsets.m_left += deltaX;
  962. m_offsets.m_right += deltaX;
  963. m_offsets.m_top += deltaY;
  964. m_offsets.m_bottom += deltaY;
  965. SetRecomputeFlags(UiTransformInterface::Recompute::RectOnly);
  966. }
  967. else
  968. {
  969. // no scale or rotation, just set the pivot
  970. SetPivot(pivot);
  971. }
  972. }
  973. ////////////////////////////////////////////////////////////////////////////////////////////////////
  974. void UiTransform2dComponent::SetLocalWidth(float width)
  975. {
  976. // If anchors are different the local width isn't a fixed quantity
  977. if (m_anchors.m_left == m_anchors.m_right)
  978. {
  979. Offsets offsets = GetOffsets();
  980. float curWidth = m_offsets.m_right - m_offsets.m_left;
  981. float diff = width - curWidth;
  982. offsets.m_left -= diff * m_pivot.GetX();
  983. offsets.m_right += diff * (1.0f - m_pivot.GetX());
  984. SetOffsets(offsets);
  985. }
  986. }
  987. ////////////////////////////////////////////////////////////////////////////////////////////////////
  988. float UiTransform2dComponent::GetLocalWidth()
  989. {
  990. float width = 0;
  991. // If anchors are different the local width isn't a fixed quantity
  992. if (m_anchors.m_left == m_anchors.m_right)
  993. {
  994. width = m_offsets.m_right - m_offsets.m_left;
  995. }
  996. else
  997. {
  998. width = GetCanvasSpaceSizeNoScaleRotate().GetX();
  999. }
  1000. return width;
  1001. }
  1002. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1003. void UiTransform2dComponent::SetLocalHeight(float height)
  1004. {
  1005. // If anchors are different the local height isn't a fixed quantity
  1006. if (m_anchors.m_top == m_anchors.m_bottom)
  1007. {
  1008. Offsets offsets = GetOffsets();
  1009. float curHeight = m_offsets.m_bottom - m_offsets.m_top;
  1010. float diff = height - curHeight;
  1011. offsets.m_top -= diff * m_pivot.GetY();
  1012. offsets.m_bottom += diff * (1.0f - m_pivot.GetY());
  1013. SetOffsets(offsets);
  1014. }
  1015. }
  1016. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1017. float UiTransform2dComponent::GetLocalHeight()
  1018. {
  1019. float height = 0;
  1020. // If anchors are different the local height isn't a fixed quantity
  1021. if (m_anchors.m_top == m_anchors.m_bottom)
  1022. {
  1023. height = m_offsets.m_bottom - m_offsets.m_top;
  1024. }
  1025. else
  1026. {
  1027. height = GetCanvasSpaceSizeNoScaleRotate().GetY();
  1028. }
  1029. return height;
  1030. }
  1031. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1032. void UiTransform2dComponent::PropertyValuesChanged()
  1033. {
  1034. SetRecomputeFlags(UiTransformInterface::Recompute::RectAndTransform);
  1035. }
  1036. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1037. void UiTransform2dComponent::RecomputeTransformsAndSendNotifications()
  1038. {
  1039. NotifyAndResetCanvasSpaceRectChange();
  1040. RecomputeTransformToViewportIfNeeded();
  1041. }
  1042. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1043. // PUBLIC STATIC MEMBER FUNCTIONS
  1044. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1045. void UiTransform2dComponent::Reflect(AZ::ReflectContext* context)
  1046. {
  1047. AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
  1048. if (serializeContext)
  1049. {
  1050. serializeContext->Class<UiTransform2dComponent, AZ::Component>()
  1051. ->Version(4, &VersionConverter)
  1052. ->Field("Anchors", &UiTransform2dComponent::m_anchors)
  1053. ->Field("Offsets", &UiTransform2dComponent::m_offsets)
  1054. ->Field("Pivot", &UiTransform2dComponent::m_pivot)
  1055. ->Field("Rotation", &UiTransform2dComponent::m_rotation)
  1056. ->Field("Scale", &UiTransform2dComponent::m_scale)
  1057. ->Field("IsFlooringOffsets", &UiTransform2dComponent::m_isFlooringOffsets)
  1058. ->Field("ScaleToDevice", &UiTransform2dComponent::m_scaleToDeviceMode);
  1059. // EditContext. Note that the Transform component is unusual in that we want to hide the
  1060. // properties when the transform is controlled by the parent. There is not a standard
  1061. // way to hide all the properties and replace them by a message. We could hide them all
  1062. // using the "Visibility" attribute, but then the component name itself is not even shown.
  1063. // We really want to be able to display a message indicating why the properties are not shown.
  1064. // Alternatively we could make them all read-only using the "ReadOnly" property. Again this
  1065. // doesn't tell the user why.
  1066. // So the approach we use is:
  1067. // - Hide all of the properties except Anchors using the "Visibility" property
  1068. // - Set the Anchors property to ReadOnly and change the ProertyHandler for Anchors to
  1069. // display a message in this case (and have a different tooltip)
  1070. // - Dynamically change the property name of the Anchors property using the
  1071. // "NameLabelOverride" attribute.
  1072. AZ::EditContext* ec = serializeContext->GetEditContext();
  1073. if (ec)
  1074. {
  1075. auto editInfo = ec->Class<UiTransform2dComponent>("Transform2D",
  1076. "All 2D UI elements have this component.\n"
  1077. "It controls the placement of the element's rectangle relative to its parent");
  1078. editInfo->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  1079. ->Attribute(AZ::Edit::Attributes::Icon, "Editor/Icons/Components/UiTransform2d.png")
  1080. ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Editor/Icons/Components/Viewport/UiTransform2d.png")
  1081. ->Attribute(AZ::Edit::Attributes::AddableByUser, false) // Cannot be added or removed by user
  1082. ->Attribute(AZ::Edit::Attributes::AutoExpand, true);
  1083. editInfo->DataElement("Anchor", &UiTransform2dComponent::m_anchors, "Anchors",
  1084. "The anchors specify proportional positions within the parent element's rectangle.\n"
  1085. "If the anchors are together (e.g. left = right or top = bottom) then, in that dimension,\n"
  1086. "there is a single anchor point that the element is offset from.\n"
  1087. "If they are apart, then there are two anchor points and as the parent changes size\n"
  1088. "this element will change size also")
  1089. ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ_CRC("RefreshAttributesAndValues", 0xcbc2147c)) // Refresh attributes for scale to device mode
  1090. ->Attribute(AZ::Edit::Attributes::Min, 0.0f)
  1091. ->Attribute(AZ::Edit::Attributes::Max, 100.0f)
  1092. ->Attribute(AZ::Edit::Attributes::Step, 1.0f)
  1093. ->Attribute(AZ::Edit::Attributes::Suffix, "%")
  1094. ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::Show) // needed because sub-elements are hidden
  1095. ->Attribute(AZ::Edit::Attributes::ReadOnly, &UiTransform2dComponent::IsControlledByParent)
  1096. ->Attribute(AZ_CRC("LayoutFitterType", 0x7c009203), &UiTransform2dComponent::GetLayoutFitterType)
  1097. ->Attribute(AZ::Edit::Attributes::NameLabelOverride, &UiTransform2dComponent::GetAnchorPropertyLabel);
  1098. editInfo->DataElement("Offset", &UiTransform2dComponent::m_offsets, "Offsets",
  1099. "The offsets (in pixels) from the anchors.\n"
  1100. "When anchors are together, the offset to the pivot plus the size is displayed.\n"
  1101. "When they are apart, the offsets to each edge of the element's rect are displayed")
  1102. ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ_CRC("RefreshValues", 0x28e720d4))
  1103. ->Attribute(AZ::Edit::Attributes::Visibility, &UiTransform2dComponent::IsNotControlledByParent)
  1104. ->Attribute(AZ_CRC("LayoutFitterType", 0x7c009203), &UiTransform2dComponent::GetLayoutFitterType)
  1105. ->Attribute(AZ::Edit::Attributes::Min, -AZ::Constants::MaxFloatBeforePrecisionLoss)
  1106. ->Attribute(AZ::Edit::Attributes::Max, AZ::Constants::MaxFloatBeforePrecisionLoss);
  1107. editInfo->DataElement("Pivot", &UiTransform2dComponent::m_pivot, "Pivot",
  1108. "Rotation and scaling happens around the pivot point.\n"
  1109. "If the anchors are together then the offsets specify the offset from the anchor to the pivot")
  1110. ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ_CRC("RefreshValues", 0x28e720d4))
  1111. ->Attribute(AZ::Edit::Attributes::Step, 0.1f)
  1112. ->Attribute(AZ::Edit::Attributes::Min, -AZ::Constants::MaxFloatBeforePrecisionLoss)
  1113. ->Attribute(AZ::Edit::Attributes::Max, AZ::Constants::MaxFloatBeforePrecisionLoss);
  1114. editInfo->DataElement(AZ::Edit::UIHandlers::SpinBox, &UiTransform2dComponent::m_rotation, "Rotation",
  1115. "The rotation in degrees about the pivot point")
  1116. ->Attribute(AZ::Edit::Attributes::Step, 0.1f)
  1117. ->Attribute(AZ::Edit::Attributes::Suffix, " degrees")
  1118. ->Attribute(AZ::Edit::Attributes::ChangeNotify, &UiTransform2dComponent::OnTransformPropertyChanged);
  1119. editInfo->DataElement(0, &UiTransform2dComponent::m_scale, "Scale",
  1120. "The X and Y scale around the pivot point")
  1121. ->Attribute(AZ::Edit::Attributes::ChangeNotify, &UiTransform2dComponent::OnTransformPropertyChanged)
  1122. ->Attribute(AZ::Edit::Attributes::Min, -AZ::Constants::MaxFloatBeforePrecisionLoss)
  1123. ->Attribute(AZ::Edit::Attributes::Max, AZ::Constants::MaxFloatBeforePrecisionLoss);
  1124. editInfo->DataElement(
  1125. AZ::Edit::UIHandlers::CheckBox,
  1126. &UiTransform2dComponent::m_isFlooringOffsets,
  1127. "Floor offsets",
  1128. "When checked, this element's offsets are floored");
  1129. editInfo->DataElement(AZ::Edit::UIHandlers::ComboBox, &UiTransform2dComponent::m_scaleToDeviceMode, "Scale to device",
  1130. "Controls how this element and all its children will be scaled to allow for\n"
  1131. "the difference between the authored canvas size and the actual viewport size")
  1132. ->EnumAttribute(UiTransformInterface::ScaleToDeviceMode::None, "None")
  1133. ->EnumAttribute(UiTransformInterface::ScaleToDeviceMode::UniformScaleToFit, "Scale to fit (uniformly)")
  1134. ->EnumAttribute(UiTransformInterface::ScaleToDeviceMode::UniformScaleToFill, "Scale to fill (uniformly)")
  1135. ->EnumAttribute(UiTransformInterface::ScaleToDeviceMode::UniformScaleToFitX, "Scale to fit X (uniformly)")
  1136. ->EnumAttribute(UiTransformInterface::ScaleToDeviceMode::UniformScaleToFitY, "Scale to fit Y (uniformly)")
  1137. ->EnumAttribute(UiTransformInterface::ScaleToDeviceMode::NonUniformScale, "Stretch to fill (non-uniformly)")
  1138. ->EnumAttribute(UiTransformInterface::ScaleToDeviceMode::ScaleXOnly, "Stretch to fit X (non-uniformly)")
  1139. ->EnumAttribute(UiTransformInterface::ScaleToDeviceMode::ScaleYOnly, "Stretch to fit Y (non-uniformly)")
  1140. ->Attribute("Warning", &UiTransform2dComponent::GetScaleToDeviceModeWarningTooltipText)
  1141. ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ_CRC("RefreshAttributesAndValues", 0xcbc2147c));
  1142. }
  1143. }
  1144. AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context);
  1145. if (behaviorContext)
  1146. {
  1147. behaviorContext->Enum<(int)UiTransformInterface::ScaleToDeviceMode::None>("eUiScaleToDeviceMode_None")
  1148. ->Enum<(int)UiTransformInterface::ScaleToDeviceMode::UniformScaleToFit>("eUiScaleToDeviceMode_UniformScaleToFit")
  1149. ->Enum<(int)UiTransformInterface::ScaleToDeviceMode::UniformScaleToFill>("eUiScaleToDeviceMode_UniformScaleToFill")
  1150. ->Enum<(int)UiTransformInterface::ScaleToDeviceMode::UniformScaleToFitX>("eUiScaleToDeviceMode_UniformScaleToFitX")
  1151. ->Enum<(int)UiTransformInterface::ScaleToDeviceMode::UniformScaleToFitY>("eUiScaleToDeviceMode_UniformScaleToFitY")
  1152. ->Enum<(int)UiTransformInterface::ScaleToDeviceMode::NonUniformScale>("eUiScaleToDeviceMode_NonUniformScale")
  1153. ->Enum<(int)UiTransformInterface::ScaleToDeviceMode::ScaleXOnly>("eUiScaleToDeviceMode_ScaleXOnly")
  1154. ->Enum<(int)UiTransformInterface::ScaleToDeviceMode::ScaleYOnly>("eUiScaleToDeviceMode_ScaleYOnly");
  1155. behaviorContext->EBus<UiTransformBus>("UiTransformBus")
  1156. ->Event("GetZRotation", &UiTransformBus::Events::GetZRotation)
  1157. ->Event("SetZRotation", &UiTransformBus::Events::SetZRotation)
  1158. ->Event("GetScale", &UiTransformBus::Events::GetScale)
  1159. ->Event("SetScale", &UiTransformBus::Events::SetScale)
  1160. ->Event("GetScaleX", &UiTransformBus::Events::GetScaleX)
  1161. ->Event("SetScaleX", &UiTransformBus::Events::SetScaleX)
  1162. ->Event("GetScaleY", &UiTransformBus::Events::GetScaleY)
  1163. ->Event("SetScaleY", &UiTransformBus::Events::SetScaleY)
  1164. ->Event("GetPivot", &UiTransformBus::Events::GetPivot)
  1165. ->Event("SetPivot", &UiTransformBus::Events::SetPivot)
  1166. ->Event("GetPivotX", &UiTransformBus::Events::GetPivotX)
  1167. ->Event("SetPivotX", &UiTransformBus::Events::SetPivotX)
  1168. ->Event("GetPivotY", &UiTransformBus::Events::GetPivotY)
  1169. ->Event("SetPivotY", &UiTransformBus::Events::SetPivotY)
  1170. ->Event("GetScaleToDeviceMode", &UiTransformBus::Events::GetScaleToDeviceMode)
  1171. ->Event("SetScaleToDeviceMode", &UiTransformBus::Events::SetScaleToDeviceMode)
  1172. ->Event("GetViewportPosition", &UiTransformBus::Events::GetViewportPosition)
  1173. ->Event("SetViewportPosition", &UiTransformBus::Events::SetViewportPosition)
  1174. ->Event("GetCanvasPosition", &UiTransformBus::Events::GetCanvasPosition)
  1175. ->Event("SetCanvasPosition", &UiTransformBus::Events::SetCanvasPosition)
  1176. ->Event("GetLocalPosition", &UiTransformBus::Events::GetLocalPosition)
  1177. ->Event("SetLocalPosition", &UiTransformBus::Events::SetLocalPosition)
  1178. ->Event("GetLocalPositionX", &UiTransformBus::Events::GetLocalPositionX)
  1179. ->Event("SetLocalPositionX", &UiTransformBus::Events::SetLocalPositionX)
  1180. ->Event("GetLocalPositionY", &UiTransformBus::Events::GetLocalPositionY)
  1181. ->Event("SetLocalPositionY", &UiTransformBus::Events::SetLocalPositionY)
  1182. ->Event("MoveViewportPositionBy", &UiTransformBus::Events::MoveViewportPositionBy)
  1183. ->Event("MoveCanvasPositionBy", &UiTransformBus::Events::MoveCanvasPositionBy)
  1184. ->Event("MoveLocalPositionBy", &UiTransformBus::Events::MoveLocalPositionBy)
  1185. ->VirtualProperty("ScaleX", "GetScaleX", "SetScaleX")
  1186. ->VirtualProperty("ScaleY", "GetScaleY", "SetScaleY")
  1187. ->VirtualProperty("PivotX", "GetPivotX", "SetPivotX")
  1188. ->VirtualProperty("PivotY", "GetPivotY", "SetPivotY")
  1189. ->VirtualProperty("LocalPositionX", "GetLocalPositionX", "SetLocalPositionX")
  1190. ->VirtualProperty("LocalPositionY", "GetLocalPositionY", "SetLocalPositionY")
  1191. ->VirtualProperty("Rotation", "GetZRotation", "SetZRotation");
  1192. behaviorContext->EBus<UiTransform2dBus>("UiTransform2dBus")
  1193. ->Event("GetAnchors", &UiTransform2dBus::Events::GetAnchors)
  1194. ->Event("SetAnchors", &UiTransform2dBus::Events::SetAnchors)
  1195. ->Event("GetOffsets", &UiTransform2dBus::Events::GetOffsets)
  1196. ->Event("SetOffsets", &UiTransform2dBus::Events::SetOffsets)
  1197. ->Event("SetPivotAndAdjustOffsets", &UiTransform2dBus::Events::SetPivotAndAdjustOffsets)
  1198. ->Event("GetLocalWidth", &UiTransform2dBus::Events::GetLocalWidth)
  1199. ->Event("SetLocalWidth", &UiTransform2dBus::Events::SetLocalWidth)
  1200. ->Event("GetLocalHeight", &UiTransform2dBus::Events::GetLocalHeight)
  1201. ->Event("SetLocalHeight", &UiTransform2dBus::Events::SetLocalHeight)
  1202. ->VirtualProperty("LocalWidth", "GetLocalWidth", "SetLocalWidth")
  1203. ->VirtualProperty("LocalHeight", "GetLocalHeight", "SetLocalHeight");
  1204. behaviorContext->Class<UiTransform2dComponent>()
  1205. ->RequestBus("UiTransformBus")
  1206. ->RequestBus("UiTransform2dBus");
  1207. }
  1208. }
  1209. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1210. // PROTECTED MEMBER FUNCTIONS
  1211. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1212. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1213. void UiTransform2dComponent::Activate()
  1214. {
  1215. UiTransformBus::Handler::BusConnect(m_entity->GetId());
  1216. UiTransform2dBus::Handler::BusConnect(m_entity->GetId());
  1217. UiAnimateEntityBus::Handler::BusConnect(m_entity->GetId());
  1218. if (!m_elementComponent)
  1219. {
  1220. m_elementComponent = GetEntity()->FindComponent<UiElementComponent>();
  1221. }
  1222. }
  1223. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1224. void UiTransform2dComponent::Deactivate()
  1225. {
  1226. UiTransformBus::Handler::BusDisconnect();
  1227. UiTransform2dBus::Handler::BusDisconnect();
  1228. UiAnimateEntityBus::Handler::BusDisconnect();
  1229. }
  1230. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1231. bool UiTransform2dComponent::IsControlledByParent() const
  1232. {
  1233. bool isControlledByParent = false;
  1234. if (IsFullyInitialized())
  1235. {
  1236. AZ::Entity* parentElement = GetElementComponent()->GetParent();
  1237. if (parentElement)
  1238. {
  1239. UiLayoutBus::EventResult(isControlledByParent, parentElement->GetId(), &UiLayoutBus::Events::IsControllingChild, GetEntityId());
  1240. }
  1241. }
  1242. else
  1243. {
  1244. EmitNotInitializedWarning();
  1245. }
  1246. return isControlledByParent;
  1247. }
  1248. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1249. UiLayoutFitterInterface::FitType UiTransform2dComponent::GetLayoutFitterType() const
  1250. {
  1251. UiLayoutFitterInterface::FitType fitType = UiLayoutFitterInterface::FitType::None;
  1252. UiLayoutFitterBus::EventResult(fitType, GetEntityId(), &UiLayoutFitterBus::Events::GetFitType);
  1253. return fitType;
  1254. }
  1255. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1256. bool UiTransform2dComponent::IsNotControlledByParent() const
  1257. {
  1258. return !IsControlledByParent();
  1259. }
  1260. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1261. AZ::EntityId UiTransform2dComponent::GetAncestorWithSameDimensionScaleToDevice(ScaleToDeviceMode scaleToDeviceMode) const
  1262. {
  1263. AZ::EntityId ancestor;
  1264. if (IsFullyInitialized())
  1265. {
  1266. AZ::EntityId prevParent;
  1267. AZ::EntityId parent;
  1268. UiElementBus::EventResult(parent, GetEntityId(), &UiElementBus::Events::GetParentEntityId);
  1269. while (parent.IsValid())
  1270. {
  1271. ScaleToDeviceMode parentScaleToDeviceMode = ScaleToDeviceMode::None;
  1272. UiTransformBus::EventResult(parentScaleToDeviceMode, parent, &UiTransformBus::Events::GetScaleToDeviceMode);
  1273. if (parentScaleToDeviceMode != ScaleToDeviceMode::None)
  1274. {
  1275. if ((DoesScaleToDeviceModeAffectX(scaleToDeviceMode) && DoesScaleToDeviceModeAffectX(parentScaleToDeviceMode))
  1276. || (DoesScaleToDeviceModeAffectY(scaleToDeviceMode) && DoesScaleToDeviceModeAffectY(parentScaleToDeviceMode)))
  1277. {
  1278. ancestor = parent;
  1279. break;
  1280. }
  1281. }
  1282. prevParent = parent;
  1283. parent.SetInvalid();
  1284. UiElementBus::EventResult(parent, prevParent, &UiElementBus::Events::GetParentEntityId);
  1285. }
  1286. }
  1287. else
  1288. {
  1289. EmitNotInitializedWarning();
  1290. }
  1291. return ancestor;
  1292. }
  1293. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1294. LyShine::EntityArray UiTransform2dComponent::GetDescendantsWithSameDimensionScaleToDevice(ScaleToDeviceMode scaleToDeviceMode) const
  1295. {
  1296. // Check if any descendants have their scale to device mode set in the same dimension
  1297. auto HasSameDimensionScaleToDevice = [scaleToDeviceMode](const AZ::Entity* entity)
  1298. {
  1299. ScaleToDeviceMode descendantScaleToDeviceMode = ScaleToDeviceMode::None;
  1300. UiTransformBus::EventResult(descendantScaleToDeviceMode, entity->GetId(), &UiTransformBus::Events::GetScaleToDeviceMode);
  1301. return ((DoesScaleToDeviceModeAffectX(descendantScaleToDeviceMode) && DoesScaleToDeviceModeAffectX(scaleToDeviceMode))
  1302. || (DoesScaleToDeviceModeAffectY(descendantScaleToDeviceMode) && DoesScaleToDeviceModeAffectY(scaleToDeviceMode)));
  1303. };
  1304. LyShine::EntityArray descendants;
  1305. UiElementBus::Event(GetEntityId(), &UiElementBus::Events::FindDescendantElements, HasSameDimensionScaleToDevice, descendants);
  1306. return descendants;
  1307. }
  1308. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1309. bool UiTransform2dComponent::AreAnchorsApartInSameScaleToDeviceDimension(ScaleToDeviceMode scaleToDeviceMode) const
  1310. {
  1311. return ((m_anchors.m_left != m_anchors.m_right && DoesScaleToDeviceModeAffectX(scaleToDeviceMode))
  1312. || (m_anchors.m_top != m_anchors.m_bottom && DoesScaleToDeviceModeAffectY(scaleToDeviceMode)));
  1313. }
  1314. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1315. AZStd::string UiTransform2dComponent::GetScaleToDeviceModeWarningText() const
  1316. {
  1317. AZStd::string warningText;
  1318. if (m_scaleToDeviceMode != ScaleToDeviceMode::None)
  1319. {
  1320. // Check if anchors are apart in the same dimension as the scale to device mode
  1321. if (AreAnchorsApartInSameScaleToDeviceDimension(m_scaleToDeviceMode))
  1322. {
  1323. warningText = AZStd::string::format("Element's anchors are not together");
  1324. }
  1325. if (warningText.empty())
  1326. {
  1327. // Check if any ancestors already have their scale to device mode set in the same dimension
  1328. AZ::EntityId ancestor = GetAncestorWithSameDimensionScaleToDevice(m_scaleToDeviceMode);
  1329. if (ancestor.IsValid())
  1330. {
  1331. warningText = AZStd::string::format("Element will be double scaled");
  1332. }
  1333. }
  1334. if (warningText.empty())
  1335. {
  1336. // Check if any descendants have their scale to device mode set in the same dimension
  1337. LyShine::EntityArray descendants = GetDescendantsWithSameDimensionScaleToDevice(m_scaleToDeviceMode);
  1338. if (!descendants.empty())
  1339. {
  1340. warningText = AZStd::string::format("Descendants will be double scaled");
  1341. }
  1342. }
  1343. }
  1344. return warningText;
  1345. }
  1346. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1347. AZStd::string UiTransform2dComponent::GetScaleToDeviceModeWarningTooltipText() const
  1348. {
  1349. AZStd::string warningTooltipText;
  1350. if (m_scaleToDeviceMode != ScaleToDeviceMode::None)
  1351. {
  1352. // Check if anchors are apart in the same dimension as the scale to device mode
  1353. if (AreAnchorsApartInSameScaleToDeviceDimension(m_scaleToDeviceMode))
  1354. {
  1355. warningTooltipText = AZStd::string::format("This scale to device mode affects the same dimension as the element's anchors that are not together. This will result in undesired behavior.");
  1356. }
  1357. if (warningTooltipText.empty())
  1358. {
  1359. // Check if any ancestors already have their scale to device mode set in the same dimension
  1360. AZ::EntityId ancestor = GetAncestorWithSameDimensionScaleToDevice(m_scaleToDeviceMode);
  1361. if (ancestor.IsValid())
  1362. {
  1363. const char* ancestorName = "";
  1364. AZ::Entity* ancestorEntity = nullptr;
  1365. AZ::ComponentApplicationBus::BroadcastResult(ancestorEntity, &AZ::ComponentApplicationBus::Events::FindEntity, ancestor);
  1366. if (ancestorEntity)
  1367. {
  1368. ancestorName = ancestorEntity->GetName().c_str();
  1369. }
  1370. warningTooltipText = AZStd::string::format("This element has an ancestor called \"%s\" who's scale to device mode affects the same dimension. This will result in double scaling.", ancestorName);
  1371. }
  1372. }
  1373. if (warningTooltipText.empty())
  1374. {
  1375. // Check if any descendants have their scale to device mode set in the same dimension
  1376. LyShine::EntityArray descendants = GetDescendantsWithSameDimensionScaleToDevice(m_scaleToDeviceMode);
  1377. if (!descendants.empty())
  1378. {
  1379. warningTooltipText = AZStd::string::format("This element has at least one descendant who's scale to device mode affects the same dimension. This will result in double scaling for those descendants.");
  1380. }
  1381. }
  1382. }
  1383. return warningTooltipText;
  1384. }
  1385. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1386. const char* UiTransform2dComponent::GetAnchorPropertyLabel() const
  1387. {
  1388. const char* label = "Anchors";
  1389. if (IsControlledByParent())
  1390. {
  1391. label = "Disabled";
  1392. }
  1393. return label;
  1394. }
  1395. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1396. AZ::EntityId UiTransform2dComponent::GetCanvasEntityId()
  1397. {
  1398. AZ::EntityId canvasEntityId;
  1399. if (m_elementComponent)
  1400. {
  1401. m_elementComponent->GetCanvasEntityId();
  1402. }
  1403. else
  1404. {
  1405. UiElementBus::EventResult(canvasEntityId, GetEntityId(), &UiElementBus::Events::GetCanvasEntityId);
  1406. }
  1407. return canvasEntityId;
  1408. }
  1409. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1410. UiCanvasComponent* UiTransform2dComponent::GetCanvasComponent() const
  1411. {
  1412. return GetElementComponent()->GetCanvasComponent();
  1413. }
  1414. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1415. void UiTransform2dComponent::OnTransformPropertyChanged()
  1416. {
  1417. SetRecomputeFlags(UiTransformInterface::Recompute::TransformOnly);
  1418. }
  1419. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1420. void UiTransform2dComponent::RecomputeTransformToViewportIfNeeded()
  1421. {
  1422. // if we already computed the transform, don't recompute.
  1423. if (!m_recomputeTransformToViewport)
  1424. {
  1425. return;
  1426. }
  1427. // first get the transform to canvas space
  1428. RecomputeTransformToCanvasSpaceIfNeeded();
  1429. // then get the transform from canvas to viewport space
  1430. AZ::Matrix4x4 canvasToViewportMatrix;
  1431. if (IsFullyInitialized())
  1432. {
  1433. canvasToViewportMatrix = GetCanvasComponent()->GetCanvasToViewportMatrix();
  1434. }
  1435. else
  1436. {
  1437. EmitNotInitializedWarning();
  1438. canvasToViewportMatrix = AZ::Matrix4x4::CreateIdentity();
  1439. }
  1440. // add the transform to viewport space to the matrix
  1441. m_transformToViewport = canvasToViewportMatrix * m_transformToCanvasSpace;
  1442. m_recomputeTransformToViewport = false;
  1443. UiTransformChangeNotificationBus::Event(GetEntityId(), &UiTransformChangeNotificationBus::Events::OnTransformToViewportChanged);
  1444. }
  1445. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1446. void UiTransform2dComponent::RecomputeTransformToCanvasSpaceIfNeeded()
  1447. {
  1448. // if we already computed the transform, don't recompute.
  1449. if (!m_recomputeTransformToCanvasSpace)
  1450. {
  1451. return;
  1452. }
  1453. // this takes a matrix and builds the concatenation of this elements rotate and scale about the pivot
  1454. // with the transforms for all parent elements into one 3x4 matrix.
  1455. UiTransform2dComponent* parentTransformComponent = GetParentTransformComponent();
  1456. if (parentTransformComponent)
  1457. {
  1458. parentTransformComponent->GetTransformToCanvasSpace(m_transformToCanvasSpace);
  1459. AZ::Matrix4x4 transformToParent;
  1460. if (HasScaleOrRotation())
  1461. {
  1462. GetLocalTransform(transformToParent);
  1463. }
  1464. else
  1465. {
  1466. transformToParent = AZ::Matrix4x4::CreateIdentity();
  1467. }
  1468. m_transformToCanvasSpace = m_transformToCanvasSpace * transformToParent;
  1469. }
  1470. else
  1471. {
  1472. m_transformToCanvasSpace = AZ::Matrix4x4::CreateIdentity();
  1473. }
  1474. m_recomputeTransformToCanvasSpace = false;
  1475. }
  1476. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1477. AZ::Vector2 UiTransform2dComponent::GetScaleAdjustedForDevice()
  1478. {
  1479. AZ::Vector2 scale = m_scale;
  1480. if (m_scaleToDeviceMode != ScaleToDeviceMode::None)
  1481. {
  1482. if (IsFullyInitialized())
  1483. {
  1484. ApplyDeviceScale(scale);
  1485. }
  1486. else
  1487. {
  1488. EmitNotInitializedWarning();
  1489. }
  1490. }
  1491. return scale;
  1492. }
  1493. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1494. void UiTransform2dComponent::CalculateCanvasSpaceRect()
  1495. {
  1496. if (!m_recomputeCanvasSpaceRect)
  1497. {
  1498. return;
  1499. }
  1500. Rect rect;
  1501. UiTransform2dComponent* parentTransformComponent = GetParentTransformComponent();
  1502. if (parentTransformComponent)
  1503. {
  1504. Rect parentRect;
  1505. parentTransformComponent->GetCanvasSpaceRectNoScaleRotate(parentRect);
  1506. AZ::Vector2 parentSize = parentRect.GetSize();
  1507. float left = parentRect.left + parentSize.GetX() * m_anchors.m_left + m_offsets.m_left;
  1508. float right = parentRect.left + parentSize.GetX() * m_anchors.m_right + m_offsets.m_right;
  1509. float top = parentRect.top + parentSize.GetY() * m_anchors.m_top + m_offsets.m_top;
  1510. float bottom = parentRect.top + parentSize.GetY() * m_anchors.m_bottom + m_offsets.m_bottom;
  1511. rect.Set(left, right, top, bottom);
  1512. }
  1513. else
  1514. {
  1515. // this is the root element, its offset and anchors are ignored
  1516. AZ::Vector2 size = UiCanvasComponent::s_defaultCanvasSize;
  1517. if (IsFullyInitialized())
  1518. {
  1519. size = GetCanvasComponent()->GetCanvasSize();
  1520. }
  1521. else
  1522. {
  1523. EmitNotInitializedWarning();
  1524. }
  1525. rect.Set(0.0f, size.GetX(), 0.0f, size.GetY());
  1526. }
  1527. // we never return a "flipped" rect. I.e. left is always less than right, top is always less than bottom
  1528. // if it is flipped in a dimension then we make it zero size in that dimension
  1529. if (rect.left > rect.right)
  1530. {
  1531. rect.left = rect.right = rect.GetCenterX();
  1532. }
  1533. if (rect.top > rect.bottom)
  1534. {
  1535. rect.top = rect.bottom = rect.GetCenterY();
  1536. }
  1537. m_rect = rect;
  1538. if (!m_rectInitialized)
  1539. {
  1540. m_prevRect = m_rect;
  1541. m_rectChangedByInitialization = true;
  1542. m_rectInitialized = true;
  1543. }
  1544. else
  1545. {
  1546. // If the rect is being changed after it was initialized, but before the first
  1547. // update, keep prev rect in sync with current rect. On a canvas space rect
  1548. // change callback, prev rect and current rect can be used to determine whether
  1549. // the canvas rect size has changed. Equal rects implies a change due to initialization
  1550. if (m_rectChangedByInitialization)
  1551. {
  1552. m_prevRect = m_rect;
  1553. }
  1554. }
  1555. m_recomputeCanvasSpaceRect = false;
  1556. }
  1557. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1558. AZ::Vector2 UiTransform2dComponent::GetCanvasSpaceAnchorsCenterNoScaleRotate()
  1559. {
  1560. // Get the position of the element's anchors in canvas space
  1561. UiTransform2dComponent* parentTransformComponent = GetParentTransformComponent();
  1562. if (!parentTransformComponent)
  1563. {
  1564. return AZ::Vector2(0.0f, 0.0f); // this is the root element
  1565. }
  1566. // Get parent's rect in canvas space
  1567. UiTransformInterface::Rect parentRect;
  1568. parentTransformComponent->GetCanvasSpaceRectNoScaleRotate(parentRect);
  1569. // Get the anchor center in canvas space
  1570. UiTransformInterface::Rect anchorRect;
  1571. anchorRect.left = parentRect.left + m_anchors.m_left * parentRect.GetWidth();
  1572. anchorRect.right = parentRect.left + m_anchors.m_right * parentRect.GetWidth();
  1573. anchorRect.top = parentRect.top + m_anchors.m_top * parentRect.GetHeight();
  1574. anchorRect.bottom = parentRect.top + m_anchors.m_bottom * parentRect.GetHeight();
  1575. return anchorRect.GetCenter();
  1576. }
  1577. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1578. UiElementComponent* UiTransform2dComponent::GetElementComponent() const
  1579. {
  1580. AZ_Assert(m_elementComponent, "UiTransform2dComponent: m_elementComponent used when not initialized");
  1581. return m_elementComponent;
  1582. }
  1583. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1584. UiTransform2dComponent* UiTransform2dComponent::GetParentTransformComponent() const
  1585. {
  1586. if (IsFullyInitialized())
  1587. {
  1588. UiElementComponent* parentElementComponent = GetElementComponent()->GetParentElementComponent();
  1589. if (parentElementComponent)
  1590. {
  1591. return parentElementComponent->GetTransform2dComponent();
  1592. }
  1593. }
  1594. else
  1595. {
  1596. EmitNotInitializedWarning();
  1597. }
  1598. return nullptr;
  1599. }
  1600. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1601. UiTransform2dComponent* UiTransform2dComponent::GetChildTransformComponent(int index) const
  1602. {
  1603. if (IsFullyInitialized())
  1604. {
  1605. UiElementComponent* childElementComponent = GetElementComponent()->GetChildElementComponent(index);
  1606. if (childElementComponent)
  1607. {
  1608. return childElementComponent->GetTransform2dComponent();
  1609. }
  1610. }
  1611. else
  1612. {
  1613. EmitNotInitializedWarning();
  1614. }
  1615. return nullptr;
  1616. }
  1617. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1618. bool UiTransform2dComponent::IsFullyInitialized() const
  1619. {
  1620. return (m_elementComponent && m_elementComponent->IsFullyInitialized());
  1621. }
  1622. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1623. void UiTransform2dComponent::EmitNotInitializedWarning() const
  1624. {
  1625. AZ_Warning("UI", false, "UiTransform2dComponent used before fully initialized, possibly on activate before FixupPostLoad was called on this element")
  1626. }
  1627. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1628. void UiTransform2dComponent::ApplyDeviceScale(AZ::Vector2& scale)
  1629. {
  1630. AZ::Vector2 deviceScale = GetCanvasComponent()->GetDeviceScale();
  1631. switch (m_scaleToDeviceMode)
  1632. {
  1633. case ScaleToDeviceMode::UniformScaleToFit:
  1634. {
  1635. float uniformScale = AZStd::min(deviceScale.GetX(), deviceScale.GetY());
  1636. scale *= uniformScale;
  1637. break;
  1638. }
  1639. case ScaleToDeviceMode::UniformScaleToFill:
  1640. {
  1641. float uniformScale = AZStd::max(deviceScale.GetX(), deviceScale.GetY());
  1642. scale *= uniformScale;
  1643. break;
  1644. }
  1645. case ScaleToDeviceMode::UniformScaleToFitX:
  1646. {
  1647. float uniformScale = deviceScale.GetX();
  1648. scale *= uniformScale;
  1649. break;
  1650. }
  1651. case ScaleToDeviceMode::UniformScaleToFitY:
  1652. {
  1653. float uniformScale = deviceScale.GetY();
  1654. scale *= uniformScale;
  1655. break;
  1656. }
  1657. case ScaleToDeviceMode::NonUniformScale:
  1658. scale *= deviceScale;
  1659. break;
  1660. case ScaleToDeviceMode::ScaleXOnly:
  1661. scale.SetX(scale.GetX() * deviceScale.GetX());
  1662. break;
  1663. case ScaleToDeviceMode::ScaleYOnly:
  1664. scale.SetY(scale.GetY() * deviceScale.GetY());
  1665. break;
  1666. }
  1667. }
  1668. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1669. bool UiTransform2dComponent::VersionConverter(AZ::SerializeContext& context,
  1670. AZ::SerializeContext::DataElementNode& classElement)
  1671. {
  1672. // conversion from version 1:
  1673. // - Need to convert Vec2 to AZ::Vector2
  1674. if (classElement.GetVersion() <= 1)
  1675. {
  1676. if (!LyShine::ConvertSubElementFromVec2ToVector2(context, classElement, "Pivot"))
  1677. {
  1678. return false;
  1679. }
  1680. if (!LyShine::ConvertSubElementFromVec2ToVector2(context, classElement, "Scale"))
  1681. {
  1682. return false;
  1683. }
  1684. }
  1685. // conversion from version 2:
  1686. // - Need to convert ScaleToDevice from a bool to an enum
  1687. if (classElement.GetVersion() <= 2)
  1688. {
  1689. if (!ConvertScaleToDeviceFromBoolToEnum(context, classElement))
  1690. {
  1691. return false;
  1692. }
  1693. }
  1694. return true;
  1695. }
  1696. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1697. bool UiTransform2dComponent::DoesScaleToDeviceModeAffectX(ScaleToDeviceMode scaleToDeviceMode)
  1698. {
  1699. return (scaleToDeviceMode != ScaleToDeviceMode::None && scaleToDeviceMode != ScaleToDeviceMode::ScaleYOnly);
  1700. }
  1701. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1702. bool UiTransform2dComponent::DoesScaleToDeviceModeAffectY(ScaleToDeviceMode scaleToDeviceMode)
  1703. {
  1704. return (scaleToDeviceMode != ScaleToDeviceMode::None && scaleToDeviceMode != ScaleToDeviceMode::ScaleXOnly);
  1705. }
  1706. #include "Tests/internal/test_UiTransform2dComponent.cpp"