using System;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
using PointerType = UnityEngine.UIElements.PointerType;
namespace Unity.Cinemachine.Editor
{
interface IDelayedFriendlyDragger
{
/// If true, temporarily disable isDelayed when dragging
public bool CancelDelayedWhenDragging { get; set; }
/// Called when dragging starts.
public Action OnStartDrag { get; set; }
/// Called when dragging stops.
public Action OnStopDrag { get; set; }
/// Called when the value changes during dragging.
public Action OnDragValueChangedInt { get; set; }
/// Called when the value changes during dragging.
public Action OnDragValueChangedFloat { get; set; }
/// Get the VisualElement being dragged
public VisualElement DragElement { get; }
}
///
/// Provides dragging on a visual element to change a value field with
/// isDelayed set, but for int and float driven fields can turn off isDelayed while dragging.
///
class DelayedFriendlyFieldDragger : BaseFieldMouseDragger, IDelayedFriendlyDragger
{
/// DelayedFriendlyFieldDragger's constructor.///
/// The field.
public DelayedFriendlyFieldDragger(IValueField drivenField)
{
m_DrivenField = drivenField;
m_DragElement = null;
m_DragHotZone = new Rect(0, 0, -1, -1);
dragging = false;
}
private readonly IValueField m_DrivenField;
private VisualElement m_DragElement;
private Rect m_DragHotZone;
private bool m_WasDelayed;
/// Is dragging.
public bool dragging { get; set; }
///
public T startValue { get; set; }
///
public bool CancelDelayedWhenDragging { get; set; }
///
public Action OnStartDrag { get; set; }
///
public Action OnStopDrag { get; set; }
///
public Action OnDragValueChangedInt { get; set; }
///
public Action OnDragValueChangedFloat { get; set; }
///
public VisualElement DragElement => m_DragElement;
///
public sealed override void SetDragZone(VisualElement dragElement, Rect hotZone)
{
if (m_DragElement != null)
{
m_DragElement.UnregisterCallback(UpdateValueOnPointerDown, TrickleDown.TrickleDown);
m_DragElement.UnregisterCallback(UpdateValueOnPointerUp);
m_DragElement.UnregisterCallback(UpdateValueOnKeyDown);
}
m_DragElement = dragElement;
m_DragHotZone = hotZone;
if (m_DragElement != null)
{
dragging = false;
m_DragElement.RegisterCallback(UpdateValueOnPointerDown, TrickleDown.TrickleDown);
m_DragElement.RegisterCallback(UpdateValueOnPointerUp);
m_DragElement.RegisterCallback(UpdateValueOnKeyDown);
}
}
private bool CanStartDrag(int button, Vector2 localPosition)
{
return button == 0 && (m_DragHotZone.width < 0 || m_DragHotZone.height < 0 ||
m_DragHotZone.Contains(m_DragElement.WorldToLocal(localPosition)));
}
private void UpdateValueOnPointerDown(PointerDownEvent evt)
{
if (CanStartDrag(evt.button, evt.localPosition))
{
// We want to allow dragging when using a mouse in any context and when in an Editor context with any pointer type.
if (evt.pointerType == PointerType.mouse)
{
m_DragElement.CaptureMouse();
ProcessDownEvent(evt);
}
else if (m_DragElement.panel.contextType == ContextType.Editor)
{
m_DragElement.CapturePointer(evt.pointerId);
ProcessDownEvent(evt);
}
}
}
private void ProcessDownEvent(EventBase evt)
{
// Make sure no other elements can capture the mouse!
evt.StopPropagation();
dragging = true;
m_DragElement.RegisterCallback(UpdateValueOnPointerMove);
startValue = m_DrivenField.value;
if (m_DrivenField is TextInputBaseField floatField)
{
m_WasDelayed = floatField.isDelayed;
if (CancelDelayedWhenDragging)
floatField.isDelayed = false;
}
else if (m_DrivenField is TextInputBaseField intField)
{
m_WasDelayed = intField.isDelayed;
if (CancelDelayedWhenDragging)
intField.isDelayed = false;
}
m_DrivenField.StartDragging();
EditorGUIUtility.SetWantsMouseJumping(1);
OnStartDrag?.Invoke(this);
}
private void UpdateValueOnPointerMove(PointerMoveEvent evt)
{
ProcessMoveEvent(evt.shiftKey, evt.altKey, evt.deltaPosition);
}
private void ProcessMoveEvent(bool shiftKey, bool altKey, Vector2 deltaPosition)
{
if (dragging)
{
DeltaSpeed s = shiftKey ? DeltaSpeed.Fast : (altKey ? DeltaSpeed.Slow : DeltaSpeed.Normal);
m_DrivenField.ApplyInputDeviceDelta(deltaPosition, s, startValue);
if (OnDragValueChangedFloat != null && m_DrivenField is TextInputBaseField floatField)
{
var textElement = floatField.Q();
if (textElement != null)
OnDragValueChangedFloat.Invoke((float)(object)float.Parse(textElement.text));
}
else if (OnDragValueChangedInt != null && m_DrivenField is TextInputBaseField intField)
{
var textElement = intField.Q();
if (textElement != null)
OnDragValueChangedInt.Invoke((int)(object)int.Parse(textElement.text));
}
}
}
private void UpdateValueOnPointerUp(PointerUpEvent evt)
{
ProcessUpEvent(evt, evt.pointerId);
}
private void ProcessUpEvent(EventBase evt, int pointerId)
{
if (dragging)
{
OnStopDrag?.Invoke(this);
dragging = false;
m_DragElement.UnregisterCallback(UpdateValueOnPointerMove);
m_DragElement.ReleasePointer(pointerId);
//if (evt is IMouseEvent)
// m_DragElement.panel.ProcessPointerCapture(PointerId.mousePointerId);
EditorGUIUtility.SetWantsMouseJumping(0);
m_DrivenField.StopDragging();
if (m_WasDelayed)
{
if (m_DrivenField is TextInputBaseField floatField)
floatField.isDelayed = true;
else if (m_DrivenField is TextInputBaseField intField)
intField.isDelayed = true;
}
}
}
private void UpdateValueOnKeyDown(KeyDownEvent evt)
{
if (dragging && evt.keyCode == KeyCode.Escape)
{
dragging = false;
m_DrivenField.value = startValue;
m_DrivenField.StopDragging();
var target = evt.target as VisualElement;
IPanel panel = target?.panel;
panel?.ReleasePointer(PointerId.mousePointerId);
EditorGUIUtility.SetWantsMouseJumping(0);
}
}
}
}