using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.UIElements; namespace RosettaUI.UIToolkit.AnimationCurveEditor { /// /// Control point of the animation curve editor. /// public class ControlPoint : VisualElement { public PointMode PointMode { get; private set; } = PointMode.Smooth; public TangentMode InTangentMode { get; private set; } = TangentMode.Free; public TangentMode OutTangentMode { get; private set; } = TangentMode.Free; private Vector2 PositionInCurve { get => new Vector2(_keyframeCopy.time, _keyframeCopy.value); set { _keyframeCopy.time = value.x; _keyframeCopy.value = value.y; } } private readonly OnPointAction _onPointSelected; private readonly OnPointMoved _onPointMoved; private readonly OnPointAction _onPointRemoved; private Vector2 _elementPositionOnDown; private Vector2 _mouseDownPosition; private readonly ICoordinateConverter _coordinateConverter; private readonly VisualElement _controlPoint; private readonly ControlPointHandle _leftHandle; private readonly ControlPointHandle _rightHandle; private readonly ControlPointPopupMenuController _popupMenuController; private Keyframe _keyframeCopy; private readonly VisualElementKeyEventHelper _keyEventHelper; private const string ContainerClassName = "rosettaui-animation-curve-editor__control-point-container"; private const string ControlPointClassName = "rosettaui-animation-curve-editor__control-point"; private const string ActiveControlPointClassName = "rosettaui-animation-curve-editor__control-point--active"; public delegate void OnPointAction(ControlPoint controlPoint); public delegate int OnPointMoved(Keyframe keyframe); public ControlPoint(ICoordinateConverter coordinateConverter, OnPointAction onPointSelected, OnPointMoved onPointMoved, OnPointAction onPointRemoved) { _coordinateConverter = coordinateConverter; _onPointSelected = onPointSelected; _onPointMoved = onPointMoved; _onPointRemoved = onPointRemoved; _keyEventHelper = new VisualElementKeyEventHelper(this); _keyEventHelper.RegisterKeyAction(new[] { KeyCode.LeftAlt, KeyCode.RightAlt }, type => { if (type != KeyEventType.KeyDown) return; SetPointMode(PointMode.Broken); }); // Handles _leftHandle = new ControlPointHandle(_coordinateConverter, -1f, () => _onPointSelected(this), (tangent, weight) => { _keyframeCopy.inTangent = tangent; _keyframeCopy.inWeight = _keyframeCopy.weightedMode is WeightedMode.In or WeightedMode.Both ? weight : 0.333333f; if (PointMode == PointMode.Flat) { PointMode = PointMode.Smooth; } if (PointMode == PointMode.Smooth) { if (float.IsInfinity(_keyframeCopy.inTangent)) _keyframeCopy.outTangent = -_keyframeCopy.inTangent; _keyframeCopy.outTangent = _keyframeCopy.inTangent; } _onPointMoved(_keyframeCopy); } ); Add(_leftHandle); _rightHandle = new ControlPointHandle(_coordinateConverter, 1f, () => _onPointSelected(this), (tangent, weight) => { _keyframeCopy.outTangent = tangent; _keyframeCopy.outWeight = _keyframeCopy.weightedMode is WeightedMode.Out or WeightedMode.Both ? weight : 0.333333f; if (PointMode == PointMode.Flat) { PointMode = PointMode.Smooth; } if (PointMode == PointMode.Smooth) { if (float.IsInfinity(_keyframeCopy.outTangent)) _keyframeCopy.inTangent = -_keyframeCopy.outTangent; _keyframeCopy.inTangent = _keyframeCopy.outTangent; } _onPointMoved(_keyframeCopy); } ); Add(_rightHandle); // Control point container AddToClassList(ContainerClassName); RegisterCallback(OnMouseDown); // Control point _controlPoint = new VisualElement { name = "AnimationCurveEditorControlPoint" }; _controlPoint.AddToClassList(ControlPointClassName); Add(_controlPoint); // Popup menu controller _popupMenuController = new ControlPointPopupMenuController( () => _onPointRemoved(this), SetPointModeAndUpdateView, SetTangentModeAndUpdateView, ToggleWeightedModeAndUpdateView ); return; void SetPointModeAndUpdateView(PointMode mode) { SetPointMode(mode); switch (mode) { case PointMode.Broken: break; case PointMode.Flat: _keyframeCopy.inTangent = 0f; _keyframeCopy.outTangent = 0f; break; case PointMode.Smooth: _keyframeCopy.inTangent = _keyframeCopy.outTangent; break; } _onPointMoved(_keyframeCopy); } void SetTangentModeAndUpdateView(TangentMode? inTangentMode, TangentMode? outTangentMode) { SetTangentMode(inTangentMode, outTangentMode); _onPointMoved(_keyframeCopy); } void ToggleWeightedModeAndUpdateView(WeightedMode mode) { _keyframeCopy.ToggleWeightedFrag(mode); _onPointMoved(_keyframeCopy); _popupMenuController.SetWeightedMode(_keyframeCopy.weightedMode); } } public void SetActive(bool active) { if (active) { _controlPoint.AddToClassList(ActiveControlPointClassName); _leftHandle.style.visibility = Visibility.Visible; _rightHandle.style.visibility = Visibility.Visible; } else { _controlPoint.RemoveFromClassList(ActiveControlPointClassName); _leftHandle.style.visibility = Visibility.Hidden; _rightHandle.style.visibility = Visibility.Hidden; } } public void SetPointMode(PointMode mode) { PointMode = mode; _popupMenuController.SetPointMode(mode); if (mode != PointMode.Broken) { SetTangentMode(TangentMode.Free, TangentMode.Free); } } public void SetTangentMode(TangentMode? inTangentMode, TangentMode? outTangentMode) { InTangentMode = inTangentMode ?? InTangentMode; OutTangentMode = outTangentMode ?? OutTangentMode; _popupMenuController.SetTangentMode(InTangentMode, OutTangentMode); } public void SetWeightedMode(WeightedMode mode) { _keyframeCopy.weightedMode = mode; _popupMenuController.SetWeightedMode(mode); } /// /// Set the keyframe of the control point /// (without applying the point mode or tangent mode) /// public void SetKeyframe(in AnimationCurve curve, int idx) { _keyframeCopy = curve[idx]; // Set Position var screenPosition = _coordinateConverter.GetScreenPosFromCurvePos(PositionInCurve); style.left = screenPosition.x; style.top = screenPosition.y; _leftHandle.SetTangent(_keyframeCopy.inTangent); _rightHandle.SetTangent(_keyframeCopy.outTangent); float? inWeight = _keyframeCopy.weightedMode is WeightedMode.In or WeightedMode.Both ? _keyframeCopy.inWeight : null; float? outWeight = _keyframeCopy.weightedMode is WeightedMode.Out or WeightedMode.Both ? _keyframeCopy.outWeight : null; _leftHandle.SetWeight(inWeight, GetXDistInScreen(curve, idx - 1, idx)); _rightHandle.SetWeight(outWeight, GetXDistInScreen(curve, idx, idx + 1)); return; float GetXDistInScreen(in AnimationCurve curve, int leftIdx, int rightIdx) { if (leftIdx < 0 || curve.length <= rightIdx) return 0f; return _coordinateConverter.GetScreenPosFromCurvePos(curve[rightIdx].GetPosition()).x - _coordinateConverter.GetScreenPosFromCurvePos(curve[leftIdx].GetPosition()).x; } } private void SetPointerPosition(Vector2 pointerPosition) { Vector2 screenPosition = new Vector2(_elementPositionOnDown.x + (pointerPosition.x - _mouseDownPosition.x), _elementPositionOnDown.y + (pointerPosition.y - _mouseDownPosition.y)); style.left = screenPosition.x; style.top = screenPosition.y; PositionInCurve = _coordinateConverter.GetCurvePosFromScreenPos(screenPosition); _onPointMoved(_keyframeCopy); } private void OnMouseDown(PointerDownEvent evt) { _mouseDownPosition = evt.position; _elementPositionOnDown = new Vector2(style.left.value.value, style.top.value.value); _onPointSelected?.Invoke(this); if (evt.button == 0) { RegisterCallback(OnPointerMove); RegisterCallback(OnPointerUp); evt.StopPropagation(); this.CaptureMouse(); } else if (evt.button == 1) { _popupMenuController.Show(_mouseDownPosition, this); evt.StopPropagation(); } } private void OnPointerMove(PointerMoveEvent evt) { SetPointerPosition(evt.position); } private void OnPointerUp(PointerUpEvent evt) { if (evt.button == 0) { UnregisterCallback(OnPointerMove); UnregisterCallback(OnPointerUp); evt.StopPropagation(); this.ReleaseMouse(); } } } }