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.9.3