From d2ab30e7a69bfe7efda63ae75812207377917bd3 Mon Sep 17 00:00:00 2001
From: miepzerino <o.skotnik@gmail.com>
Date: Sun, 30 Mar 2025 18:50:27 +0000
Subject: [PATCH] Merge branch 'Flexalon-UI-Layouts' into develop
---
Assets/Flexalon/Runtime/Interaction/FlexalonInteractable.cs | 914 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 914 insertions(+), 0 deletions(-)
diff --git a/Assets/Flexalon/Runtime/Interaction/FlexalonInteractable.cs b/Assets/Flexalon/Runtime/Interaction/FlexalonInteractable.cs
new file mode 100644
index 0000000..873387a
--- /dev/null
+++ b/Assets/Flexalon/Runtime/Interaction/FlexalonInteractable.cs
@@ -0,0 +1,914 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+using UnityEngine.Events;
+
+namespace Flexalon
+{
+ /// <summary> Allows a gameObject to be clicked and dragged. </summary>
+ [AddComponentMenu("Flexalon/Flexalon Interactable"), HelpURL("https://www.flexalon.com/docs/interactable"), DisallowMultipleComponent]
+ public class FlexalonInteractable : MonoBehaviour
+ {
+ [SerializeField]
+ private bool _clickable = false;
+ /// <summary> Determines if this object can be clicked and generate click events. </summary>
+ public bool Clickable {
+ get => _clickable;
+ set => _clickable = value;
+ }
+
+ [SerializeField]
+ private float _maxClickTime = 0.1f;
+ /// <summary>
+ /// With a mouse or touch input, a click is defined as a press and release.
+ /// The time between press and release must be less than Max Click Time to
+ /// count as a click. A drag interaction cannot start until Max Click Time is exceeded.
+ /// </summary>
+ public float MaxClickTime {
+ get => _maxClickTime;
+ set => _maxClickTime = value;
+ }
+
+ [SerializeField]
+ private bool _draggable = false;
+ /// <summary> Determines if this object can be dragged and generate drag events. </summary>
+ public bool Draggable {
+ get => _draggable;
+ set => _draggable = value;
+ }
+
+ [SerializeField]
+ private float _interpolationSpeed = 10;
+ /// <summary> How quickly the object moves towards the cursor when dragged. </summary>
+ public float InterpolationSpeed {
+ get => _interpolationSpeed;
+ set => _interpolationSpeed = value;
+ }
+
+ [SerializeField]
+ private float _insertRadius = 0.5f;
+ /// <summary> How close this object needs to a drag target's bounds to be inserted. </summary>
+ public float InsertRadius {
+ get => _insertRadius;
+ set => _insertRadius = value;
+ }
+
+ /// <summary> Restricts the movement of an object during a drag. </summary>
+ public enum RestrictionType
+ {
+ /// <summary> No restriction ensures the object can move freely. </summary>
+ None,
+
+ /// <summary> Plane restriction ensures the object moves along a plane, defined
+ /// by the objects initial position and the Plane Normal property. </summary>
+ Plane,
+
+ /// <summary> Line restriction ensures the object moves along a line, defined
+ /// by the object's initial position and the Line Direction property. </summary>
+ Line
+ }
+
+ [SerializeField]
+ private RestrictionType _restriction = RestrictionType.None;
+ /// <summary> Determines how to restrict the object's drag movement. </summary>
+ public RestrictionType Restriction {
+ get => _restriction;
+ set => _restriction = value;
+ }
+
+ [SerializeField]
+ private Vector3 _planeNormal = Vector3.up;
+ /// <summary> Defines the normal of the plane when using a plane restriction.
+ /// If 'Local Space' is checked, this normal is rotated by the transform
+ /// of the layout that the object started in. </summary>
+ public Vector3 PlaneNormal {
+ get => _planeNormal;
+ set
+ {
+ _restriction = RestrictionType.Plane;
+ _planeNormal = value;
+ }
+ }
+
+ [SerializeField]
+ private Vector3 _lineDirection = Vector3.right;
+ /// <summary> Defines the direction of the line when using a line restriction.
+ /// If 'Local Space'is checked, this direction is rotated by the transform
+ /// of the layout that the object started in. </summary>
+ public Vector3 LineDirection {
+ get => _lineDirection;
+ set
+ {
+ _restriction = RestrictionType.Line;
+ _lineDirection = value;
+ }
+ }
+
+ [SerializeField]
+ private bool _localSpaceRestriction = true;
+ /// <summary> When checked, the Plane Normal and Line Direction are applied in local space. </summary>
+ public bool LocalSpaceRestriction {
+ get => _localSpaceRestriction;
+ set => _localSpaceRestriction = value;
+ }
+
+ [SerializeField]
+ private Vector3 _holdOffset;
+ // <summary> When dragged, this option adds an offset to the dragged object's position.
+ // This can be used to float the object near the layout while it is being dragged.
+ // If 'Local Space' is checked, this offset is rotated and scaled by the transform
+ // of the layout that the object started in. </summary>
+ public Vector3 HoldOffset {
+ get => _holdOffset;
+ set => _holdOffset = value;
+ }
+
+ [SerializeField]
+ private bool _localSpaceOffset = true;
+ /// <summary> When checked, the Hold Offset is applied in local space. </summary>
+ public bool LocalSpaceOffset {
+ get => _localSpaceOffset;
+ set => _localSpaceOffset = value;
+ }
+
+ [SerializeField]
+ private bool _rotateOnDrag = false;
+ // <summary> When dragged, this option adds a rotation to the dragged object.
+ // This can be used to tilt the object while it is being dragged.
+ // If 'Local Space' is checked, this rotation will be in the local
+ // space of the layout that the object started in. </summary>
+ public bool RotateOnDrag {
+ get => _rotateOnDrag;
+ set => _rotateOnDrag = value;
+ }
+
+ [SerializeField]
+ private Quaternion _holdRotation;
+ /// <summary> The rotation to apply to the object when it is being dragged. </summary>
+ public Quaternion HoldRotation {
+ get => _holdRotation;
+ set
+ {
+ _rotateOnDrag = true;
+ _holdRotation = value;
+ }
+ }
+
+ [SerializeField]
+ private bool _localSpaceRotation = true;
+ /// <summary> When checked, the Hold Rotation is applied in local space. </summary>
+ public bool LocalSpaceRotation {
+ get => _localSpaceRotation;
+ set => _localSpaceRotation = value;
+ }
+
+ [SerializeField]
+ private bool _hideCursor = false;
+ /// <summary> When checked, Cursor.visible is set to false when the object is dragged. </summary>
+ public bool HideCursor {
+ get => _hideCursor;
+ set => _hideCursor = value;
+ }
+
+ [SerializeField]
+ private GameObject _handle = null;
+ /// <summary> GameObject to use to select and drag this object. If not set, uses self. </summary>
+ public GameObject Handle {
+ get => _handle;
+ set
+ {
+ _raycaster.Unregister(this);
+ _handle = value;
+ _raycaster.Register(this);
+ }
+ }
+
+#if UNITY_PHYSICS
+ [SerializeField, Obsolete("Use Handle instead.")]
+ private Collider _collider;
+
+ void OnValidate()
+ {
+ #pragma warning disable 618
+ if (_collider && !_handle)
+ {
+ _handle = _collider.gameObject;
+ _collider = null;
+ }
+ #pragma warning restore 618
+ }
+
+ [SerializeField]
+ private Collider _bounds;
+ /// <summary> If set, the object cannot be dragged outside of the bounds collider. </summary>
+ public Collider Bounds {
+ get => _bounds;
+ set => _bounds = value;
+ }
+#endif
+
+ [SerializeField]
+ private LayerMask _layerMask = -1;
+ /// <summary> When dragged, limits which Flexalon Drag Targets will accept this object
+ /// by comparing the Layer Mask to the target GameObject's layer. </summary>
+ public LayerMask LayerMask {
+ get => _layerMask;
+ set => _layerMask = value;
+ }
+
+ /// <summary> An event that occurs to a FlexalonInteractable. </summary>
+ [System.Serializable]
+ public class InteractableEvent : UnityEvent<FlexalonInteractable>{}
+
+ [SerializeField]
+ private InteractableEvent _clicked;
+ /// <summary> Unity Event invoked when the object is pressed and released within MaxClickTime. </summary>
+ public InteractableEvent Clicked => _clicked;
+
+ [SerializeField]
+ private InteractableEvent _hoverStart;
+ /// <summary> Unity Event invoked when the object starts being hovered. </summary>
+ public InteractableEvent HoverStart => _hoverStart;
+
+ [SerializeField]
+ private InteractableEvent _hoverEnd;
+ /// <summary> Unity Event invoked when the object stops being hovered. </summary>
+ public InteractableEvent HoverEnd => _hoverEnd;
+
+ [SerializeField]
+ private InteractableEvent _selectStart;
+ /// <summary> Unity Event invoked when the object starts being selected (e.g. press down mouse over object). </summary>
+ public InteractableEvent SelectStart => _selectStart;
+
+ [SerializeField]
+ private InteractableEvent _selectEnd;
+ /// <summary> Unity Event invoked when the object stops being selected (e.g. release mouse). </summary>
+ public InteractableEvent SelectEnd => _selectEnd;
+
+ [SerializeField]
+ private InteractableEvent _dragStart;
+ /// <summary> Unity Event invoked when the object starts being dragged. </summary>
+ public InteractableEvent DragStart => _dragStart;
+
+ [SerializeField]
+ private InteractableEvent _dragEnd;
+ /// <summary> Unity Event invoked when the object stops being dragged. </summary>
+ public InteractableEvent DragEnd => _dragEnd;
+
+ private static List<FlexalonInteractable> _hoveredObjects = new List<FlexalonInteractable>();
+ /// <summary> The currently hovered objects. </summary>
+ public static List<FlexalonInteractable> HoveredObjects => _hoveredObjects;
+
+ /// <summary> The first hovered object. </summary>
+ public static FlexalonInteractable HoveredObject => _hoveredObjects.Count > 0 ? _hoveredObjects[0] : null;
+
+ private static List<FlexalonInteractable> _selectedObjects = new List<FlexalonInteractable>();
+ /// <summary> The currently selected / dragged objects. </summary>
+ public static List<FlexalonInteractable> SelectedObjects => _selectedObjects;
+
+ /// <summary> The first selected / dragged object. </summary>
+ public static FlexalonInteractable SelectedObject => _selectedObjects.Count > 0 ? _selectedObjects[0] : null;
+
+ private Vector3 _target;
+ private Vector3 _lastTarget;
+ private float _distance;
+ private GameObject _placeholder;
+ private Vector3 _startPosition;
+ private int _startSiblingIndex;
+ private UnityEngine.Plane _plane = new UnityEngine.Plane();
+ private static FlexalonRaycaster _raycaster = new FlexalonRaycaster();
+ private Transform _localSpace;
+ private Transform _lastValidLocalSpace;
+ private float _selectTime;
+ private Vector3 _clickOffset;
+ private InputProvider _inputProvider;
+ private FlexalonNode _node;
+ private bool _wasActive;
+
+#if UNITY_UI
+ private Canvas _canvas;
+ internal Canvas Canvas => _canvas;
+#endif
+
+ // For Editor
+ internal bool _showAllDragProperties => GetInputProvider().InputMode == InputMode.Raycast;
+
+ /// <summary> The current state of the interactable. </summary>
+ public enum InteractableState
+ {
+ /// <summary> The object is not being interacted with. </summary>
+ Init,
+
+ /// <summary> The object is being hovered over. </summary>
+ Hovering,
+
+ /// <summary> The object is being selected (e.g. press down mouse over object). </summary>
+ Selecting,
+
+ /// <summary> The object is being dragged. </summary>
+ Dragging
+ }
+
+ private InteractableState _state = InteractableState.Init;
+ /// <summary> The current state of the interactable. </summary>
+ public InteractableState State => _state;
+
+ void Awake()
+ {
+ if (_clicked == null)
+ {
+ _clicked = new InteractableEvent();
+ }
+
+ if (_hoverStart == null)
+ {
+ _hoverStart = new InteractableEvent();
+ }
+
+ if (_hoverEnd == null)
+ {
+ _hoverEnd = new InteractableEvent();
+ }
+
+ if (_selectStart == null)
+ {
+ _selectStart = new InteractableEvent();
+ }
+
+ if (_selectEnd == null)
+ {
+ _selectEnd = new InteractableEvent();
+ }
+
+ if (_dragStart == null)
+ {
+ _dragStart = new InteractableEvent();
+ }
+
+ if (_dragEnd == null)
+ {
+ _dragEnd = new InteractableEvent();
+ }
+ }
+
+ void OnEnable()
+ {
+ _node = Flexalon.GetOrCreateNode(gameObject);
+ _inputProvider = GetInputProvider();
+
+ UpdateCanvas();
+
+ if (!_handle)
+ {
+ _handle = gameObject;
+ }
+
+ _raycaster.Register(this);
+ }
+
+ void OnDisable()
+ {
+ _raycaster.Unregister(this);
+ if (_state != InteractableState.Init)
+ {
+ UpdateState(_inputProvider.InputMode, default, false, false, false);
+ }
+
+ _node = null;
+ }
+
+ void Update()
+ {
+ var inputMode = _inputProvider.InputMode;
+ Vector3 uiPointer = _inputProvider.UIPointer;
+ Ray ray = _inputProvider.Ray;
+ bool isHit = false;
+ bool isActive = _inputProvider.Active;
+ bool becameActive = isActive && !_wasActive;
+ _wasActive = isActive;
+
+ if (inputMode == InputMode.Raycast)
+ {
+ if (_selectedObjects.Count == 0 || _selectedObjects[0] == this)
+ {
+ isHit = _raycaster.IsHit(uiPointer, ray, this);
+ }
+ }
+ else
+ {
+ var focusedObject = _inputProvider.ExternalFocusedObject;
+ isHit = focusedObject && focusedObject == gameObject;
+ }
+
+#if UNITY_UI
+ if (_canvas && _canvas.renderMode == RenderMode.ScreenSpaceOverlay)
+ {
+ ray = new Ray(uiPointer, Vector3.forward);
+ }
+#endif
+
+ UpdateState(inputMode, ray, isHit, isActive, becameActive);
+ }
+
+ void FixedUpdate()
+ {
+ if (_state != InteractableState.Dragging)
+ {
+ return;
+ }
+
+ if (_target == _lastTarget)
+ {
+ return;
+ }
+
+ var currentDragTarget = _placeholder.transform.parent ? _placeholder.transform.parent.GetComponent<FlexalonDragTarget>() : null;
+
+ // Find a drag target to insert into.
+ if (TryFindNearestDragTarget(currentDragTarget, out var newDragTarget, out var nearestChild))
+ {
+ AddToLayout(currentDragTarget, newDragTarget, nearestChild);
+ }
+ else
+ {
+ MovePlaceholder(null);
+ }
+
+ _lastTarget = _target;
+ }
+
+ internal void UpdateCanvas()
+ {
+#if UNITY_UI
+ if (_canvas)
+ {
+ return;
+ }
+
+ _canvas = GetComponentInParent<Canvas>();
+
+ if (_canvas)
+ {
+ if (_restriction == RestrictionType.None)
+ {
+ _restriction = RestrictionType.Plane;
+ _planeNormal = Vector3.forward;
+ }
+ }
+#endif
+
+ }
+
+ private InputProvider GetInputProvider()
+ {
+ var inputProvider = GetComponent<InputProvider>();
+ if (inputProvider == null)
+ {
+ inputProvider = Flexalon.GetInputProvider();
+ }
+
+ return inputProvider;
+ }
+
+ private void SetState(InteractableState state)
+ {
+ _state = state;
+ }
+
+ private void UpdateState(InputMode inputMode, Ray ray, bool isHit, bool isActive, bool becameActive)
+ {
+ if (_state == InteractableState.Init)
+ {
+ if (isHit && (!isActive || becameActive))
+ {
+ SetState(InteractableState.Hovering);
+ OnHoverStart();
+ }
+ }
+
+ if (_state == InteractableState.Hovering)
+ {
+ if (!isHit)
+ {
+ SetState(InteractableState.Init);
+ OnHoverEnd();
+ }
+ else if (becameActive)
+ {
+ SetState(InteractableState.Selecting);
+ OnSelectStart();
+ }
+ }
+
+ if (_state == InteractableState.Selecting)
+ {
+ if (!isActive)
+ {
+ if (_clickable && isHit && (Time.time - _selectTime <= _maxClickTime))
+ {
+ Clicked.Invoke(this);
+ }
+
+ if (isHit)
+ {
+ SetState(InteractableState.Hovering);
+ OnSelectEnd();
+ }
+ else
+ {
+ SetState(InteractableState.Init);
+ OnSelectEnd();
+ OnHoverEnd();
+ }
+
+ }
+ else if (_draggable && (!_clickable || (Time.time - _selectTime > _maxClickTime)))
+ {
+ SetState(InteractableState.Dragging);
+ OnDragStart(inputMode, ray);
+ }
+ }
+
+ if (_state == InteractableState.Dragging)
+ {
+ if (!isActive)
+ {
+ if (isHit)
+ {
+ SetState(InteractableState.Hovering);
+ OnDragEnd();
+ OnSelectEnd();
+ }
+ else
+ {
+ SetState(InteractableState.Init);
+ OnDragEnd();
+ OnSelectEnd();
+ OnHoverEnd();
+ }
+ }
+ else
+ {
+ OnDragMove(inputMode, ray);
+ }
+ }
+ }
+
+ private void OnHoverStart()
+ {
+ _hoveredObjects.Add(this);
+ HoverStart.Invoke(this);
+
+ // Save this here in case the input provider changes the parent on select.
+ _localSpace = transform.parent;
+ _startSiblingIndex = transform.GetSiblingIndex();
+ }
+
+ private void OnHoverEnd()
+ {
+ _hoveredObjects.Remove(this);
+ HoverEnd.Invoke(this);
+ _localSpace = null;
+ }
+
+ private void OnSelectStart()
+ {
+ _selectTime = Time.time;
+ _selectedObjects.Add(this);
+ SelectStart.Invoke(this);
+ }
+
+ private void OnSelectEnd()
+ {
+ _selectedObjects.Remove(this);
+ SelectEnd.Invoke(this);
+ }
+
+ private void OnDragStart(InputMode inputMode, Ray ray)
+ {
+ if (_hideCursor)
+ {
+ Cursor.visible = false;
+ }
+
+ _target = _lastTarget = transform.position;
+ _clickOffset = transform.position - _raycaster.hitPosition;
+ _distance = Vector3.Distance(_target, ray.origin + _clickOffset);
+ _startPosition = transform.position;
+
+ // Create a placeholder
+ _placeholder = new GameObject("Drag Placeholder");
+ var placeholderObj = Flexalon.AddComponent<FlexalonObject>(_placeholder);
+ _node = Flexalon.GetOrCreateNode(gameObject);
+ placeholderObj.Size = _node.Result.LayoutBounds.size;
+ placeholderObj.Rotation = _node.Rotation;
+ placeholderObj.Scale = _node.Scale;
+ placeholderObj.Margin = _node.Margin;
+ placeholderObj.Padding = _node.Padding;
+
+ _node.IsDragging = true;
+
+ // If we're in a valid drag target, swap with the placeholder.
+ var parentDragTarget = _localSpace ? _localSpace.GetComponent<FlexalonDragTarget>() : null;
+ if (CanAdd(parentDragTarget, parentDragTarget))
+ {
+ MovePlaceholder(_localSpace, _startSiblingIndex);
+
+ // Input provider may be changing the parent before we get here.
+ if (transform.parent == _localSpace)
+ {
+#if UNITY_UI
+ transform.SetParent(_canvas?.transform, true);
+#else
+ transform.SetParent(null, true);
+#endif
+ }
+ }
+ else
+ {
+ _placeholder.transform.SetParent(null);
+ _placeholder.SetActive(false);
+ }
+
+ DragStart.Invoke(this);
+ }
+
+ private void OnDragMove(InputMode inputMode, Ray ray)
+ {
+ if (inputMode == InputMode.External)
+ {
+ _target = transform.position;
+ }
+ else
+ {
+ UpdateTarget(ray);
+ UpdateObjectPosition();
+ }
+ }
+
+ private void OnDragEnd()
+ {
+ _node.IsDragging = false;
+
+ // Swap places with the placeholder and destroy it.
+ if (_placeholder.activeSelf)
+ {
+ transform.SetParent(_placeholder.transform.parent, true);
+ transform.SetSiblingIndex(_placeholder.transform.GetSiblingIndex());
+ }
+
+ _lastValidLocalSpace = null;
+ _placeholder.transform.SetParent(null);
+ Destroy(_placeholder);
+
+ if (_hideCursor)
+ {
+ Cursor.visible = true;
+ }
+
+ DragEnd.Invoke(this);
+ }
+
+ private static bool ClosestPointOnTwoLines(Vector3 p0, Vector3 v0, Vector3 p1, Vector3 v1, out Vector3 closestPointLine2)
+ {
+ closestPointLine2 = Vector3.zero;
+
+ float a = Vector3.Dot(v0, v0);
+ float b = Vector3.Dot(v0, v1);
+ float e = Vector3.Dot(v1, v1);
+
+ float d = a * e - b * b;
+
+ // Lines are not parallel
+ if (d != 0.0f)
+ {
+ Vector3 r = p0 - p1;
+ float c = Vector3.Dot(v0, r);
+ float f = Vector3.Dot(v1, r);
+ float t = (a * f - c * b) / d;
+ closestPointLine2 = p1 + v1 * t;
+ return true;
+ }
+
+ return false;
+ }
+
+ // Sets _target to where we want to move the dragged object -- based on the input ray, restrictions, and bounds.
+ private void UpdateTarget(Ray ray)
+ {
+ ray.origin += _clickOffset;
+
+ if (_restriction == RestrictionType.Line)
+ {
+ var lineDir = _lineDirection;
+ if (_localSpaceRestriction && _lastValidLocalSpace)
+ {
+ lineDir = _lastValidLocalSpace.rotation * _lineDirection;
+ }
+
+ if (!ClosestPointOnTwoLines(ray.origin, ray.direction, _startPosition, lineDir.normalized, out _target))
+ {
+ _target = _startPosition;
+ }
+ }
+ else if (_restriction == RestrictionType.Plane)
+ {
+ var normal = _planeNormal;
+ if (_localSpaceRestriction && _lastValidLocalSpace)
+ {
+ normal = _lastValidLocalSpace.rotation * _planeNormal;
+ }
+
+ _plane.SetNormalAndPosition(normal.normalized, _startPosition);
+ _plane.Raycast(ray, out var distance);
+ _target = ray.origin + ray.direction * distance;
+ }
+ else
+ {
+ // If there's no restriction, just project forward at the same distance as the placeholder.
+ if (_placeholder.gameObject.activeSelf && Flexalon.GetOrCreateNode(_placeholder).HasResult)
+ {
+ _distance = Vector3.Distance(ray.origin, _placeholder.transform.position);
+ }
+
+ _target = ray.origin + ray.direction * _distance;
+ }
+
+#if UNITY_PHYSICS
+ // Apply bounds restriction
+ if (_bounds && !_bounds.bounds.Contains(_target))
+ {
+ _target = _bounds.ClosestPointOnBounds(_target);
+ }
+#endif
+ }
+
+ private void UpdateObjectPosition()
+ {
+ // Apply hold offset
+ var offset = Vector3.zero;
+ if (_localSpaceOffset && _localSpace)
+ {
+ offset = _localSpace.localToWorldMatrix.MultiplyVector(_holdOffset);
+ }
+ else if (!_localSpaceOffset)
+ {
+ offset = _holdOffset;
+ }
+
+ // Interpolate object towards target.
+ transform.position = Vector3.Lerp(transform.position, _target + offset, Time.deltaTime * _interpolationSpeed);
+
+ // Apply hold rotation
+ if (_rotateOnDrag)
+ {
+ var rotation = Quaternion.identity;
+ if (_localSpaceRotation && _localSpace)
+ {
+ rotation = _localSpace.rotation * _holdRotation;
+ }
+ else if (!_localSpaceRotation)
+ {
+ rotation = _holdRotation;
+ }
+
+ transform.rotation = Quaternion.Lerp(transform.rotation, rotation, Time.deltaTime * _interpolationSpeed);
+ }
+ }
+
+ private bool TryFindNearestChild(FlexalonDragTarget dragTarget, out Transform nearestChild, out float distanceSquared)
+ {
+ var moveDirection = (_target - _lastTarget).normalized;
+ nearestChild = null;
+ distanceSquared = float.MaxValue;
+ foreach (Transform child in dragTarget.transform)
+ {
+ var childPos = dragTarget.transform.localToWorldMatrix.MultiplyPoint(child.GetComponent<FlexalonResult>().TargetPosition);
+ var toChild = (childPos - _lastTarget).normalized;
+ if (child == _placeholder.transform || Vector3.Dot(toChild, moveDirection) > 0)
+ {
+ var distSq = Vector3.SqrMagnitude(childPos - _target);
+ if (distSq < distanceSquared)
+ {
+ distanceSquared = distSq;
+ nearestChild = child;
+ }
+ }
+ }
+
+ return nearestChild != null;
+ }
+
+ // Find a drag target to insert into by checking if it contains the target point.
+ private bool TryFindNearestDragTarget(FlexalonDragTarget currentDragTarget, out FlexalonDragTarget dragTarget, out Transform nearestChild)
+ {
+ if (!CanLeave(currentDragTarget))
+ {
+ dragTarget = currentDragTarget;
+ TryFindNearestChild(currentDragTarget, out nearestChild, out var distanceSquared);
+ return true;
+ }
+
+ dragTarget = null;
+ nearestChild = null;
+ var minDistance = float.MaxValue;
+ GetInsertPositionAndRadius(_node, _target, out var worldInsertPosition, out var worldInsertRadius);
+
+ foreach (var candidate in FlexalonDragTarget.DragTargets)
+ {
+ if (CanAdd(currentDragTarget, candidate) && candidate.OverlapsSphere(worldInsertPosition, worldInsertRadius))
+ {
+ if (TryFindNearestChild(candidate, out var candidateNearestChild, out var distanceSquared))
+ {
+ if (distanceSquared < minDistance)
+ {
+ minDistance = distanceSquared;
+ dragTarget = candidate;
+ nearestChild = candidateNearestChild;
+ }
+ }
+ else if (dragTarget == null)
+ {
+ dragTarget = candidate;
+ break;
+ }
+ }
+
+ }
+
+ return dragTarget != null;
+ }
+
+ // Moves the placeholder into the drag target at a particular index.
+ private void MovePlaceholder(Transform newParent, int siblingIndex = 0)
+ {
+ if (newParent != _placeholder.transform.parent || siblingIndex != _placeholder.transform.GetSiblingIndex())
+ {
+ _placeholder.SetActive(!!newParent);
+ _placeholder.transform.SetParent(newParent);
+ if (newParent)
+ {
+ _placeholder.transform.SetSiblingIndex(siblingIndex);
+ _lastValidLocalSpace = newParent;
+ }
+
+ _localSpace = newParent;
+ }
+ }
+
+ // Finds an appropriate place to add the placeholder into the drag target.
+ private void AddToLayout(FlexalonDragTarget currentDragTarget, FlexalonDragTarget newDragTarget, Transform nearestChild)
+ {
+ var insertIndex = nearestChild ? nearestChild.GetSiblingIndex() : 0;
+
+ // Special case -- if adding a new item at the end, the user usually wants to place
+ // it after the last element.
+ if (currentDragTarget != newDragTarget && insertIndex == newDragTarget.transform.childCount - 1)
+ {
+ insertIndex++;
+ }
+
+ MovePlaceholder(newDragTarget.transform, insertIndex);
+ }
+
+ private bool CanLeave(FlexalonDragTarget dragTarget)
+ {
+ return dragTarget == null ||
+ (dragTarget.CanRemoveObjects && dragTarget.transform.childCount > dragTarget.MinObjects);
+ }
+
+ private bool CanAdd(FlexalonDragTarget currentDragTarget, FlexalonDragTarget dragTarget)
+ {
+ if (currentDragTarget == dragTarget)
+ {
+ return true;
+ }
+
+ return dragTarget != null &&
+ dragTarget.gameObject != gameObject &&
+ dragTarget.CanAddObjects &&
+ (dragTarget.MaxObjects == 0 || dragTarget.transform.childCount < dragTarget.MaxObjects) &&
+ (_layerMask.value & (1 << dragTarget.gameObject.layer)) != 0;
+ }
+
+ private void GetInsertPositionAndRadius(FlexalonNode node, Vector3 target, out Vector3 position, out float radius)
+ {
+ var worldBoxScale = node.GetWorldBoxScale(true);
+ var scale = transform.lossyScale;
+ radius = _insertRadius * Mathf.Max(scale.x, scale.y, scale.z);
+ position = target;
+ }
+
+ private void OnDrawGizmosSelected()
+ {
+ var node = Flexalon.GetNode(gameObject);
+ if (node != null && _draggable)
+ {
+ Gizmos.color = Color.green;
+ var target = _state == InteractableState.Dragging ? _target : transform.position;
+ GetInsertPositionAndRadius(node, target, out var insertPosition, out var insertRadius);
+ Gizmos.DrawWireSphere(insertPosition, insertRadius);
+ }
+ }
+ }
+}
\ No newline at end of file
--
Gitblit v1.10.0