UiDraggableComponent.cpp 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917
  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 "UiDraggableComponent.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 <AzCore/Component/ComponentApplicationBus.h>
  14. #include <LyShine/Bus/UiElementBus.h>
  15. #include <LyShine/Bus/UiTransformBus.h>
  16. #include <LyShine/Bus/UiCanvasBus.h>
  17. #include <LyShine/Bus/UiCanvasManagerBus.h>
  18. #include <LyShine/Bus/UiDropTargetBus.h>
  19. #include <LyShine/Bus/UiInteractionMaskBus.h>
  20. #include <LyShine/Bus/UiNavigationBus.h>
  21. #include "UiNavigationHelpers.h"
  22. ////////////////////////////////////////////////////////////////////////////////////////////////////
  23. //! UiDraggableNotificationBus Behavior context handler class
  24. class UiDraggableNotificationBusBehaviorHandler
  25. : public UiDraggableNotificationBus::Handler
  26. , public AZ::BehaviorEBusHandler
  27. {
  28. public:
  29. AZ_EBUS_BEHAVIOR_BINDER(UiDraggableNotificationBusBehaviorHandler, "{7EEA2A71-AB29-4F1D-AC76-4BE7237AB99B}", AZ::SystemAllocator,
  30. OnDragStart, OnDrag, OnDragEnd);
  31. void OnDragStart(AZ::Vector2 position) override
  32. {
  33. Call(FN_OnDragStart, position);
  34. }
  35. void OnDrag(AZ::Vector2 position) override
  36. {
  37. Call(FN_OnDrag, position);
  38. }
  39. void OnDragEnd(AZ::Vector2 position) override
  40. {
  41. Call(FN_OnDragEnd, position);
  42. }
  43. };
  44. ////////////////////////////////////////////////////////////////////////////////////////////////////
  45. // PUBLIC MEMBER FUNCTIONS
  46. ////////////////////////////////////////////////////////////////////////////////////////////////////
  47. ////////////////////////////////////////////////////////////////////////////////////////////////////
  48. UiDraggableComponent::UiDraggableComponent()
  49. {
  50. // Must be called in the same order as the states defined in UiDraggableInterface
  51. m_stateActionManager.AddState(&m_dragNormalStateActions);
  52. m_stateActionManager.AddState(&m_dragValidStateActions);
  53. m_stateActionManager.AddState(&m_dragInvalidStateActions);
  54. }
  55. ////////////////////////////////////////////////////////////////////////////////////////////////////
  56. UiDraggableComponent::~UiDraggableComponent()
  57. {
  58. // delete all the state actions now rather than letting the base class do it automatically
  59. // because the m_stateActionManager has pointers to members in this derived class.
  60. m_stateActionManager.ClearStates();
  61. }
  62. ////////////////////////////////////////////////////////////////////////////////////////////////////
  63. bool UiDraggableComponent::HandlePressed(AZ::Vector2 point, bool& shouldStayActive)
  64. {
  65. bool handled = UiInteractableComponent::HandlePressed(point, shouldStayActive);
  66. if (handled)
  67. {
  68. // NOTE: Drag start does not happen until the mouse actually starts moving so HandlePressed does
  69. // not do much. Reset these member variables just in case they did not get reset in end drag
  70. m_isDragging = false;
  71. m_dragState = DragState::Normal;
  72. m_hoverDropTarget.SetInvalid();
  73. }
  74. return handled;
  75. }
  76. ////////////////////////////////////////////////////////////////////////////////////////////////////
  77. bool UiDraggableComponent::HandleReleased(AZ::Vector2 point)
  78. {
  79. EndDragOperation(point, false);
  80. if (m_isPressed && m_isHandlingEvents)
  81. {
  82. UiInteractableComponent::TriggerReleasedAction();
  83. }
  84. m_isPressed = false;
  85. m_pressedPoint = AZ::Vector2(0.0f, 0.0f);
  86. return m_isHandlingEvents;
  87. }
  88. ////////////////////////////////////////////////////////////////////////////////////////////////////
  89. bool UiDraggableComponent::HandleEnterPressed(bool& shouldStayActive)
  90. {
  91. bool handled = UiInteractableComponent::HandleEnterPressed(shouldStayActive);
  92. if (handled)
  93. {
  94. AZ::Vector2 point(0.0f, 0.0f);
  95. UiTransformBus::EventResult(point, GetEntityId(), &UiTransformBus::Events::GetViewportSpacePivot);
  96. // if we are not yet in the dragging state do some tests to see if we should be
  97. if (!m_isDragging)
  98. {
  99. // the draggable will stay active after released so that arrow keys can be used to place it
  100. // over a drop target
  101. shouldStayActive = true;
  102. m_isActive = true;
  103. // the drag was valid for this draggable, we are now dragging
  104. m_isDragging = true;
  105. m_dragState = DragState::Normal;
  106. UiDraggableNotificationBus::QueueEvent(GetEntityId(), &UiDraggableNotificationBus::Events::OnDragStart, point);
  107. m_hoverDropTarget.SetInvalid();
  108. // find closest drop target to the draggable's center
  109. AZ::EntityId closestDropTarget = FindClosestNavigableDropTarget();
  110. if (closestDropTarget.IsValid())
  111. {
  112. UiTransformBus::EventResult(point, closestDropTarget, &UiTransformBus::Events::GetViewportPosition);
  113. }
  114. DoDrag(point, true);
  115. }
  116. }
  117. return handled;
  118. }
  119. /////////////////////////////////////////////////////////////////
  120. bool UiDraggableComponent::HandleKeyInputBegan(const AzFramework::InputChannel::Snapshot& inputSnapshot, AzFramework::ModifierKeyMask activeModifierKeys)
  121. {
  122. if (!m_isHandlingEvents)
  123. {
  124. return false;
  125. }
  126. // don't accept key input while in pressed state
  127. if (m_isPressed)
  128. {
  129. return false;
  130. }
  131. bool result = false;
  132. const UiNavigationHelpers::Command command = UiNavigationHelpers::MapInputChannelIdToUiNavigationCommand(inputSnapshot.m_channelId, activeModifierKeys);
  133. if (command == UiNavigationHelpers::Command::Up ||
  134. command == UiNavigationHelpers::Command::Down ||
  135. command == UiNavigationHelpers::Command::Left ||
  136. command == UiNavigationHelpers::Command::Right)
  137. {
  138. AZ::EntityId closestDropTarget = FindClosestNavigableDropTarget();
  139. AZ::EntityId newElement;
  140. if (m_hoverDropTarget.IsValid())
  141. {
  142. LyShine::EntityArray navigableElements;
  143. FindNavigableDropTargetElements(m_hoverDropTarget, navigableElements);
  144. auto isValidDropTarget = [](AZ::EntityId entityId)
  145. {
  146. bool isEnabled = false;
  147. UiElementBus::EventResult(isEnabled, entityId, &UiElementBus::Events::IsEnabled);
  148. if (isEnabled && UiDropTargetBus::FindFirstHandler(entityId))
  149. {
  150. return true;
  151. }
  152. return false;
  153. };
  154. newElement = UiNavigationHelpers::GetNextElement(m_hoverDropTarget, command,
  155. navigableElements, closestDropTarget, isValidDropTarget);
  156. }
  157. else
  158. {
  159. // find closest drop target to the draggable's center
  160. newElement = closestDropTarget;
  161. }
  162. if (newElement.IsValid())
  163. {
  164. AZ::Vector2 point(0.0f, 0.0f);
  165. UiTransformBus::EventResult(point, newElement, &UiTransformBus::Events::GetViewportSpacePivot);
  166. DoDrag(point, true);
  167. }
  168. result = true;
  169. }
  170. return result;
  171. }
  172. ////////////////////////////////////////////////////////////////////////////////////////////////////
  173. void UiDraggableComponent::InputPositionUpdate(AZ::Vector2 point)
  174. {
  175. if (m_isPressed)
  176. {
  177. // if we are not yet in the dragging state do some tests to see if we should be
  178. if (!m_isDragging)
  179. {
  180. bool handOffDone = false;
  181. bool dragDetected = CheckForDragOrHandOffToParent(GetEntityId(), m_pressedPoint, point, 0.0f, handOffDone);
  182. if (dragDetected)
  183. {
  184. if (handOffDone)
  185. {
  186. // the drag was handed off to a parent, this draggable is no longer active
  187. m_isPressed = false;
  188. }
  189. else
  190. {
  191. // the drag was valid for this draggable, we are now dragging
  192. m_isDragging = true;
  193. m_dragState = DragState::Normal;
  194. UiDraggableNotificationBus::QueueEvent(GetEntityId(), &UiDraggableNotificationBus::Events::OnDragStart, point);
  195. m_hoverDropTarget.SetInvalid();
  196. }
  197. }
  198. }
  199. // if we are now in the dragging state do the drag update and handle start/end of drop hover
  200. if (m_isDragging)
  201. {
  202. DoDrag(point, false);
  203. }
  204. }
  205. }
  206. /////////////////////////////////////////////////////////////////
  207. bool UiDraggableComponent::DoesSupportDragHandOff(AZ::Vector2 startPoint)
  208. {
  209. // this component does support hand-off, so long as the start point is in its bounds
  210. // i.e. if there is a child interactable element such as a button or checkbox and the user
  211. // drags it, then the drag can get handed off to the parent draggable element
  212. bool isPointInRect = false;
  213. UiTransformBus::EventResult(isPointInRect, GetEntityId(), &UiTransformBus::Events::IsPointInRect, startPoint);
  214. return isPointInRect;
  215. }
  216. /////////////////////////////////////////////////////////////////
  217. bool UiDraggableComponent::OfferDragHandOff(AZ::EntityId currentActiveInteractable, AZ::Vector2 startPoint, AZ::Vector2 currentPoint, float dragThreshold)
  218. {
  219. // A child interactable element is offering to hand-off a drag interaction to this element
  220. bool handedOffToParent = false;
  221. bool dragDetected = CheckForDragOrHandOffToParent(currentActiveInteractable, startPoint, currentPoint, dragThreshold, handedOffToParent);
  222. if (dragDetected)
  223. {
  224. if (!handedOffToParent)
  225. {
  226. // a drag was detected and it was not handed off to a parent, so this draggable is now taking the handoff
  227. m_isPressed = true;
  228. m_pressedPoint = startPoint;
  229. // tell the canvas that this is now the active interactable
  230. UiInteractableActiveNotificationBus::Event(
  231. currentActiveInteractable, &UiInteractableActiveNotificationBus::Events::ActiveChanged, GetEntityId(), false);
  232. // start the drag
  233. m_isDragging = true;
  234. m_dragState = DragState::Normal;
  235. UiDraggableNotificationBus::QueueEvent(GetEntityId(), &UiDraggableNotificationBus::Events::OnDragStart, currentPoint);
  236. m_hoverDropTarget.SetInvalid();
  237. // Send the OnDrag and any OnDropHoverStart immediately so that it doesn't require another frame to
  238. // update.
  239. DoDrag(currentPoint, false);
  240. }
  241. }
  242. return dragDetected;
  243. }
  244. ////////////////////////////////////////////////////////////////////////////////////////////////////
  245. void UiDraggableComponent::LostActiveStatus()
  246. {
  247. // this is called when keyboard or console operation is being used and Enter was used to end the
  248. // operation.
  249. UiInteractableComponent::LostActiveStatus();
  250. AZ::Vector2 viewportPoint;
  251. UiTransformBus::EventResult(viewportPoint, GetEntityId(), &UiTransformBus::Events::GetViewportSpacePivot);
  252. EndDragOperation(viewportPoint, true);
  253. m_isActive = false;
  254. }
  255. ////////////////////////////////////////////////////////////////////////////////////////////////////
  256. UiDraggableInterface::DragState UiDraggableComponent::GetDragState()
  257. {
  258. return m_dragState;
  259. }
  260. ////////////////////////////////////////////////////////////////////////////////////////////////////
  261. void UiDraggableComponent::SetDragState(DragState dragState)
  262. {
  263. m_dragState = dragState;
  264. }
  265. ////////////////////////////////////////////////////////////////////////////////////////////////////
  266. void UiDraggableComponent::RedoDrag(AZ::Vector2 point)
  267. {
  268. DoDrag(point, true);
  269. }
  270. ////////////////////////////////////////////////////////////////////////////////////////////////////
  271. void UiDraggableComponent::SetAsProxy(AZ::EntityId originalDraggableId, AZ::Vector2 point)
  272. {
  273. // find the originalDraggable by Id
  274. AZ::Entity* originalDraggable = nullptr;
  275. AZ::ComponentApplicationBus::BroadcastResult(originalDraggable, &AZ::ComponentApplicationBus::Events::FindEntity, originalDraggableId);
  276. if (!originalDraggable)
  277. {
  278. AZ_Warning("UI", false, "SetAsProxy: Cannot find original draggable");
  279. return;
  280. }
  281. // Find the UiDraggableComponent on the originalDraggable
  282. UiDraggableComponent* originalComponent = originalDraggable->FindComponent<UiDraggableComponent>();
  283. if (!originalComponent)
  284. {
  285. AZ_Warning("UI", false, "SetAsProxy: Cannot find draggable component");
  286. return;
  287. }
  288. // Set the isProxyFor member variable, this indicates that this is a proxy
  289. m_isProxyFor = originalDraggableId;
  290. // put this draggable into the drag state and copy some of the state from the original
  291. m_isPressed = true;
  292. m_pressedPoint = originalComponent->m_pressedPoint;
  293. m_isActive = originalComponent->m_isActive;
  294. // tell the proxy draggable's canvas that this is now the active interactable
  295. AZ::EntityId canvasEntityId;
  296. UiElementBus::EventResult(canvasEntityId, GetEntityId(), &UiElementBus::Events::GetCanvasEntityId);
  297. UiCanvasBus::Event(canvasEntityId, &UiCanvasBus::Events::ForceActiveInteractable, GetEntityId(), m_isActive, m_pressedPoint);
  298. // start the drag on the proxy
  299. m_isDragging = true;
  300. m_dragState = DragState::Normal;
  301. UiDraggableNotificationBus::QueueEvent(GetEntityId(), &UiDraggableNotificationBus::Events::OnDragStart, point);
  302. m_hoverDropTarget.SetInvalid();
  303. // Send the OnDrag and any OnDropHoverStart immediately so that it doesn't require another frame to
  304. // update.
  305. DoDrag(point, false);
  306. // Turn of these flags on the original, this stops it responding to HandleReleased, InputPositionUpdate, etc
  307. // If the original is on a different canvas to the proxy then the original withh still get these functions called.
  308. // They just won't do anything.
  309. originalComponent->m_isDragging = false;
  310. originalComponent->m_isPressed = false;
  311. }
  312. ////////////////////////////////////////////////////////////////////////////////////////////////////
  313. void UiDraggableComponent::ProxyDragEnd(AZ::Vector2 point)
  314. {
  315. AZ::Entity* originalDraggable = nullptr;
  316. AZ::ComponentApplicationBus::BroadcastResult(originalDraggable, &AZ::ComponentApplicationBus::Events::FindEntity, m_isProxyFor);
  317. if (!originalDraggable)
  318. {
  319. AZ_Warning("UI", false, "ProxyDragEnd: Cannot find original draggable");
  320. return;
  321. }
  322. UiDraggableComponent* originalComponent = originalDraggable->FindComponent<UiDraggableComponent>();
  323. if (!originalComponent)
  324. {
  325. AZ_Warning("UI", false, "ProxyDragEnd: Cannot find draggable component on original");
  326. return;
  327. }
  328. // we don't want the proxy to get in the way of the search for a drop target under the original
  329. // draggable so disable interaction on it
  330. m_isHandlingEvents = false;
  331. originalComponent->m_isPressed = true;
  332. originalComponent->m_isDragging = true;
  333. originalComponent->HandleReleased(point);
  334. }
  335. ////////////////////////////////////////////////////////////////////////////////////////////////////
  336. bool UiDraggableComponent::IsProxy()
  337. {
  338. return (m_isProxyFor.IsValid()) ? true : false;
  339. }
  340. ////////////////////////////////////////////////////////////////////////////////////////////////////
  341. AZ::EntityId UiDraggableComponent::GetOriginalFromProxy()
  342. {
  343. return m_isProxyFor;
  344. }
  345. ////////////////////////////////////////////////////////////////////////////////////////////////////
  346. bool UiDraggableComponent::GetCanDropOnAnyCanvas()
  347. {
  348. return m_canDropOnAnyCanvas;
  349. }
  350. ////////////////////////////////////////////////////////////////////////////////////////////////////
  351. void UiDraggableComponent::SetCanDropOnAnyCanvas(bool anyCanvas)
  352. {
  353. m_canDropOnAnyCanvas = anyCanvas;
  354. }
  355. ////////////////////////////////////////////////////////////////////////////////////////////////////
  356. // PROTECTED MEMBER FUNCTIONS
  357. ////////////////////////////////////////////////////////////////////////////////////////////////////
  358. ////////////////////////////////////////////////////////////////////////////////////////////////////
  359. void UiDraggableComponent::Activate()
  360. {
  361. UiInteractableComponent::Activate();
  362. UiDraggableBus::Handler::BusConnect(m_entity->GetId());
  363. }
  364. ////////////////////////////////////////////////////////////////////////////////////////////////////
  365. void UiDraggableComponent::Deactivate()
  366. {
  367. UiInteractableComponent::Deactivate();
  368. UiDraggableBus::Handler::BusDisconnect();
  369. }
  370. ////////////////////////////////////////////////////////////////////////////////////////////////////
  371. UiInteractableStatesInterface::State UiDraggableComponent::ComputeInteractableState()
  372. {
  373. UiInteractableStatesInterface::State state = UiInteractableStatesInterface::StateNormal;
  374. if (!m_isHandlingEvents)
  375. {
  376. state = UiInteractableStatesInterface::StateDisabled;
  377. }
  378. else if (m_isDragging)
  379. {
  380. switch (m_dragState)
  381. {
  382. case DragState::Normal:
  383. state = StateDragNormal;
  384. break;
  385. case DragState::Valid:
  386. state = StateDragValid;
  387. break;
  388. case DragState::Invalid:
  389. state = StateDragInvalid;
  390. break;
  391. }
  392. }
  393. else if (m_isPressed || m_isActive)
  394. {
  395. // To support keyboard/console we stay in pressed state when active
  396. state = UiInteractableStatesInterface::StatePressed;
  397. }
  398. else if (m_isHover)
  399. {
  400. state = UiInteractableStatesInterface::StateHover;
  401. }
  402. return state;
  403. }
  404. ////////////////////////////////////////////////////////////////////////////////////////////////////
  405. void UiDraggableComponent::OnDragNormalStateActionsChanged()
  406. {
  407. m_stateActionManager.InitInteractableEntityForStateActions(m_dragNormalStateActions);
  408. }
  409. ////////////////////////////////////////////////////////////////////////////////////////////////////
  410. void UiDraggableComponent::OnDragValidStateActionsChanged()
  411. {
  412. m_stateActionManager.InitInteractableEntityForStateActions(m_dragValidStateActions);
  413. }
  414. ////////////////////////////////////////////////////////////////////////////////////////////////////
  415. void UiDraggableComponent::OnDragInvalidStateActionsChanged()
  416. {
  417. m_stateActionManager.InitInteractableEntityForStateActions(m_dragInvalidStateActions);
  418. }
  419. ////////////////////////////////////////////////////////////////////////////////////////////////////
  420. // PROTECTED STATIC MEMBER FUNCTIONS
  421. ////////////////////////////////////////////////////////////////////////////////////////////////////
  422. ////////////////////////////////////////////////////////////////////////////////////////////////////
  423. void UiDraggableComponent::Reflect(AZ::ReflectContext* context)
  424. {
  425. AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
  426. if (serializeContext)
  427. {
  428. serializeContext->Class<UiDraggableComponent, UiInteractableComponent>()
  429. ->Version(1)
  430. ->Field("DragNormalStateActions", &UiDraggableComponent::m_dragNormalStateActions)
  431. ->Field("DragValidStateActions", &UiDraggableComponent::m_dragValidStateActions)
  432. ->Field("DragInvalidStateActions", &UiDraggableComponent::m_dragInvalidStateActions);
  433. AZ::EditContext* ec = serializeContext->GetEditContext();
  434. if (ec)
  435. {
  436. auto editInfo = ec->Class<UiDraggableComponent>("Draggable", "An interactable component for drag and drop behavior");
  437. editInfo->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  438. ->Attribute(AZ::Edit::Attributes::Category, "UI")
  439. ->Attribute(AZ::Edit::Attributes::Icon, "Editor/Icons/Components/UiDraggable.png")
  440. ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Editor/Icons/Components/Viewport/UiDraggable.png")
  441. ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("UI"))
  442. ->Attribute(AZ::Edit::Attributes::AutoExpand, true);
  443. editInfo->ClassElement(AZ::Edit::ClassElements::Group, "Drag States")
  444. ->Attribute(AZ::Edit::Attributes::AutoExpand, true);
  445. editInfo->DataElement(0, &UiDraggableComponent::m_dragNormalStateActions, "Normal", "The normal drag state actions")
  446. ->Attribute(AZ::Edit::Attributes::AddNotify, &UiDraggableComponent::OnDragNormalStateActionsChanged);
  447. editInfo->DataElement(0, &UiDraggableComponent::m_dragValidStateActions, "Valid", "The valid drag state actions")
  448. ->Attribute(AZ::Edit::Attributes::AddNotify, &UiDraggableComponent::OnDragValidStateActionsChanged);
  449. editInfo->DataElement(0, &UiDraggableComponent::m_dragInvalidStateActions, "Invalid", "The invalid drag state actions")
  450. ->Attribute(AZ::Edit::Attributes::AddNotify, &UiDraggableComponent::OnDragInvalidStateActionsChanged);
  451. }
  452. }
  453. AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context);
  454. if (behaviorContext)
  455. {
  456. behaviorContext->Enum<(int)UiDraggableInterface::DragState::Normal>("eUiDragState_Normal")
  457. ->Enum<(int)UiDraggableInterface::DragState::Valid>("eUiDragState_Valid")
  458. ->Enum<(int)UiDraggableInterface::DragState::Invalid>("eUiDragState_Invalid");
  459. behaviorContext->EBus<UiDraggableBus>("UiDraggableBus")
  460. ->Event("GetDragState", &UiDraggableBus::Events::GetDragState)
  461. ->Event("SetDragState", &UiDraggableBus::Events::SetDragState)
  462. ->Event("RedoDrag", &UiDraggableBus::Events::RedoDrag)
  463. ->Event("SetAsProxy", &UiDraggableBus::Events::SetAsProxy)
  464. ->Event("ProxyDragEnd", &UiDraggableBus::Events::ProxyDragEnd)
  465. ->Event("IsProxy", &UiDraggableBus::Events::IsProxy)
  466. ->Event("GetOriginalFromProxy", &UiDraggableBus::Events::GetOriginalFromProxy)
  467. ->Event("GetCanDropOnAnyCanvas", &UiDraggableBus::Events::GetCanDropOnAnyCanvas)
  468. ->Event("SetCanDropOnAnyCanvas", &UiDraggableBus::Events::SetCanDropOnAnyCanvas);
  469. behaviorContext->EBus<UiDraggableNotificationBus>("UiDraggableNotificationBus")
  470. ->Handler<UiDraggableNotificationBusBehaviorHandler>();
  471. }
  472. }
  473. ////////////////////////////////////////////////////////////////////////////////////////////////////
  474. // PRIVATE MEMBER FUNCTIONS
  475. ////////////////////////////////////////////////////////////////////////////////////////////////////
  476. ////////////////////////////////////////////////////////////////////////////////////////////////////
  477. AZ::EntityId UiDraggableComponent::GetDropTargetUnderDraggable(AZ::Vector2 point, bool ignoreInteractables)
  478. {
  479. AZ::EntityId result;
  480. AZ::EntityId canvasEntity;
  481. UiElementBus::EventResult(canvasEntity, GetEntityId(), &UiElementBus::Events::GetCanvasEntityId);
  482. // We will ignore this element and all its children in the search
  483. AZ::EntityId ignoreElement = GetEntityId();
  484. // Look for a drop target under the mouse position
  485. // recursively check the children of the canvas (in reverse order since children are in front of parent)
  486. if (m_canDropOnAnyCanvas)
  487. {
  488. result = FindDropTargetOrInteractableOnAllCanvases(point, ignoreElement, ignoreInteractables);
  489. }
  490. else
  491. {
  492. result = FindDropTargetOrInteractableOnCanvas(canvasEntity, point, ignoreElement, ignoreInteractables);
  493. }
  494. // The result could be an interactable that is not a drop target since an interactable in front of a drop target
  495. // can block dropping on it (unless it is the child of the drop target)
  496. if (!UiDropTargetBus::FindFirstHandler(result))
  497. {
  498. result.SetInvalid();
  499. }
  500. return result;
  501. }
  502. ////////////////////////////////////////////////////////////////////////////////////////////////////
  503. bool UiDraggableComponent::CheckForDragOrHandOffToParent([[maybe_unused]] AZ::EntityId currentActiveInteractable, AZ::Vector2 startPoint, AZ::Vector2 currentPoint, float childDragThreshold, [[maybe_unused]] bool& handOffDone)
  504. {
  505. // Currently a draggable never hands off the drag to a parent since a drag in any direction is valid.
  506. // Potentially this could change if we allowed, for example, a scroll box containing draggables where
  507. // dragging up and down scrolled the scroll box and dragging left and right initiated drag and drop.
  508. // In that case we would need a property to say in which direction a draggable can be dragged.
  509. bool result = false;
  510. // Possibly this should be a user defined property since it defines how much movement constitutes a drag start
  511. const float normalDragThreshold = 3.0f;
  512. float dragThreshold = normalDragThreshold;
  513. if (childDragThreshold > 0.0f)
  514. {
  515. dragThreshold = childDragThreshold;
  516. }
  517. float dragThresholdSq = dragThreshold * dragThreshold;
  518. // calculate how much we have dragged
  519. AZ::Vector2 dragVector = currentPoint - startPoint;
  520. float dragDistanceSq = dragVector.GetLengthSq();
  521. if (dragDistanceSq > dragThresholdSq)
  522. {
  523. // we dragged above the threshold value
  524. result = true;
  525. }
  526. return result;
  527. }
  528. ////////////////////////////////////////////////////////////////////////////////////////////////////
  529. void UiDraggableComponent::DoDrag(AZ::Vector2 viewportPoint, bool ignoreInteractables)
  530. {
  531. // In the case where a proxy has been created in the OnDragStart handler we would no longer
  532. // be in the dragging state, in that case do nothing here
  533. if (!m_isDragging)
  534. {
  535. return;
  536. }
  537. // Send the OnDrag notification
  538. UiDraggableNotificationBus::QueueEvent(GetEntityId(), &UiDraggableNotificationBus::Events::OnDrag, viewportPoint);
  539. AZ::EntityId dropEntity = GetDropTargetUnderDraggable(viewportPoint, ignoreInteractables);
  540. // if we have a drop hover entity and we are no longer hovering over it
  541. if (m_hoverDropTarget.IsValid() && m_hoverDropTarget != dropEntity)
  542. {
  543. // end the drop hover
  544. UiDropTargetBus::Event(m_hoverDropTarget, &UiDropTargetBus::Events::HandleDropHoverEnd, GetEntityId());
  545. m_hoverDropTarget.SetInvalid();
  546. }
  547. // if we do not have a drop hover entity and we are hovering over a drop target
  548. if (!m_hoverDropTarget.IsValid() && dropEntity.IsValid())
  549. {
  550. // start a drop hover
  551. UiDropTargetBus::Event(dropEntity, &UiDropTargetBus::Events::HandleDropHoverStart, GetEntityId());
  552. m_hoverDropTarget = dropEntity;
  553. }
  554. }
  555. ////////////////////////////////////////////////////////////////////////////////////////////////////
  556. void UiDraggableComponent::EndDragOperation(AZ::Vector2 viewportPoint, bool ignoreInteractables)
  557. {
  558. if (m_isDragging)
  559. {
  560. // If we were hovering over a drop target then forget it, we will recompute what we are over now
  561. if (m_hoverDropTarget.IsValid())
  562. {
  563. UiDropTargetBus::Event(m_hoverDropTarget, &UiDropTargetBus::Events::HandleDropHoverEnd, GetEntityId());
  564. m_hoverDropTarget.SetInvalid();
  565. }
  566. // Search for a drop target before calling OnDragEnd in case OnDragEnd moves the drop target that we are over
  567. AZ::EntityId dropEntity = GetDropTargetUnderDraggable(viewportPoint, ignoreInteractables);
  568. // send a drag end notification
  569. UiDraggableNotificationBus::QueueEvent(GetEntityId(), &UiDraggableNotificationBus::Events::OnDragEnd, viewportPoint);
  570. // If there was a drop target under the cursor then send it a message to handle this draggable being dropped on it
  571. if (dropEntity.IsValid())
  572. {
  573. UiDropTargetBus::Event(dropEntity, &UiDropTargetBus::Events::HandleDrop, GetEntityId());
  574. }
  575. m_isDragging = false;
  576. m_dragState = DragState::Normal;
  577. }
  578. }
  579. ////////////////////////////////////////////////////////////////////////////////////////////////////
  580. void UiDraggableComponent::FindNavigableDropTargetElements(AZ::EntityId ignoreElement, LyShine::EntityArray& result)
  581. {
  582. AZ::EntityId canvasEntity;
  583. UiElementBus::EventResult(canvasEntity, GetEntityId(), &UiElementBus::Events::GetCanvasEntityId);
  584. LyShine::EntityArray elements;
  585. UiCanvasBus::EventResult(elements, canvasEntity, &UiCanvasBus::Events::GetChildElements);
  586. AZStd::list<AZ::Entity*> elementList(elements.begin(), elements.end());
  587. while (!elementList.empty())
  588. {
  589. auto entity = elementList.front();
  590. elementList.pop_front();
  591. if (ignoreElement.IsValid() && entity->GetId() == ignoreElement)
  592. {
  593. continue; // this is the element to ignore, ignore its children also
  594. }
  595. // Check if the element is enabled
  596. bool isEnabled = false;
  597. UiElementBus::EventResult(isEnabled, entity->GetId(), &UiElementBus::Events::IsEnabled);
  598. if (!isEnabled)
  599. {
  600. continue;
  601. }
  602. bool isDropTarget = false;
  603. if (UiDropTargetBus::FindFirstHandler(entity->GetId()))
  604. {
  605. isDropTarget = true;
  606. }
  607. UiNavigationInterface::NavigationMode navigationMode = UiNavigationInterface::NavigationMode::None;
  608. UiNavigationBus::EventResult(navigationMode, entity->GetId(), &UiNavigationBus::Events::GetNavigationMode);
  609. bool isNavigable = (navigationMode != UiNavigationInterface::NavigationMode::None);
  610. if (isDropTarget && isNavigable)
  611. {
  612. result.push_back(entity);
  613. }
  614. else
  615. {
  616. LyShine::EntityArray childElements;
  617. UiElementBus::EventResult(childElements, entity->GetId(), &UiElementBus::Events::GetChildElements);
  618. elementList.insert(elementList.end(), childElements.begin(), childElements.end());
  619. }
  620. }
  621. }
  622. ////////////////////////////////////////////////////////////////////////////////////////////////////
  623. AZ::EntityId UiDraggableComponent::FindClosestNavigableDropTarget()
  624. {
  625. UiTransformInterface::RectPoints srcPoints;
  626. UiTransformBus::Event(GetEntityId(), &UiTransformBus::Events::GetViewportSpacePoints, srcPoints);
  627. AZ::Vector2 srcCenter = srcPoints.GetCenter();
  628. LyShine::EntityArray dropTargets;
  629. FindNavigableDropTargetElements(AZ::EntityId(), dropTargets);
  630. float shortestDist = FLT_MAX;
  631. AZ::EntityId closestElement;
  632. for (auto dropTarget : dropTargets)
  633. {
  634. UiTransformInterface::RectPoints destPoints;
  635. UiTransformBus::Event(dropTarget->GetId(), &UiTransformBus::Events::GetViewportSpacePoints, destPoints);
  636. AZ::Vector2 destCenter = destPoints.GetCenter();
  637. float dist = (destCenter - srcCenter).GetLengthSq();
  638. if (dist < shortestDist)
  639. {
  640. shortestDist = dist;
  641. closestElement = dropTarget->GetId();
  642. }
  643. }
  644. return closestElement;
  645. }
  646. ////////////////////////////////////////////////////////////////////////////////////////////////////
  647. // PRIVATE STATIC FUNCTIONS
  648. ////////////////////////////////////////////////////////////////////////////////////////////////////
  649. ////////////////////////////////////////////////////////////////////////////////////////////////////
  650. AZ::EntityId UiDraggableComponent::FindDropTargetOrInteractableOnAllCanvases(
  651. AZ::Vector2 point, AZ::EntityId ignoreElement, bool ignoreInteractables)
  652. {
  653. AZ::EntityId result;
  654. UiCanvasManagerInterface::CanvasEntityList canvases;
  655. UiCanvasManagerBus::BroadcastResult(canvases, &UiCanvasManagerBus::Events::GetLoadedCanvases);
  656. // reverse iterate over the loaded canvases so that the front most canvas gets first chance to
  657. // handle the event
  658. for (auto iter = canvases.rbegin(); iter != canvases.rend() && !result.IsValid(); ++iter)
  659. {
  660. AZ::EntityId canvasEntityId = *iter;
  661. result = FindDropTargetOrInteractableOnCanvas(canvasEntityId, point, ignoreElement, ignoreInteractables);
  662. }
  663. return result;
  664. }
  665. ////////////////////////////////////////////////////////////////////////////////////////////////////
  666. AZ::EntityId UiDraggableComponent::FindDropTargetOrInteractableOnCanvas(AZ::EntityId canvasEntityId,
  667. AZ::Vector2 point, AZ::EntityId ignoreElement, bool ignoreInteractables)
  668. {
  669. AZ::EntityId result;
  670. // recursively check the children of the canvas (in reverse order since children are in front of parent)
  671. int numChildren = 0;
  672. UiCanvasBus::EventResult(numChildren, canvasEntityId, &UiCanvasBus::Events::GetNumChildElements);
  673. for (int i = numChildren - 1; !result.IsValid() && i >= 0; i--)
  674. {
  675. AZ::EntityId child;
  676. UiCanvasBus::EventResult(child, canvasEntityId, &UiCanvasBus::Events::GetChildElementEntityId, i);
  677. if (child != ignoreElement)
  678. {
  679. result = FindDropTargetOrInteractableUnderCursor(child, point, ignoreElement, ignoreInteractables);
  680. }
  681. }
  682. return result;
  683. }
  684. ////////////////////////////////////////////////////////////////////////////////////////////////////
  685. AZ::EntityId UiDraggableComponent::FindDropTargetOrInteractableUnderCursor(AZ::EntityId element,
  686. AZ::Vector2 point, AZ::EntityId ignoreElement, bool ignoreInteractables)
  687. {
  688. AZ::EntityId result;
  689. bool isEnabled = false;
  690. UiElementBus::EventResult(isEnabled, element, &UiElementBus::Events::IsEnabled);
  691. if (!isEnabled)
  692. {
  693. // Nothing to do
  694. return result;
  695. }
  696. // first check the children (in reverse order since children are in front of parent)
  697. {
  698. // if this element is masking children at this point then don't check the children
  699. bool isMasked = false;
  700. UiInteractionMaskBus::EventResult(isMasked, element, &UiInteractionMaskBus::Events::IsPointMasked, point);
  701. if (!isMasked)
  702. {
  703. int numChildren = 0;
  704. UiElementBus::EventResult(numChildren, element, &UiElementBus::Events::GetNumChildElements);
  705. for (int i = numChildren - 1; !result.IsValid() && i >= 0; i--)
  706. {
  707. AZ::EntityId child;
  708. UiElementBus::EventResult(child, element, &UiElementBus::Events::GetChildEntityId, i);
  709. if (child != ignoreElement)
  710. {
  711. result = FindDropTargetOrInteractableUnderCursor(child, point, ignoreElement, ignoreInteractables);
  712. }
  713. }
  714. }
  715. }
  716. // if no match then check this element
  717. if (!result.IsValid())
  718. {
  719. // if the point is in this element's rect
  720. bool isInRect = false;
  721. UiTransformBus::EventResult(isInRect, element, &UiTransformBus::Events::IsPointInRect, point);
  722. if (isInRect)
  723. {
  724. // If this element has a drop target component
  725. if (UiDropTargetBus::FindFirstHandler(element))
  726. {
  727. // this is the drop target under the cursor
  728. result = element;
  729. }
  730. // else if this element has an interactable component
  731. else if (!ignoreInteractables && UiInteractableBus::FindFirstHandler(element))
  732. {
  733. // check if this interactable component is in a state where it can handle an event at the given point
  734. bool canHandle = false;
  735. UiInteractableBus::EventResult(canHandle, element, &UiInteractableBus::Events::CanHandleEvent, point);
  736. if (canHandle)
  737. {
  738. // in this case the interaction is blocked unless this interactable has a parent that is
  739. // a drop target
  740. AZ::EntityId parent;
  741. UiElementBus::EventResult(parent, element, &UiElementBus::Events::GetParentEntityId);
  742. while (parent.IsValid())
  743. {
  744. bool isInParentRect = false;
  745. UiTransformBus::EventResult(isInParentRect, parent, &UiTransformBus::Events::IsPointInRect, point);
  746. if (isInParentRect && UiDropTargetBus::FindFirstHandler(parent))
  747. {
  748. // We found a parent drop target and the cursor is in its rect,
  749. // this is considered the drop target under the cursor
  750. result = parent;
  751. break;
  752. }
  753. UiElementBus::EventResult(parent, parent, &UiElementBus::Events::GetParentEntityId);
  754. }
  755. if (!result.IsValid())
  756. {
  757. // no parent drop target was found, return this blocking interactable
  758. result = element;
  759. }
  760. }
  761. }
  762. }
  763. }
  764. return result;
  765. }