using System;
using System.Collections.Generic;
using RosettaUI.Builder;
using UnityEngine;
using UnityEngine.UIElements;
namespace RosettaUI.UIToolkit.AnimationCurveEditor
{
///
/// A visual element that allows you to edit an .
///
public class AnimationCurveEditor : VisualElement, IDisposable
{
#region Static Window Management
private static ModalWindow _window;
private static AnimationCurveEditor _animationCurveEditorInstance;
static AnimationCurveEditor()
{
StaticResourceUtility.AddResetStaticResourceCallback(ReleaseStaticResource);
void ReleaseStaticResource()
{
_window?.Hide();
_window = null;
_animationCurveEditorInstance?.Dispose();
_animationCurveEditorInstance = null;
}
}
public static void Show(Vector2 position, VisualElement target, AnimationCurve initialCurve,
Action onCurveChanged)
{
if (_window == null)
{
_window = new ModalWindow(true) { style = { width = 800, height = 500, minWidth = 500 } };
_animationCurveEditorInstance = new AnimationCurveEditor();
_window.Add(_animationCurveEditorInstance);
_window.RegisterCallback(_ => _window.Hide());
_window.RegisterCallback(_ =>
{
onCurveChanged?.Invoke(initialCurve);
_window.Hide();
});
}
_window.Show(position, target);
// Preventing Overflow
if (!float.IsNaN(_window.resolvedStyle.width) && !float.IsNaN(_window.resolvedStyle.height))
{
VisualElementExtension.CheckOutOfScreen(position, _window);
}
// Panel is not set before Show(), so callback registration and other operations are performed after Show().
_animationCurveEditorInstance.SetCurve(initialCurve);
_animationCurveEditorInstance.OnCurveChanged += onCurveChanged;
_animationCurveEditorInstance.RegisterCallback(OnDetach);
return;
void OnDetach(DetachFromPanelEvent _)
{
_animationCurveEditorInstance.OnCurveChanged -= onCurveChanged;
_animationCurveEditorInstance.UnregisterCallback(OnDetach);
target?.Focus();
}
}
#endregion
public event Action OnCurveChanged;
private CurvePointContainer _curvePointContainer;
private CurvePreview _curvePreview;
private RenderTexture _curveEditorTexture = null;
private VisualElement _curvePreviewElement;
private VisualElementKeyEventHelper _keyEventHelper;
private ToggleButton _snapXButton;
private ToggleButton _snapYButton;
private ScrollerController _scrollerController;
private AxisLabelController _axisLabelController;
private PropertyFieldController _propertyFieldController;
private PreviewTransform _previewTransform;
private int _selectedControlPointIndex = -1;
private int _mouseButton;
private Vector2 _prevPointerPosition;
private long _lastPointerDownTime;
private bool _prevSnapX = false;
private bool _prevSnapY = false;
private static readonly string USSClassName = "rosettaui-animation-curve-editor";
private static readonly Vector2 ZoomRange = new(0.0001f, 10000f);
private static readonly float FitViewPadding = 0.05f;
public AnimationCurveEditor()
{
var visualTreeAsset = Resources.Load("RosettaUI_AnimationCurveEditor");
visualTreeAsset.CloneTree(this);
AddToClassList(USSClassName);
InitUI();
_curvePointContainer = new CurvePointContainer(_curvePreviewElement, () => new ControlPoint(_previewTransform, OnControlPointSelected, OnControlPointMoved, RemoveControlPoint));
}
///
/// Set the curve to be edited.
///
public void SetCurve(AnimationCurve curve)
{
_curvePointContainer.SetCurve(curve);
FitViewToCurve();
UpdateView();
UnselectAllControlPoint();
}
public void Dispose()
{
if (_curvePreview != null)
{
_curvePreview.Dispose();
_curvePreview = null;
}
if (_curveEditorTexture != null)
{
UnityEngine.Object.DestroyImmediate(_curveEditorTexture);
_curveEditorTexture = null;
}
}
private void MoveKey(Keyframe key)
{
if (_curvePointContainer.IsEmpty || _selectedControlPointIndex < 0) return;
var gridViewport = new GridViewport(_previewTransform.GetPreviewRect());
if (_snapXButton.value) { key.time = gridViewport.RoundX(key.time, 0.05f); }
if (_snapYButton.value) { key.value = gridViewport.RoundY(key.value, 0.05f); }
_selectedControlPointIndex = _curvePointContainer.MoveKey(_selectedControlPointIndex, key);
}
#region Initialization
private void InitUI()
{
// Key Bind
_keyEventHelper = new VisualElementKeyEventHelper(this);
_keyEventHelper.RegisterKeyAction(new [] { KeyCode.LeftControl, KeyCode.LeftCommand, KeyCode.RightControl, KeyCode.RightCommand }, evt =>
{
switch (evt)
{
case KeyEventType.KeyDown:
_prevSnapX = _snapXButton.value;
_prevSnapY = _snapYButton.value;
_snapXButton.SetValueWithoutNotify(true);
_snapYButton.SetValueWithoutNotify(true);
break;
case KeyEventType.KeyUp:
_snapXButton.SetValueWithoutNotify(_prevSnapX);
_snapYButton.SetValueWithoutNotify(_prevSnapY);
break;
case KeyEventType.KeyPress:
default:
break;
}
});
_keyEventHelper.RegisterKeyAction(KeyCode.A, evt =>
{
if (evt == KeyEventType.KeyUp) { FitViewToCurve(); }
});
_keyEventHelper.RegisterKeyAction(KeyCode.Delete, evt =>
{
if (evt != KeyEventType.KeyUp || _selectedControlPointIndex < 0) return;
RemoveControlPoint(_curvePointContainer.ControlPoints[_selectedControlPointIndex]);
});
// Buttons
var fitButton = this.Q