text: /*
   * Copyright (c) Contributors to the Open 3D Engine Project.
   * For complete copyright and license terms please see the LICENSE at the root of this distribution.
   *
   * SPDX-License-Identifier: Apache-2.0 OR MIT
   *
   */
  #include "UiCanvasEditor_precompiled.h"
  
  #include "EditorCommon.h"
  #include "QtHelpers.h"
  
  namespace EntityHelpers
  {
      AZ::Vector2 RoundXY(const AZ::Vector2& v)
      {
          return AZ::Vector2(roundf(v.GetX()), roundf(v.GetY()));
      }
  
      AZ::Vector3 RoundXY(const AZ::Vector3& v)
      {
          return AZ::Vector3(roundf(v.GetX()), roundf(v.GetY()), v.GetZ());
      }
  
      AZ::Vector3 MakeVec3(const AZ::Vector2& v)
      {
          return AZ::Vector3(v.GetX(), v.GetY(), 0.0f);
      }
  
      float Snap(float value, float snapDistance)
      {
          // std::remainder gives the difference between the value and the closest multiple of the snap distance
          float rem = std::remainder(value, snapDistance);
  
          // return the closest value that is on the snap grid
          return value - rem;
      }
  
      AZ::Vector2 Snap(const AZ::Vector2& v, float snapDistance)
      {
          return AZ::Vector2(v.GetX() - std::remainder(v.GetX(), snapDistance),
              v.GetY() - std::remainder(v.GetY(), snapDistance));
      }
  
      UiTransform2dInterface::Offsets Snap(const UiTransform2dInterface::Offsets& offs, const ViewportHelpers::ElementEdges& grabbedEdges, float snapDistance)
      {
          return UiTransform2dInterface::Offsets(offs.m_left - std::remainder((grabbedEdges.m_left ? offs.m_left : 0.0f), snapDistance),
              offs.m_top - std::remainder((grabbedEdges.m_top ? offs.m_top : 0.0f), snapDistance),
              offs.m_right - std::remainder((grabbedEdges.m_right ? offs.m_right : 0.0f), snapDistance),
              offs.m_bottom - std::remainder((grabbedEdges.m_bottom ? offs.m_bottom : 0.0f), snapDistance));
      }
  
      void MoveElementToGlobalPosition(AZ::Entity* element, const QPoint& globalPos)
      {
          if (!element)
          {
               return;
          }
  
          // Transform pivot position to canvas space
          AZ::Vector2 pivotPos;
          EBUS_EVENT_ID_RESULT(pivotPos, element->GetId(), UiTransformBus, GetCanvasSpacePivotNoScaleRotate);
  
          // Transform destination position to canvas space
          AZ::Matrix4x4 transformFromViewport;
          EBUS_EVENT_ID(element->GetId(), UiTransformBus, GetTransformFromViewport, transformFromViewport);
          AZ::Vector2 globalPos2(QtHelpers::QPointFToVector2(globalPos));
          AZ::Vector3 destPos3 = transformFromViewport * AZ::Vector3(globalPos2.GetX(), globalPos2.GetY(), 0.0f);
          AZ::Vector2 destPos(destPos3.GetX(), destPos3.GetY());
  
          // Adjust offsets
          UiTransform2dInterface::Offsets offsets;
          EBUS_EVENT_ID_RESULT(offsets, element->GetId(), UiTransform2dBus, GetOffsets);
          EBUS_EVENT_ID(element->GetId(), UiTransform2dBus, SetOffsets, offsets + (destPos - pivotPos));
      }
  
      AZ::Entity* GetParentElement(const AZ::Entity* element)
      {
          AZ::Entity* parentElement = nullptr;
  
          if (!element)
          {
              return nullptr;
          }
  
          EBUS_EVENT_ID_RESULT(parentElement, element->GetId(), UiElementBus, GetParent);
  
          return parentElement;
      }
  
      AZ::Entity* GetParentElement(const AZ::EntityId& elementId)
      {
          AZ::Entity* parentElement = nullptr;
          EBUS_EVENT_ID_RESULT(parentElement, elementId, UiElementBus, GetParent);
          return parentElement;
      }
  
      AZ::Entity* GetEntity(AZ::EntityId id)
      {
          AZ::Entity* element = nullptr;
          EBUS_EVENT_RESULT(element, AZ::ComponentApplicationBus, FindEntity, id);
          return element;
      }
  
      void ComputeCanvasSpaceRectNoScaleRotate(AZ::EntityId elementId, UiTransform2dInterface::Offsets offsets, UiTransformInterface::Rect& rect)
      {
          AZ::Entity* parentElement = nullptr;
          EBUS_EVENT_ID_RESULT(parentElement, elementId, UiElementBus, GetParent);
          if (parentElement)
          {
              UiTransformInterface::Rect parentRect;
              EBUS_EVENT_ID(parentElement->GetId(), UiTransformBus, GetCanvasSpaceRectNoScaleRotate, parentRect);
  
              AZ::Vector2 parentSize = parentRect.GetSize();
  
              UiTransform2dInterface::Anchors anchors;
              EBUS_EVENT_ID_RESULT(anchors, elementId, UiTransform2dBus, GetAnchors);
  
              float left   = parentRect.left + parentSize.GetX() * anchors.m_left   + offsets.m_left;
              float right  = parentRect.left + parentSize.GetX() * anchors.m_right  + offsets.m_right;
              float top    = parentRect.top + parentSize.GetY() * anchors.m_top    + offsets.m_top;
              float bottom = parentRect.top + parentSize.GetY() * anchors.m_bottom + offsets.m_bottom;
  
              rect.Set(left, right, top, bottom);
          }
          else
          {
              AZ_Assert(false, "This is the root element.");
          }
  
          // we never return a "flipped" rect. I.e. left is always less than right, top is always less than bottom
          // if it is flipped in a dimension then we make it zero size in that dimension
          if (rect.left > rect.right)
          {
              rect.left = rect.right = rect.GetCenterX();
          }
          if (rect.top > rect.bottom)
          {
              rect.top = rect.bottom = rect.GetCenterY();
          }
      }
  
      AZ::Vector2 ComputeCanvasSpacePivotNoScaleRotate(AZ::EntityId elementId, UiTransform2dInterface::Offsets offsets)
      {
          AZ::Vector2 pivot;
          EBUS_EVENT_ID_RESULT(pivot, elementId, UiTransformBus, GetPivot);
  
          UiTransformInterface::Rect rect;
          ComputeCanvasSpaceRectNoScaleRotate(elementId, offsets, rect);
  
          AZ::Vector2 size = rect.GetSize();
  
          float x =  rect.left + size.GetX() * pivot.GetX();
          float y =  rect.top + size.GetY() * pivot.GetY();
  
          return AZ::Vector2(x, y);
      }
  
      AZStd::string GetHierarchicalElementName(AZ::EntityId entityId)
      {
          AZStd::string result;
  
          // attempt to get more info about the entity
          AZ::Entity* entity = nullptr;
          EBUS_EVENT_RESULT(entity, AZ::ComponentApplicationBus, FindEntity, entityId);
  
          if (entity)
          {
              result = entity->GetName();
  
              AZ::Entity* parent = nullptr;
              EBUS_EVENT_ID_RESULT(parent, entityId, UiElementBus, GetParent);
              while (parent)
              {
                  AZStd::string entityName = parent->GetName();
                  AZ::EntityId parentId = parent->GetId();
                  parent = nullptr;   // in case entity is not listening on bus
                  EBUS_EVENT_ID_RESULT(parent, parentId, UiElementBus, GetParent);
  
                  // we do not want to include the root element name
                  if (parent)
                  {
                      result = entityName + "/" + result;
                  }
              }
          }
          else
          {
              result = entityId.ToString();
          }
  
          return result;
      }
  
      AZ::Entity* GetCommonAncestor(AZ::Entity* element1, AZ::Entity* element2,
          AZ::Entity*& element1NextAncestor, AZ::Entity*& element2NextAncestor)
      {
          element1NextAncestor = element1;
          element2NextAncestor = element2;
  
          // if the two elements are the same then their common ancestor is the element itself
          if (element1 == element2)
          {
              return element1;
          }
  
          // traverse up element1's parent chain storing all the ancestors in a vector
          AZStd::vector<AZ::Entity*> element1Ancestors;
          AZ::Entity* parent = GetParentElement(element1);
          while (parent)
          {
              if (parent == element2)
              {
                  return element2;    // element2 is an ancestor of element1, early out
              }
  
              element1Ancestors.push_back(parent);
              element1NextAncestor = parent;
  
              parent = GetParentElement(parent);
          }
  
          // now traverse up element2's parent chain looking for a match in element1's ancestors
          parent = GetParentElement(element2);
          while (parent)
          {
              if (parent == element1)
              {
                  return element1; // element1 is a parent of element 2, early out
              }
  
              // search for this parent in element1's ancestors
              for (int i = 0; i < element1Ancestors.size(); ++i)
              {
                  if (element1Ancestors[i] == parent)
                  {
                      // this parent is in element1's ancestors so it is the common ancestor
  
                      if (i > 0)
                      {
                          // the child of the common ancestor is the previous ancestor in the list
                          element1NextAncestor = element1Ancestors[i-1];
                      }
                      else
                      {
                          // element1 is an immediate child of the common parent
                          element1NextAncestor = element1;
                      }
  
                      // we have found the common parent
                      return parent;
                  }
              }
  
              element2NextAncestor = parent;
              parent = GetParentElement(parent);
          }
  
          // no common parent was found
          // this should never happen if the given elements are part of the same canvas
          return nullptr;
      }
  
      bool CompareOrderInElementHierarchy(AZ::Entity* element1, AZ::Entity* element2)
      {
          if (element1 == element2)
          {
              // this should not be used to compare the same element but if it is always return a consistent
              // result
              return true;
          }
  
          AZ::Entity* element1NextAncestor = nullptr;
          AZ::Entity* element2NextAncestor = nullptr;
          AZ::Entity* commonParent = GetCommonAncestor(element1, element2, element1NextAncestor, element2NextAncestor);
  
          if (!commonParent)
          {
              // an error orccured and no common parent was found
              // to recover just compare the pointers
              AZ_Assert(false, "No common parent found.");
              return (element1 < element2);
          }
  
          if (element1 == commonParent)
          {
              return true; // element2 is a child of element1 so element1 is before
          }
          else if (element2 == commonParent)
          {
              return false; // element1 is a child of element2 so element1 is not before
          }
          else
          {
              // neither is the parent of the other. In this case we know that element1NextAncestor and
              // element2NextAncestor are siblings and children of the common parent
              int index1 = -1;
              EBUS_EVENT_ID_RESULT(index1, commonParent->GetId(), UiElementBus, GetIndexOfChild, element1NextAncestor);
              int index2 = -1;
              EBUS_EVENT_ID_RESULT(index2, commonParent->GetId(), UiElementBus, GetIndexOfChild, element2NextAncestor);
  
              AZ_Assert(index1 != -1 && index2 != -1, "Immediate ancestors not found in parent.");
  
              return (index1 < index2);
          }
  
      }
  
      void MoveByLocalDeltaUsingOffsets(AZ::EntityId entityId, AZ::Vector2 deltaInLocalSpace)
      {
          // Get the existing offsets and pass them to the version of this function that takes starting offsets
          UiTransform2dInterface::Offsets offsets;
          EBUS_EVENT_ID_RESULT(offsets, entityId, UiTransform2dBus, GetOffsets);
          MoveByLocalDeltaUsingOffsets(entityId, offsets, deltaInLocalSpace);
      }
  
      void MoveByLocalDeltaUsingOffsets(AZ::EntityId entityId, const UiTransform2dInterface::Offsets& startingOffsets, AZ::Vector2 deltaInLocalSpace)
      {
          // simply add the local space delta to the offsets
          EBUS_EVENT_ID(entityId, UiTransform2dBus, SetOffsets, startingOffsets + deltaInLocalSpace);
      }
  
      AZ::Vector2 MoveByLocalDeltaUsingAnchors(AZ::EntityId entityId, AZ::EntityId parentEntityId, AZ::Vector2 deltaInLocalSpace, bool restrictDirection)
      {
          // Get the existing anchors and pass them to the version of this function that takes starting anchors
          UiTransform2dInterface::Anchors anchors;
          EBUS_EVENT_ID_RESULT(anchors, entityId, UiTransform2dBus, GetAnchors);
          return MoveByLocalDeltaUsingAnchors(entityId, parentEntityId, anchors, deltaInLocalSpace, restrictDirection);
      }
  
      AZ::Vector2 MoveByLocalDeltaUsingAnchors(AZ::EntityId entityId, AZ::EntityId parentEntityId,
          const UiTransform2dInterface::Anchors& startingAnchors, AZ::Vector2 deltaInLocalSpace, bool restrictDirection)
      {
          UiTransform2dInterface::Anchors anchors = startingAnchors;
  
          AZ::Vector2 parentSize;
          EBUS_EVENT_ID_RESULT(parentSize, parentEntityId, UiTransformBus, GetCanvasSpaceSizeNoScaleRotate);
  
          // compute the anchorDelta in anchor space (0-1) and add to the anchor values
          AZ::Vector2 anchorDelta(0.0f, 0.0f);
          const float epsilon = 0.001f;
          if (parentSize.GetX() > epsilon)
          {
              anchorDelta.SetX(deltaInLocalSpace.GetX() / parentSize.GetX());
              anchors.m_left += anchorDelta.GetX();
              anchors.m_right += anchorDelta.GetX();
          }
          if (parentSize.GetY() > epsilon)
          {
              anchorDelta.SetY(deltaInLocalSpace.GetY() / parentSize.GetY());
              anchors.m_top += anchorDelta.GetY();
              anchors.m_bottom += anchorDelta.GetY();
          }
  
          // Check if the anchors are now out of the 0-1 range and if so move them back along the delta vector
          // Note that we can't just clamp (both because it doesn't work if the anchors are apart and because we
          // do not want to change the angle of movement).
          if (anchors.m_left < 0.0f || anchors.m_right > 1.0f || anchors.m_top < 0.0f || anchors.m_bottom > 1.0f)
          {
              // compute the adjustment needed to get the anchors in range
              AZ::Vector2 adjustment(0.0f, 0.0f);
              if (anchors.m_left < 0.0f)
              {
                  adjustment.SetX(0.0f - anchors.m_left);
              }
              else if (anchors.m_right > 1.0f)
              {
                  adjustment.SetX(1.0f - anchors.m_right);
              }
  
              if (anchors.m_top < 0.0f)
              {
                  adjustment.SetY(0.0f - anchors.m_top);
              }
              else if (anchors.m_bottom > 1.0f)
              {
                  adjustment.SetY(1.0f - anchors.m_bottom);
              }
  
              // If we are moving only in one axis then there are no issues with sliding along the edge since we must be moving directly
              // against one anchor limit (edge) only, if we are moving in more than one axis then we have to make sure we adjust back only
              // along the direction of movement (if restrictDirection is true)
              if (anchorDelta.GetX() != 0.0f && anchorDelta.GetY() != 0.0f && restrictDirection)
              {
                  // The adjustment vector as it is would put the anchor on the limit but not respect only moving it along the
                  // deltaInLocalSpace (and therefore anchorDelta) vector.
                  // So we modify the adjustment vector to be co-linear (but in the opposite direction) to anchorDelta.
                  // Because of rounding errors where the anchorDelta vector is very close to the x or y axis we check whether
                  // the x or y component is greater and switch the order of the calculation.
                  float xAdjustAbs = fabsf(adjustment.GetX());
                  float yAdjustAbs = fabsf(adjustment.GetY());
                  if (xAdjustAbs < yAdjustAbs)
                  {
                      float xAdjustmentToFitY = adjustment.GetY() * anchorDelta.GetX() / anchorDelta.GetY();
                      if (fabsf(xAdjustmentToFitY) >= xAdjustAbs)
                      {
                          adjustment.SetX(xAdjustmentToFitY);
                      }
                      else
                      {
                          float yAdjustmentToFitX = adjustment.GetX() * anchorDelta.GetY() / anchorDelta.GetX();
                          adjustment.SetY(yAdjustmentToFitX);
                      }
                  }
                  else
                  {
                      float yAdjustmentToFitX = adjustment.GetX() * anchorDelta.GetY() / anchorDelta.GetX();
                      if (fabsf(yAdjustmentToFitX) >= yAdjustAbs)
                      {
                          adjustment.SetY(yAdjustmentToFitX);
                      }
                      else
                      {
                          float xAdjustmentToFitY = adjustment.GetY() * anchorDelta.GetX() / anchorDelta.GetY();
                          adjustment.SetX(xAdjustmentToFitY);
                      }
                  }
              }
  
              // apply the adjustment to the anchors so that they stay in bounds
              anchors.m_left += adjustment.GetX();
              anchors.m_right += adjustment.GetX();
              anchors.m_top += adjustment.GetY();
              anchors.m_bottom += adjustment.GetY();
  
              // do an extra clamp just in case of rounding errors to ensure the anchor is never even a tiny amount out of range
              anchors.UnitClamp();
  
              // we will return the adjusted localTranslation which is the amount we actually moved in local space
              deltaInLocalSpace.SetX(deltaInLocalSpace.GetX() + adjustment.GetX() * parentSize.GetX());
              deltaInLocalSpace.SetY(deltaInLocalSpace.GetY() + adjustment.GetY() * parentSize.GetY());
          }
  
          EBUS_EVENT_ID(entityId, UiTransform2dBus, SetAnchors, anchors, false, false);
  
          return deltaInLocalSpace;
      }
  
      AZ::Vector2 TransformDeltaFromCanvasToLocalSpace(AZ::EntityId entityId, AZ::Vector2 deltaInCanvasSpace)
      {
          AZ::Vector3 deltaInCanvasSpace3(deltaInCanvasSpace.GetX(), deltaInCanvasSpace.GetY(), 0.0f);
          AZ::Matrix4x4 transform;
          EBUS_EVENT_ID(entityId, UiTransformBus, GetTransformFromCanvasSpace, transform);
  
          AZ::Vector3 deltaInLocalSpace3 = transform.Multiply3x3(deltaInCanvasSpace3);
          AZ::Vector2 deltaInLocalSpace = AZ::Vector2(deltaInLocalSpace3.GetX(), deltaInLocalSpace3.GetY());
  
          return deltaInLocalSpace;
      }
  
      AZ::Vector2 TransformDeltaFromLocalToCanvasSpace(AZ::EntityId entityId, AZ::Vector2 deltaInLocalSpace)
      {
          AZ::Vector3 deltaInLocalSpace3(deltaInLocalSpace.GetX(), deltaInLocalSpace.GetY(), 0.0f);
          AZ::Matrix4x4 transform;
          EBUS_EVENT_ID(entityId, UiTransformBus, GetTransformToCanvasSpace, transform);
  
          AZ::Vector3 deltaInCanvasSpace3 = transform.Multiply3x3(deltaInLocalSpace3);
          AZ::Vector2 deltaInCanvasSpace = AZ::Vector2(deltaInCanvasSpace3.GetX(), deltaInCanvasSpace3.GetY());
  
          return deltaInCanvasSpace;
      }
  
      AZ::Vector2 TransformDeltaFromViewportToCanvasSpace(AZ::EntityId canvasEntityId, AZ::Vector2 deltaInViewportSpace)
      {
          AZ::Vector3 deltaInViewportSpace3(deltaInViewportSpace.GetX(), deltaInViewportSpace.GetY(), 0.0f);
          AZ::Matrix4x4 transform;
          EBUS_EVENT_ID(canvasEntityId, UiCanvasBus, GetViewportToCanvasMatrix, transform);
  
          AZ::Vector3 deltaInCanvasSpace3 = transform.Multiply3x3(deltaInViewportSpace3);
          AZ::Vector2 deltaInLocalSpace = AZ::Vector2(deltaInCanvasSpace3.GetX(), deltaInCanvasSpace3.GetY());
  
          return deltaInLocalSpace;
      }
  
  }   // namespace EntityHelpers
  