ViewportMoveInteraction.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  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 "EditorCommon.h"
  9. #include "ViewportMoveInteraction.h"
  10. #include <LyShine/Bus/UiEditorCanvasBus.h>
  11. ViewportMoveInteraction::ViewportMoveInteraction(
  12. HierarchyWidget* hierarchy,
  13. const QTreeWidgetItemRawPtrQList& selectedItems,
  14. AZ::EntityId canvasId,
  15. AZ::Entity* activeElement,
  16. ViewportInteraction::CoordinateSystem coordinateSystem,
  17. ViewportHelpers::GizmoParts grabbedGizmoParts,
  18. ViewportInteraction::InteractionMode interactionMode,
  19. ViewportInteraction::InteractionType interactionType,
  20. const AZ::Vector2& startDragMousePos
  21. )
  22. : ViewportDragInteraction(startDragMousePos)
  23. , m_canvasId(canvasId)
  24. , m_coordinateSystem(coordinateSystem)
  25. , m_grabbedGizmoParts(grabbedGizmoParts)
  26. , m_interactionMode(interactionMode)
  27. , m_interactionType(interactionType)
  28. {
  29. LyShine::EntityArray topLevelSelectedElements = SelectionHelpers::GetTopLevelSelectedElements(hierarchy, selectedItems);
  30. // store the starting anchors or offsets (depending on the interaction mode)
  31. if (m_interactionMode == ViewportInteraction::InteractionMode::MOVE)
  32. {
  33. for (auto element : topLevelSelectedElements)
  34. {
  35. UiTransform2dInterface::Offsets offsets;
  36. UiTransform2dBus::EventResult(offsets, element->GetId(), &UiTransform2dBus::Events::GetOffsets);
  37. m_startingOffsets[element->GetId()] = offsets;
  38. }
  39. }
  40. else
  41. {
  42. for (auto element : topLevelSelectedElements)
  43. {
  44. UiTransform2dInterface::Anchors anchors;
  45. UiTransform2dBus::EventResult(anchors, element->GetId(), &UiTransform2dBus::Events::GetAnchors);
  46. m_startingAnchors[element->GetId()] = anchors;
  47. }
  48. }
  49. // the primary element is usually the active element (the one being clicked on and dragged),
  50. // but if a parent of the active element is also selected it is the top-level selected ancestor of the active element
  51. m_primaryElement = SelectionHelpers::GetTopLevelParentOfElement(topLevelSelectedElements, activeElement);
  52. if (m_primaryElement)
  53. {
  54. // remove the primary element from the array
  55. SelectionHelpers::RemoveEntityFromArray(topLevelSelectedElements, m_primaryElement);
  56. // store the top-level selected elements that are not the primary element - these will just follow along with
  57. // how the primary element is moved
  58. m_secondarySelectedElements = topLevelSelectedElements;
  59. // store whether snapping is enabled for this canvas
  60. UiEditorCanvasBus::EventResult(m_isSnapping, canvasId, &UiEditorCanvasBus::Events::GetIsSnapEnabled);
  61. // remember the parent of the primary element also
  62. m_primaryElementParent = EntityHelpers::GetParentElement(m_primaryElement);
  63. // store the starting pivots of the primary element for snapping (in local and canvas space)
  64. m_startingPrimaryLocalPivot = GetPivotRelativeToTopLeftAnchor(m_primaryElement->GetId());
  65. UiTransformBus::EventResult(
  66. m_startingPrimaryCanvasSpacePivot, m_primaryElement->GetId(), &UiTransformBus::Events::GetCanvasSpacePivot);
  67. }
  68. else
  69. {
  70. // This should never happen but when we had an assert here it was occasionally hit but not in a reproducable way.
  71. // It is recoverable so we don't want to crash if this happens. Report a warning and do not crash.
  72. AZ_Warning("UI", false, "The active element is not one of the selected elements. Active element is '%s'. There are %d selected elements and %d top level selected elements.",
  73. activeElement ? activeElement->GetName().c_str() : "None", selectedItems.count(), topLevelSelectedElements.size());
  74. }
  75. }
  76. ViewportMoveInteraction::~ViewportMoveInteraction()
  77. {
  78. }
  79. void ViewportMoveInteraction::Update(const AZ::Vector2& mousePos)
  80. {
  81. // If there is no primary element (should never happen) or if the primary element is controlled by a layout component
  82. // and therefore not movable, then do nothing
  83. if (!m_primaryElement || ViewportHelpers::IsControlledByLayout(m_primaryElement))
  84. {
  85. return;
  86. }
  87. // compute the total mouse delta since the start of the interaction
  88. AZ::Vector2 mouseDelta = mousePos - m_startMousePos;
  89. // Constrain this delta based on gizmo and coordinate space
  90. AZ::Vector2 canvasSpaceMouseDelta;
  91. AZ::Vector2 localMouseDelta;
  92. bool restrictDirection = ConstrainMovementDirection(mouseDelta, canvasSpaceMouseDelta, localMouseDelta);
  93. // adjust the mouse deltas for snapping
  94. SnapMouseDeltas(canvasSpaceMouseDelta, localMouseDelta);
  95. // Move the primary element
  96. MovePrimaryElement(restrictDirection, canvasSpaceMouseDelta, localMouseDelta);
  97. // Move each of the secondary elements by the same delta
  98. for (auto element : m_secondarySelectedElements)
  99. {
  100. if (!ViewportHelpers::IsControlledByLayout(element))
  101. {
  102. MoveSecondaryElement(element, restrictDirection, canvasSpaceMouseDelta);
  103. }
  104. }
  105. }
  106. bool ViewportMoveInteraction::ConstrainMovementDirection(const AZ::Vector2& mouseDelta, AZ::Vector2& canvasSpaceMouseDelta, AZ::Vector2& localMouseDelta)
  107. {
  108. bool restrictDirection = false;
  109. canvasSpaceMouseDelta = EntityHelpers::TransformDeltaFromViewportToCanvasSpace(m_canvasId, mouseDelta);
  110. if (m_interactionType == ViewportInteraction::InteractionType::TRANSFORM_GIZMO && m_grabbedGizmoParts.Single())
  111. {
  112. restrictDirection = true;
  113. if (m_coordinateSystem == ViewportInteraction::CoordinateSystem::LOCAL)
  114. {
  115. // For LOCAL space, we need to transform the translation from canvas space to the parent element's local space
  116. localMouseDelta = EntityHelpers::TransformDeltaFromCanvasToLocalSpace(m_primaryElementParent->GetId(), canvasSpaceMouseDelta);
  117. // Zero-out the non-moving axis in local space.
  118. if (!m_grabbedGizmoParts.m_right)
  119. {
  120. localMouseDelta.SetX(0.0f);
  121. }
  122. if (!m_grabbedGizmoParts.m_top)
  123. {
  124. localMouseDelta.SetY(0.0f);
  125. }
  126. // now convert back to canvas space
  127. canvasSpaceMouseDelta = EntityHelpers::TransformDeltaFromLocalToCanvasSpace(m_primaryElementParent->GetId(), localMouseDelta);
  128. }
  129. else // if (coordinateSystem == ViewportInteraction::CoordinateSystem::VIEW)
  130. {
  131. // Zero-out the non-moving axis in viewport space.
  132. if (!m_grabbedGizmoParts.m_right)
  133. {
  134. canvasSpaceMouseDelta.SetX(0.0f);
  135. }
  136. if (!m_grabbedGizmoParts.m_top)
  137. {
  138. canvasSpaceMouseDelta.SetY(0.0f);
  139. }
  140. // For LOCAL space, we need to transform the translation from canvas space to the parent element's local space
  141. localMouseDelta = EntityHelpers::TransformDeltaFromCanvasToLocalSpace(m_primaryElementParent->GetId(), canvasSpaceMouseDelta);
  142. }
  143. }
  144. else
  145. {
  146. // Not single gizmo - just convert canvas translation to local
  147. localMouseDelta = EntityHelpers::TransformDeltaFromCanvasToLocalSpace(m_primaryElementParent->GetId(), canvasSpaceMouseDelta);
  148. }
  149. return restrictDirection;
  150. }
  151. void ViewportMoveInteraction::SnapMouseDeltas(AZ::Vector2& canvasSpaceMouseDelta, AZ::Vector2& localMouseDelta)
  152. {
  153. if (m_isSnapping)
  154. {
  155. float snapDistance = 1.0f;
  156. UiEditorCanvasBus::EventResult(snapDistance, m_canvasId, &UiEditorCanvasBus::Events::GetSnapDistance);
  157. if (m_coordinateSystem == ViewportInteraction::CoordinateSystem::LOCAL)
  158. {
  159. // calculate what the pivot of the active element should be (ignoring snapping)
  160. AZ::Vector2 translatedLocalPivot = m_startingPrimaryLocalPivot + localMouseDelta;
  161. // snap the position of the pivot
  162. AZ::Vector2 snappedPivot = EntityHelpers::Snap(translatedLocalPivot, snapDistance);
  163. AZ::Vector2 localSnapAdjustment = snappedPivot - translatedLocalPivot;
  164. // adjust the local translation to allow for snapping
  165. if (m_grabbedGizmoParts.Single())
  166. {
  167. if (m_grabbedGizmoParts.m_right)
  168. {
  169. localMouseDelta.SetX(localMouseDelta.GetX() + localSnapAdjustment.GetX());
  170. }
  171. if (m_grabbedGizmoParts.m_top)
  172. {
  173. localMouseDelta.SetY(localMouseDelta.GetY() + localSnapAdjustment.GetY());
  174. }
  175. }
  176. else
  177. {
  178. localMouseDelta += localSnapAdjustment;
  179. }
  180. // Compute a canvas space delta based on local delta
  181. canvasSpaceMouseDelta = EntityHelpers::TransformDeltaFromLocalToCanvasSpace(m_primaryElementParent->GetId(), localMouseDelta);
  182. }
  183. else
  184. {
  185. // calculate what the pivot of the active element should be (ignoring snapping)
  186. AZ::Vector2 translatedCanvasSpacePivot = m_startingPrimaryCanvasSpacePivot + canvasSpaceMouseDelta;
  187. // snap the position of the pivot
  188. AZ::Vector2 snappedPivot = EntityHelpers::Snap(translatedCanvasSpacePivot, snapDistance);
  189. AZ::Vector2 canvasSpaceSnapAdjustment = snappedPivot - translatedCanvasSpacePivot;
  190. // adjust the local translation to allow for snapping
  191. if (m_grabbedGizmoParts.Single())
  192. {
  193. if (m_grabbedGizmoParts.m_right)
  194. {
  195. canvasSpaceMouseDelta.SetX(canvasSpaceMouseDelta.GetX() + canvasSpaceSnapAdjustment.GetX());
  196. }
  197. if (m_grabbedGizmoParts.m_top)
  198. {
  199. canvasSpaceMouseDelta.SetY(canvasSpaceMouseDelta.GetY() + canvasSpaceSnapAdjustment.GetY());
  200. }
  201. }
  202. else
  203. {
  204. canvasSpaceMouseDelta += canvasSpaceSnapAdjustment;
  205. }
  206. // Compute a local delta based on canvas space delta
  207. localMouseDelta = EntityHelpers::TransformDeltaFromCanvasToLocalSpace(m_primaryElementParent->GetId(), canvasSpaceMouseDelta);
  208. }
  209. }
  210. }
  211. void ViewportMoveInteraction::MovePrimaryElement(bool restrictDirection, AZ::Vector2& canvasSpaceMouseDelta, AZ::Vector2& localMouseDelta)
  212. {
  213. if (m_interactionMode == ViewportInteraction::InteractionMode::MOVE)
  214. {
  215. const UiTransform2dInterface::Offsets& startingOffsets = m_startingOffsets[m_primaryElement->GetId()];
  216. EntityHelpers::MoveByLocalDeltaUsingOffsets(m_primaryElement->GetId(), startingOffsets, localMouseDelta);
  217. }
  218. else if (m_interactionMode == ViewportInteraction::InteractionMode::ANCHOR)
  219. {
  220. const UiTransform2dInterface::Anchors& startingAnchors = m_startingAnchors[m_primaryElement->GetId()];
  221. AZ::Vector2 constrainedLocalTranslation = EntityHelpers::MoveByLocalDeltaUsingAnchors(m_primaryElement->GetId(), m_primaryElementParent->GetId(),
  222. startingAnchors, localMouseDelta, restrictDirection);
  223. if (constrainedLocalTranslation != localMouseDelta)
  224. {
  225. // The anchor limits prevented moving the active element the requested amount.
  226. // We want the secondary elements to move the same amount as the primary one.
  227. // NOTE: if the secondary elements hit an anchor limit they will be constrained but other elements will not - so relative positions
  228. // are not ALWAYS preserved.
  229. localMouseDelta = constrainedLocalTranslation;
  230. // Recompute the canvas space delta based on local delta
  231. canvasSpaceMouseDelta = EntityHelpers::TransformDeltaFromLocalToCanvasSpace(m_primaryElementParent->GetId(), localMouseDelta);
  232. }
  233. }
  234. UiElementChangeNotificationBus::Event(m_primaryElement->GetId(), &UiElementChangeNotificationBus::Events::UiElementPropertyChanged);
  235. }
  236. void ViewportMoveInteraction::MoveSecondaryElement(AZ::Entity* element, bool restrictDirection, const AZ::Vector2& canvasSpaceMouseDelta)
  237. {
  238. AZ::Entity* parentElement = EntityHelpers::GetParentElement(element);
  239. AZ::Vector2 localMouseDelta = EntityHelpers::TransformDeltaFromCanvasToLocalSpace(parentElement->GetId(), canvasSpaceMouseDelta);
  240. if (m_interactionMode == ViewportInteraction::InteractionMode::MOVE)
  241. {
  242. const UiTransform2dInterface::Offsets& startingOffsets = m_startingOffsets[element->GetId()];
  243. EntityHelpers::MoveByLocalDeltaUsingOffsets(element->GetId(), startingOffsets, localMouseDelta);
  244. }
  245. else if (m_interactionMode == ViewportInteraction::InteractionMode::ANCHOR)
  246. {
  247. const UiTransform2dInterface::Anchors& startingAnchors = m_startingAnchors[element->GetId()];
  248. EntityHelpers::MoveByLocalDeltaUsingAnchors(element->GetId(), parentElement->GetId(), startingAnchors, localMouseDelta, restrictDirection);
  249. }
  250. UiElementChangeNotificationBus::Event(element->GetId(), &UiElementChangeNotificationBus::Events::UiElementPropertyChanged);
  251. }
  252. AZ::Vector2 ViewportMoveInteraction::GetPivotRelativeToTopLeftAnchor(AZ::EntityId entityId)
  253. {
  254. UiTransform2dInterface::Offsets currentOffsets;
  255. UiTransform2dBus::EventResult(currentOffsets, entityId, &UiTransform2dBus::Events::GetOffsets);
  256. // Get the width and height in canvas space no scale rotate.
  257. AZ::Vector2 elementSize;
  258. UiTransformBus::EventResult(elementSize, entityId, &UiTransformBus::Events::GetCanvasSpaceSizeNoScaleRotate);
  259. AZ::Vector2 pivot;
  260. UiTransformBus::EventResult(pivot, entityId, &UiTransformBus::Events::GetPivot);
  261. AZ::Vector2 pivotRelativeToTopLeftAnchor(currentOffsets.m_left + (elementSize.GetX() * pivot.GetX()),
  262. currentOffsets.m_top + (elementSize.GetY() * pivot.GetY()));
  263. return pivotRelativeToTopLeftAnchor;
  264. }