Split CurveEditor into separate files for easier navigation
This commit is contained in:
261
Source/Editor/GUI/CurveEditor.Access.cs
Normal file
261
Source/Editor/GUI/CurveEditor.Access.cs
Normal file
@@ -0,0 +1,261 @@
|
||||
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEngine;
|
||||
|
||||
// ReSharper disable RedundantAssignment
|
||||
|
||||
namespace FlaxEditor.GUI
|
||||
{
|
||||
partial class CurveEditor<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// The generic keyframe value accessor object for curve editor.
|
||||
/// </summary>
|
||||
/// <typeparam name="U">The keyframe value type.</typeparam>
|
||||
public interface IKeyframeAccess<U> where U : new()
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the default value.
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
void GetDefaultValue(out U value);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the curve components count. Vector types should return amount of component to use for value editing.
|
||||
/// </summary>
|
||||
/// <returns>The components count.</returns>
|
||||
int GetCurveComponents();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of the component for the curve.
|
||||
/// </summary>
|
||||
/// <param name="value">The keyframe value.</param>
|
||||
/// <param name="component">The component index.</param>
|
||||
/// <returns>The curve value.</returns>
|
||||
float GetCurveValue(ref U value, int component);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the curve value of the component.
|
||||
/// </summary>
|
||||
/// <param name="curve">The curve value to assign.</param>
|
||||
/// <param name="value">The keyframe value.</param>
|
||||
/// <param name="component">The component index.</param>
|
||||
void SetCurveValue(float curve, ref U value, int component);
|
||||
}
|
||||
|
||||
private class KeyframeAccess :
|
||||
IKeyframeAccess<bool>,
|
||||
IKeyframeAccess<int>,
|
||||
IKeyframeAccess<double>,
|
||||
IKeyframeAccess<float>,
|
||||
IKeyframeAccess<Vector2>,
|
||||
IKeyframeAccess<Vector3>,
|
||||
IKeyframeAccess<Vector4>,
|
||||
IKeyframeAccess<Quaternion>,
|
||||
IKeyframeAccess<Color32>,
|
||||
IKeyframeAccess<Color>
|
||||
{
|
||||
void IKeyframeAccess<bool>.GetDefaultValue(out bool value)
|
||||
{
|
||||
value = false;
|
||||
}
|
||||
|
||||
int IKeyframeAccess<bool>.GetCurveComponents()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
float IKeyframeAccess<bool>.GetCurveValue(ref bool value, int component)
|
||||
{
|
||||
return value ? 1 : 0;
|
||||
}
|
||||
|
||||
void IKeyframeAccess<bool>.SetCurveValue(float curve, ref bool value, int component)
|
||||
{
|
||||
value = curve >= 0.5f;
|
||||
}
|
||||
|
||||
void IKeyframeAccess<int>.GetDefaultValue(out int value)
|
||||
{
|
||||
value = 0;
|
||||
}
|
||||
|
||||
int IKeyframeAccess<int>.GetCurveComponents()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
float IKeyframeAccess<int>.GetCurveValue(ref int value, int component)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
void IKeyframeAccess<int>.SetCurveValue(float curve, ref int value, int component)
|
||||
{
|
||||
value = (int)curve;
|
||||
}
|
||||
|
||||
void IKeyframeAccess<double>.GetDefaultValue(out double value)
|
||||
{
|
||||
value = 0.0;
|
||||
}
|
||||
|
||||
int IKeyframeAccess<double>.GetCurveComponents()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
float IKeyframeAccess<double>.GetCurveValue(ref double value, int component)
|
||||
{
|
||||
return (float)value;
|
||||
}
|
||||
|
||||
void IKeyframeAccess<double>.SetCurveValue(float curve, ref double value, int component)
|
||||
{
|
||||
value = curve;
|
||||
}
|
||||
|
||||
void IKeyframeAccess<float>.GetDefaultValue(out float value)
|
||||
{
|
||||
value = 0.0f;
|
||||
}
|
||||
|
||||
int IKeyframeAccess<float>.GetCurveComponents()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
float IKeyframeAccess<float>.GetCurveValue(ref float value, int component)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
void IKeyframeAccess<float>.SetCurveValue(float curve, ref float value, int component)
|
||||
{
|
||||
value = curve;
|
||||
}
|
||||
|
||||
void IKeyframeAccess<Vector2>.GetDefaultValue(out Vector2 value)
|
||||
{
|
||||
value = Vector2.Zero;
|
||||
}
|
||||
|
||||
int IKeyframeAccess<Vector2>.GetCurveComponents()
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
float IKeyframeAccess<Vector2>.GetCurveValue(ref Vector2 value, int component)
|
||||
{
|
||||
return value[component];
|
||||
}
|
||||
|
||||
void IKeyframeAccess<Vector2>.SetCurveValue(float curve, ref Vector2 value, int component)
|
||||
{
|
||||
value[component] = curve;
|
||||
}
|
||||
|
||||
void IKeyframeAccess<Vector3>.GetDefaultValue(out Vector3 value)
|
||||
{
|
||||
value = Vector3.Zero;
|
||||
}
|
||||
|
||||
int IKeyframeAccess<Vector3>.GetCurveComponents()
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
|
||||
float IKeyframeAccess<Vector3>.GetCurveValue(ref Vector3 value, int component)
|
||||
{
|
||||
return value[component];
|
||||
}
|
||||
|
||||
void IKeyframeAccess<Vector3>.SetCurveValue(float curve, ref Vector3 value, int component)
|
||||
{
|
||||
value[component] = curve;
|
||||
}
|
||||
|
||||
void IKeyframeAccess<Vector4>.GetDefaultValue(out Vector4 value)
|
||||
{
|
||||
value = Vector4.Zero;
|
||||
}
|
||||
|
||||
int IKeyframeAccess<Vector4>.GetCurveComponents()
|
||||
{
|
||||
return 4;
|
||||
}
|
||||
|
||||
float IKeyframeAccess<Vector4>.GetCurveValue(ref Vector4 value, int component)
|
||||
{
|
||||
return value[component];
|
||||
}
|
||||
|
||||
void IKeyframeAccess<Vector4>.SetCurveValue(float curve, ref Vector4 value, int component)
|
||||
{
|
||||
value[component] = curve;
|
||||
}
|
||||
|
||||
public void GetDefaultValue(out Quaternion value)
|
||||
{
|
||||
value = Quaternion.Identity;
|
||||
}
|
||||
|
||||
int IKeyframeAccess<Quaternion>.GetCurveComponents()
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
|
||||
float IKeyframeAccess<Quaternion>.GetCurveValue(ref Quaternion value, int component)
|
||||
{
|
||||
return value.EulerAngles[component];
|
||||
}
|
||||
|
||||
void IKeyframeAccess<Quaternion>.SetCurveValue(float curve, ref Quaternion value, int component)
|
||||
{
|
||||
var euler = value.EulerAngles;
|
||||
euler[component] = curve;
|
||||
Quaternion.Euler(euler.X, euler.Y, euler.Z, out value);
|
||||
}
|
||||
|
||||
void IKeyframeAccess<Color>.GetDefaultValue(out Color value)
|
||||
{
|
||||
value = Color.Black;
|
||||
}
|
||||
|
||||
int IKeyframeAccess<Color>.GetCurveComponents()
|
||||
{
|
||||
return 4;
|
||||
}
|
||||
|
||||
float IKeyframeAccess<Color>.GetCurveValue(ref Color value, int component)
|
||||
{
|
||||
return value[component];
|
||||
}
|
||||
|
||||
void IKeyframeAccess<Color>.SetCurveValue(float curve, ref Color value, int component)
|
||||
{
|
||||
value[component] = curve;
|
||||
}
|
||||
|
||||
void IKeyframeAccess<Color32>.GetDefaultValue(out Color32 value)
|
||||
{
|
||||
value = Color32.Black;
|
||||
}
|
||||
|
||||
int IKeyframeAccess<Color32>.GetCurveComponents()
|
||||
{
|
||||
return 4;
|
||||
}
|
||||
|
||||
float IKeyframeAccess<Color32>.GetCurveValue(ref Color32 value, int component)
|
||||
{
|
||||
return value[component];
|
||||
}
|
||||
|
||||
void IKeyframeAccess<Color32>.SetCurveValue(float curve, ref Color32 value, int component)
|
||||
{
|
||||
value[component] = (byte)Mathf.Clamp(curve, 0, 255);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
232
Source/Editor/GUI/CurveEditor.Base.cs
Normal file
232
Source/Editor/GUI/CurveEditor.Base.cs
Normal file
@@ -0,0 +1,232 @@
|
||||
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.GUI
|
||||
{
|
||||
/// <summary>
|
||||
/// The base class for <see cref="CurveBase{T}"/> editors. Allows to use generic curve editor without type information at compile-time.
|
||||
/// </summary>
|
||||
[HideInEditor]
|
||||
public abstract class CurveEditorBase : ContainerControl
|
||||
{
|
||||
/// <summary>
|
||||
/// The UI use mode flags.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum UseMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Disable usage.
|
||||
/// </summary>
|
||||
Off = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Allow only vertical usage.
|
||||
/// </summary>
|
||||
Vertical = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Allow only horizontal usage.
|
||||
/// </summary>
|
||||
Horizontal = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Allow both vertical and horizontal usage.
|
||||
/// </summary>
|
||||
On = Vertical | Horizontal,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when curve gets edited.
|
||||
/// </summary>
|
||||
public event Action Edited;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when curve data editing starts (via UI).
|
||||
/// </summary>
|
||||
public event Action EditingStart;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when curve data editing ends (via UI).
|
||||
/// </summary>
|
||||
public event Action EditingEnd;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum amount of keyframes to use in a single curve.
|
||||
/// </summary>
|
||||
public int MaxKeyframes = ushort.MaxValue;
|
||||
|
||||
/// <summary>
|
||||
/// True if enable view zooming. Otherwise user won't be able to zoom in or out.
|
||||
/// </summary>
|
||||
public UseMode EnableZoom = UseMode.On;
|
||||
|
||||
/// <summary>
|
||||
/// True if enable view panning. Otherwise user won't be able to move the view area.
|
||||
/// </summary>
|
||||
public UseMode EnablePanning = UseMode.On;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the scroll bars usage.
|
||||
/// </summary>
|
||||
public abstract ScrollBars ScrollBars { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enables drawing start/end values continuous lines.
|
||||
/// </summary>
|
||||
public bool ShowStartEndLines;
|
||||
|
||||
/// <summary>
|
||||
/// Enables drawing background.
|
||||
/// </summary>
|
||||
public bool ShowBackground = true;
|
||||
|
||||
/// <summary>
|
||||
/// Enables drawing time and values axes (lines and labels).
|
||||
/// </summary>
|
||||
public bool ShowAxes = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the curves keyframes value.
|
||||
/// </summary>
|
||||
public abstract Type ValueType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The amount of frames per second of the curve animation (optional). Can be used to restrict the keyframes time values to the given time quantization rate.
|
||||
/// </summary>
|
||||
public abstract float? FPS { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether show curve collapsed as a list of keyframe points rather than a full curve.
|
||||
/// </summary>
|
||||
public abstract bool ShowCollapsed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the view offset (via scroll bars).
|
||||
/// </summary>
|
||||
public abstract Vector2 ViewOffset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the view scale.
|
||||
/// </summary>
|
||||
public abstract Vector2 ViewScale { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of keyframes added to the curve.
|
||||
/// </summary>
|
||||
public abstract int KeyframesCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Called when curve gets edited.
|
||||
/// </summary>
|
||||
public void OnEdited()
|
||||
{
|
||||
Edited?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when curve data editing starts (via UI).
|
||||
/// </summary>
|
||||
public void OnEditingStart()
|
||||
{
|
||||
EditingStart?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when curve data editing ends (via UI).
|
||||
/// </summary>
|
||||
public void OnEditingEnd()
|
||||
{
|
||||
EditingEnd?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the keyframes positioning.
|
||||
/// </summary>
|
||||
public abstract void UpdateKeyframes();
|
||||
|
||||
/// <summary>
|
||||
/// Updates the tangents positioning.
|
||||
/// </summary>
|
||||
public abstract void UpdateTangents();
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates the animation curve value at the specified time.
|
||||
/// </summary>
|
||||
/// <param name="result">The interpolated value from the curve at provided time.</param>
|
||||
/// <param name="time">The time to evaluate the curve at.</param>
|
||||
/// <param name="loop">If true the curve will loop when it goes past the end or beginning. Otherwise the curve value will be clamped.</param>
|
||||
public abstract void Evaluate(out object result, float time, bool loop = false);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the keyframes collection (as boxed objects).
|
||||
/// </summary>
|
||||
/// <returns>The array of boxed keyframe values of type <see cref="BezierCurve{T}.Keyframe"/> or <see cref="LinearCurve{T}.Keyframe"/>.</returns>
|
||||
public abstract object[] GetKeyframes();
|
||||
|
||||
/// <summary>
|
||||
/// Sets the keyframes collection (as boxed objects).
|
||||
/// </summary>
|
||||
/// <param name="keyframes">The array of boxed keyframe values of type <see cref="BezierCurve{T}.Keyframe"/> or <see cref="LinearCurve{T}.Keyframe"/>.</param>
|
||||
public abstract void SetKeyframes(object[] keyframes);
|
||||
|
||||
/// <summary>
|
||||
/// Adds the new keyframe (as boxed object).
|
||||
/// </summary>
|
||||
/// <param name="time">The keyframe time.</param>
|
||||
/// <param name="value">The keyframe value.</param>
|
||||
public abstract void AddKeyframe(float time, object value);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the keyframe data (as boxed objects).
|
||||
/// </summary>
|
||||
/// <param name="index">The keyframe index.</param>
|
||||
/// <param name="time">The keyframe time.</param>
|
||||
/// <param name="value">The keyframe value (boxed).</param>
|
||||
/// <param name="tangentIn">The keyframe 'In' tangent value (boxed).</param>
|
||||
/// <param name="tangentOut">The keyframe 'Out' tangent value (boxed).</param>
|
||||
public abstract void GetKeyframe(int index, out float time, out object value, out object tangentIn, out object tangentOut);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the existing keyframe value (as boxed object).
|
||||
/// </summary>
|
||||
/// <param name="index">The keyframe index.</param>
|
||||
/// <returns>The keyframe value.</returns>
|
||||
public abstract object GetKeyframe(int index);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the existing keyframe value (as boxed object).
|
||||
/// </summary>
|
||||
/// <param name="index">The keyframe index.</param>
|
||||
/// <param name="value">The keyframe value.</param>
|
||||
public abstract void SetKeyframeValue(int index, object value);
|
||||
|
||||
/// <summary>
|
||||
/// Converts the <see cref="UseMode"/> into the <see cref="Vector2"/> mask.
|
||||
/// </summary>
|
||||
/// <param name="mode">The mode.</param>
|
||||
/// <returns>The mask.</returns>
|
||||
protected static Vector2 GetUseModeMask(UseMode mode)
|
||||
{
|
||||
return new Vector2((mode & UseMode.Horizontal) == UseMode.Horizontal ? 1.0f : 0.0f, (mode & UseMode.Vertical) == UseMode.Vertical ? 1.0f : 0.0f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters the given value using the <see cref="UseMode"/>.
|
||||
/// </summary>
|
||||
/// <param name="mode">The mode.</param>
|
||||
/// <param name="value">The value to process.</param>
|
||||
/// <param name="defaultValue">The default value.</param>
|
||||
/// <returns>The combined value.</returns>
|
||||
protected static Vector2 ApplyUseModeMask(UseMode mode, Vector2 value, Vector2 defaultValue)
|
||||
{
|
||||
return new Vector2(
|
||||
(mode & UseMode.Horizontal) == UseMode.Horizontal ? value.X : defaultValue.X,
|
||||
(mode & UseMode.Vertical) == UseMode.Vertical ? value.Y : defaultValue.Y
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
450
Source/Editor/GUI/CurveEditor.Contents.cs
Normal file
450
Source/Editor/GUI/CurveEditor.Contents.cs
Normal file
@@ -0,0 +1,450 @@
|
||||
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.GUI
|
||||
{
|
||||
partial class CurveEditor<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// The curve contents container control.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEngine.GUI.ContainerControl" />
|
||||
protected class ContentsBase : ContainerControl
|
||||
{
|
||||
private readonly CurveEditor<T> _editor;
|
||||
internal bool _leftMouseDown;
|
||||
private bool _rightMouseDown;
|
||||
internal Vector2 _leftMouseDownPos = Vector2.Minimum;
|
||||
private Vector2 _rightMouseDownPos = Vector2.Minimum;
|
||||
private Vector2 _movingViewLastPos;
|
||||
internal Vector2 _mousePos = Vector2.Minimum;
|
||||
internal bool _isMovingSelection;
|
||||
internal bool _isMovingTangent;
|
||||
internal bool _movedKeyframes;
|
||||
private TangentPoint _movingTangent;
|
||||
private Vector2 _movingSelectionStart;
|
||||
private Vector2[] _movingSelectionOffsets;
|
||||
private Vector2 _cmShowPos;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ContentsBase"/> class.
|
||||
/// </summary>
|
||||
/// <param name="editor">The curve editor.</param>
|
||||
public ContentsBase(CurveEditor<T> editor)
|
||||
{
|
||||
_editor = editor;
|
||||
}
|
||||
|
||||
private void UpdateSelectionRectangle()
|
||||
{
|
||||
var selectionRect = Rectangle.FromPoints(_leftMouseDownPos, _mousePos);
|
||||
|
||||
// Find controls to select
|
||||
for (int i = 0; i < Children.Count; i++)
|
||||
{
|
||||
if (Children[i] is KeyframePoint p)
|
||||
{
|
||||
p.IsSelected = p.Bounds.Intersects(ref selectionRect);
|
||||
}
|
||||
}
|
||||
|
||||
_editor.UpdateTangents();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool IntersectsContent(ref Vector2 locationParent, out Vector2 location)
|
||||
{
|
||||
// Pass all events
|
||||
location = PointFromParent(ref locationParent);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnMouseEnter(Vector2 location)
|
||||
{
|
||||
_mousePos = location;
|
||||
|
||||
base.OnMouseEnter(location);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnMouseMove(Vector2 location)
|
||||
{
|
||||
_mousePos = location;
|
||||
|
||||
// Moving view
|
||||
if (_rightMouseDown)
|
||||
{
|
||||
// Calculate delta
|
||||
Vector2 delta = location - _movingViewLastPos;
|
||||
delta *= GetUseModeMask(_editor.EnablePanning);
|
||||
if (delta.LengthSquared > 0.01f)
|
||||
{
|
||||
// Move view
|
||||
_editor.ViewOffset += delta * _editor.ViewScale;
|
||||
_movingViewLastPos = location;
|
||||
Cursor = CursorType.SizeAll;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
// Moving selection
|
||||
else if (_isMovingSelection)
|
||||
{
|
||||
var viewRect = _editor._mainPanel.GetClientArea();
|
||||
var locationKeyframes = PointToKeyframes(location, ref viewRect);
|
||||
var accessor = _editor.Accessor;
|
||||
var components = accessor.GetCurveComponents();
|
||||
for (var i = 0; i < _editor._points.Count; i++)
|
||||
{
|
||||
var p = _editor._points[i];
|
||||
if (p.IsSelected)
|
||||
{
|
||||
var k = _editor.GetKeyframe(p.Index);
|
||||
float time = _editor.GetKeyframeTime(k);
|
||||
float value = _editor.GetKeyframeValue(k, p.Component);
|
||||
|
||||
float minTime = p.Index != 0 ? _editor.GetKeyframeTime(_editor.GetKeyframe(p.Index - 1)) + Mathf.Epsilon : float.MinValue;
|
||||
float maxTime = p.Index != _editor.KeyframesCount - 1 ? _editor.GetKeyframeTime(_editor.GetKeyframe(p.Index + 1)) - Mathf.Epsilon : float.MaxValue;
|
||||
|
||||
var offset = _movingSelectionOffsets[i];
|
||||
|
||||
if (!_editor.ShowCollapsed)
|
||||
{
|
||||
// Move on value axis
|
||||
value = locationKeyframes.Y + offset.Y;
|
||||
}
|
||||
|
||||
// Let the first selected point of this keyframe to edit time
|
||||
bool isFirstSelected = false;
|
||||
for (var j = 0; j < components; j++)
|
||||
{
|
||||
var idx = p.Index * components + j;
|
||||
if (idx == i)
|
||||
{
|
||||
isFirstSelected = true;
|
||||
break;
|
||||
}
|
||||
if (_editor._points[idx].IsSelected)
|
||||
break;
|
||||
}
|
||||
if (isFirstSelected)
|
||||
{
|
||||
time = locationKeyframes.X + offset.X;
|
||||
|
||||
if (_editor.FPS.HasValue)
|
||||
{
|
||||
float fps = _editor.FPS.Value;
|
||||
time = Mathf.Floor(time * fps) / fps;
|
||||
}
|
||||
time = Mathf.Clamp(time, minTime, maxTime);
|
||||
}
|
||||
|
||||
// TODO: snapping keyframes to grid when moving
|
||||
|
||||
_editor.SetKeyframeInternal(p.Index, time, value, p.Component);
|
||||
}
|
||||
_editor.UpdateKeyframes();
|
||||
_editor.UpdateTooltips();
|
||||
if (_editor.EnablePanning == UseMode.On)
|
||||
{
|
||||
//_editor._mainPanel.ScrollViewTo(PointToParent(_editor._mainPanel, location));
|
||||
}
|
||||
Cursor = CursorType.SizeAll;
|
||||
_movedKeyframes = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Moving tangent
|
||||
else if (_isMovingTangent)
|
||||
{
|
||||
var viewRect = _editor._mainPanel.GetClientArea();
|
||||
var direction = _movingTangent.IsIn ? -1.0f : 1.0f;
|
||||
var k = _editor.GetKeyframe(_movingTangent.Index);
|
||||
var kv = _editor.GetKeyframeValue(k);
|
||||
var value = _editor.Accessor.GetCurveValue(ref kv, _movingTangent.Component);
|
||||
_movingTangent.TangentValue = direction * (PointToKeyframes(location, ref viewRect).Y - value);
|
||||
_editor.UpdateTangents();
|
||||
Cursor = CursorType.SizeNS;
|
||||
_movedKeyframes = true;
|
||||
return;
|
||||
}
|
||||
// Selecting
|
||||
else if (_leftMouseDown)
|
||||
{
|
||||
UpdateSelectionRectangle();
|
||||
return;
|
||||
}
|
||||
|
||||
base.OnMouseMove(location);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLostFocus()
|
||||
{
|
||||
// Clear flags and state
|
||||
if (_leftMouseDown)
|
||||
{
|
||||
_leftMouseDown = false;
|
||||
}
|
||||
if (_rightMouseDown)
|
||||
{
|
||||
_rightMouseDown = false;
|
||||
Cursor = CursorType.Default;
|
||||
}
|
||||
_isMovingSelection = false;
|
||||
_isMovingTangent = false;
|
||||
|
||||
base.OnLostFocus();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseDown(Vector2 location, MouseButton button)
|
||||
{
|
||||
if (base.OnMouseDown(location, button))
|
||||
{
|
||||
// Clear flags
|
||||
_isMovingSelection = false;
|
||||
_isMovingTangent = false;
|
||||
_rightMouseDown = false;
|
||||
_leftMouseDown = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Cache data
|
||||
_isMovingSelection = false;
|
||||
_isMovingTangent = false;
|
||||
_mousePos = location;
|
||||
if (button == MouseButton.Left)
|
||||
{
|
||||
_leftMouseDown = true;
|
||||
_leftMouseDownPos = location;
|
||||
}
|
||||
if (button == MouseButton.Right)
|
||||
{
|
||||
_rightMouseDown = true;
|
||||
_rightMouseDownPos = location;
|
||||
_movingViewLastPos = location;
|
||||
}
|
||||
|
||||
// Check if any node is under the mouse
|
||||
var underMouse = GetChildAt(location);
|
||||
if (underMouse is KeyframePoint keyframe)
|
||||
{
|
||||
if (_leftMouseDown)
|
||||
{
|
||||
// Check if user is pressing control
|
||||
if (Root.GetKey(KeyboardKeys.Control))
|
||||
{
|
||||
// Add to selection
|
||||
keyframe.Select();
|
||||
_editor.UpdateTangents();
|
||||
}
|
||||
// Check if node isn't selected
|
||||
else if (!keyframe.IsSelected)
|
||||
{
|
||||
// Select node
|
||||
_editor.ClearSelection();
|
||||
keyframe.Select();
|
||||
_editor.UpdateTangents();
|
||||
}
|
||||
|
||||
// Start moving selected nodes
|
||||
StartMouseCapture();
|
||||
_isMovingSelection = true;
|
||||
_movedKeyframes = false;
|
||||
var viewRect = _editor._mainPanel.GetClientArea();
|
||||
_movingSelectionStart = PointToKeyframes(location, ref viewRect);
|
||||
if (_movingSelectionOffsets == null || _movingSelectionOffsets.Length != _editor._points.Count)
|
||||
_movingSelectionOffsets = new Vector2[_editor._points.Count];
|
||||
for (int i = 0; i < _movingSelectionOffsets.Length; i++)
|
||||
_movingSelectionOffsets[i] = _editor._points[i].TimeValue - _movingSelectionStart;
|
||||
_editor.OnEditingStart();
|
||||
Focus();
|
||||
Tooltip?.Hide();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (underMouse is TangentPoint tangent && tangent.Visible)
|
||||
{
|
||||
if (_leftMouseDown)
|
||||
{
|
||||
// Start moving tangent
|
||||
StartMouseCapture();
|
||||
_isMovingTangent = true;
|
||||
_movedKeyframes = false;
|
||||
_movingTangent = tangent;
|
||||
_editor.OnEditingStart();
|
||||
Focus();
|
||||
Tooltip?.Hide();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_leftMouseDown)
|
||||
{
|
||||
// Start selecting
|
||||
StartMouseCapture();
|
||||
_editor.ClearSelection();
|
||||
_editor.UpdateTangents();
|
||||
Focus();
|
||||
return true;
|
||||
}
|
||||
if (_rightMouseDown)
|
||||
{
|
||||
// Start navigating
|
||||
StartMouseCapture();
|
||||
Focus();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Focus();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseUp(Vector2 location, MouseButton button)
|
||||
{
|
||||
_mousePos = location;
|
||||
|
||||
if (_leftMouseDown && button == MouseButton.Left)
|
||||
{
|
||||
_leftMouseDown = false;
|
||||
EndMouseCapture();
|
||||
Cursor = CursorType.Default;
|
||||
|
||||
// Editing tangent
|
||||
if (_isMovingTangent)
|
||||
{
|
||||
if (_movedKeyframes)
|
||||
{
|
||||
_editor.OnEdited();
|
||||
_editor.OnEditingEnd();
|
||||
_editor.UpdateKeyframes();
|
||||
}
|
||||
}
|
||||
// Moving keyframes
|
||||
else if (_isMovingSelection)
|
||||
{
|
||||
if (_movedKeyframes)
|
||||
{
|
||||
_editor.OnEdited();
|
||||
_editor.OnEditingEnd();
|
||||
}
|
||||
}
|
||||
// Selecting
|
||||
else
|
||||
{
|
||||
UpdateSelectionRectangle();
|
||||
}
|
||||
|
||||
_isMovingSelection = false;
|
||||
_isMovingTangent = false;
|
||||
_movedKeyframes = false;
|
||||
}
|
||||
if (_rightMouseDown && button == MouseButton.Right)
|
||||
{
|
||||
_rightMouseDown = false;
|
||||
EndMouseCapture();
|
||||
Cursor = CursorType.Default;
|
||||
|
||||
// Check if no move has been made at all
|
||||
if (Vector2.Distance(ref location, ref _rightMouseDownPos) < 3.0f)
|
||||
{
|
||||
var selectionCount = _editor.SelectionCount;
|
||||
var underMouse = GetChildAt(location);
|
||||
if (selectionCount == 0 && underMouse is KeyframePoint point)
|
||||
{
|
||||
// Select node
|
||||
selectionCount = 1;
|
||||
point.Select();
|
||||
_editor.UpdateTangents();
|
||||
}
|
||||
|
||||
var viewRect = _editor._mainPanel.GetClientArea();
|
||||
_cmShowPos = PointToKeyframes(location, ref viewRect);
|
||||
|
||||
var cm = new ContextMenu.ContextMenu();
|
||||
cm.AddButton("Add keyframe", () => _editor.AddKeyframe(_cmShowPos)).Enabled = _editor.KeyframesCount < _editor.MaxKeyframes;
|
||||
if (selectionCount == 0)
|
||||
{
|
||||
}
|
||||
else if (selectionCount == 1)
|
||||
{
|
||||
cm.AddButton("Edit keyframe", () => _editor.EditKeyframes(this, location));
|
||||
cm.AddButton("Remove keyframe", _editor.RemoveKeyframes);
|
||||
}
|
||||
else
|
||||
{
|
||||
cm.AddButton("Edit keyframes", () => _editor.EditKeyframes(this, location));
|
||||
cm.AddButton("Remove keyframes", _editor.RemoveKeyframes);
|
||||
}
|
||||
cm.AddButton("Edit all keyframes", () => _editor.EditAllKeyframes(this, location));
|
||||
if (_editor.EnableZoom != UseMode.Off || _editor.EnablePanning != UseMode.Off)
|
||||
{
|
||||
cm.AddSeparator();
|
||||
cm.AddButton("Show whole curve", _editor.ShowWholeCurve);
|
||||
cm.AddButton("Reset view", _editor.ResetView);
|
||||
}
|
||||
_editor.OnShowContextMenu(cm, selectionCount);
|
||||
cm.Show(this, location);
|
||||
}
|
||||
}
|
||||
|
||||
if (base.OnMouseUp(location, button))
|
||||
{
|
||||
// Clear flags
|
||||
_rightMouseDown = false;
|
||||
_leftMouseDown = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseWheel(Vector2 location, float delta)
|
||||
{
|
||||
if (base.OnMouseWheel(location, delta))
|
||||
return true;
|
||||
|
||||
// Zoom in/out
|
||||
if (_editor.EnableZoom != UseMode.Off && IsMouseOver && !_leftMouseDown && RootWindow.GetKey(KeyboardKeys.Control))
|
||||
{
|
||||
// TODO: preserve the view center point for easier zooming
|
||||
_editor.ViewScale += GetUseModeMask(_editor.EnableZoom) * (delta * 0.1f);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void SetScaleInternal(ref Vector2 scale)
|
||||
{
|
||||
base.SetScaleInternal(ref scale);
|
||||
|
||||
_editor.UpdateKeyframes();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the input point from curve editor contents control space into the keyframes time/value coordinates.
|
||||
/// </summary>
|
||||
/// <param name="point">The point.</param>
|
||||
/// <param name="curveContentAreaBounds">The curve contents area bounds.</param>
|
||||
/// <returns>The result.</returns>
|
||||
private Vector2 PointToKeyframes(Vector2 point, ref Rectangle curveContentAreaBounds)
|
||||
{
|
||||
// Contents -> Keyframes
|
||||
return new Vector2(
|
||||
(point.X + Location.X) / UnitsPerSecond,
|
||||
(point.Y + Location.Y - curveContentAreaBounds.Height) / -UnitsPerSecond
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,491 +9,15 @@ using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
// ReSharper disable RedundantAssignment
|
||||
|
||||
namespace FlaxEditor.GUI
|
||||
{
|
||||
/// <summary>
|
||||
/// The base class for <see cref="CurveBase{T}"/> editors. Allows to use generic curve editor without type information at compile-time.
|
||||
/// </summary>
|
||||
[HideInEditor]
|
||||
public abstract class CurveEditorBase : ContainerControl
|
||||
{
|
||||
/// <summary>
|
||||
/// The UI use mode flags.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum UseMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Disable usage.
|
||||
/// </summary>
|
||||
Off = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Allow only vertical usage.
|
||||
/// </summary>
|
||||
Vertical = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Allow only horizontal usage.
|
||||
/// </summary>
|
||||
Horizontal = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Allow both vertical and horizontal usage.
|
||||
/// </summary>
|
||||
On = Vertical | Horizontal,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when curve gets edited.
|
||||
/// </summary>
|
||||
public event Action Edited;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when curve data editing starts (via UI).
|
||||
/// </summary>
|
||||
public event Action EditingStart;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when curve data editing ends (via UI).
|
||||
/// </summary>
|
||||
public event Action EditingEnd;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum amount of keyframes to use in a single curve.
|
||||
/// </summary>
|
||||
public int MaxKeyframes = ushort.MaxValue;
|
||||
|
||||
/// <summary>
|
||||
/// True if enable view zooming. Otherwise user won't be able to zoom in or out.
|
||||
/// </summary>
|
||||
public UseMode EnableZoom = UseMode.On;
|
||||
|
||||
/// <summary>
|
||||
/// True if enable view panning. Otherwise user won't be able to move the view area.
|
||||
/// </summary>
|
||||
public UseMode EnablePanning = UseMode.On;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the scroll bars usage.
|
||||
/// </summary>
|
||||
public abstract ScrollBars ScrollBars { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enables drawing start/end values continuous lines.
|
||||
/// </summary>
|
||||
public bool ShowStartEndLines;
|
||||
|
||||
/// <summary>
|
||||
/// Enables drawing background.
|
||||
/// </summary>
|
||||
public bool ShowBackground = true;
|
||||
|
||||
/// <summary>
|
||||
/// Enables drawing time and values axes (lines and labels).
|
||||
/// </summary>
|
||||
public bool ShowAxes = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the curves keyframes value.
|
||||
/// </summary>
|
||||
public abstract Type ValueType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The amount of frames per second of the curve animation (optional). Can be used to restrict the keyframes time values to the given time quantization rate.
|
||||
/// </summary>
|
||||
public abstract float? FPS { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether show curve collapsed as a list of keyframe points rather than a full curve.
|
||||
/// </summary>
|
||||
public abstract bool ShowCollapsed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the view offset (via scroll bars).
|
||||
/// </summary>
|
||||
public abstract Vector2 ViewOffset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the view scale.
|
||||
/// </summary>
|
||||
public abstract Vector2 ViewScale { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of keyframes added to the curve.
|
||||
/// </summary>
|
||||
public abstract int KeyframesCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Called when curve gets edited.
|
||||
/// </summary>
|
||||
public void OnEdited()
|
||||
{
|
||||
Edited?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when curve data editing starts (via UI).
|
||||
/// </summary>
|
||||
public void OnEditingStart()
|
||||
{
|
||||
EditingStart?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when curve data editing ends (via UI).
|
||||
/// </summary>
|
||||
public void OnEditingEnd()
|
||||
{
|
||||
EditingEnd?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the keyframes positioning.
|
||||
/// </summary>
|
||||
public abstract void UpdateKeyframes();
|
||||
|
||||
/// <summary>
|
||||
/// Updates the tangents positioning.
|
||||
/// </summary>
|
||||
public abstract void UpdateTangents();
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates the animation curve value at the specified time.
|
||||
/// </summary>
|
||||
/// <param name="result">The interpolated value from the curve at provided time.</param>
|
||||
/// <param name="time">The time to evaluate the curve at.</param>
|
||||
/// <param name="loop">If true the curve will loop when it goes past the end or beginning. Otherwise the curve value will be clamped.</param>
|
||||
public abstract void Evaluate(out object result, float time, bool loop = false);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the keyframes collection (as boxed objects).
|
||||
/// </summary>
|
||||
/// <returns>The array of boxed keyframe values of type <see cref="BezierCurve{T}.Keyframe"/> or <see cref="LinearCurve{T}.Keyframe"/>.</returns>
|
||||
public abstract object[] GetKeyframes();
|
||||
|
||||
/// <summary>
|
||||
/// Sets the keyframes collection (as boxed objects).
|
||||
/// </summary>
|
||||
/// <param name="keyframes">The array of boxed keyframe values of type <see cref="BezierCurve{T}.Keyframe"/> or <see cref="LinearCurve{T}.Keyframe"/>.</param>
|
||||
public abstract void SetKeyframes(object[] keyframes);
|
||||
|
||||
/// <summary>
|
||||
/// Adds the new keyframe (as boxed object).
|
||||
/// </summary>
|
||||
/// <param name="time">The keyframe time.</param>
|
||||
/// <param name="value">The keyframe value.</param>
|
||||
public abstract void AddKeyframe(float time, object value);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the keyframe data (as boxed objects).
|
||||
/// </summary>
|
||||
/// <param name="index">The keyframe index.</param>
|
||||
/// <param name="time">The keyframe time.</param>
|
||||
/// <param name="value">The keyframe value (boxed).</param>
|
||||
/// <param name="tangentIn">The keyframe 'In' tangent value (boxed).</param>
|
||||
/// <param name="tangentOut">The keyframe 'Out' tangent value (boxed).</param>
|
||||
public abstract void GetKeyframe(int index, out float time, out object value, out object tangentIn, out object tangentOut);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the existing keyframe value (as boxed object).
|
||||
/// </summary>
|
||||
/// <param name="index">The keyframe index.</param>
|
||||
/// <returns>The keyframe value.</returns>
|
||||
public abstract object GetKeyframe(int index);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the existing keyframe value (as boxed object).
|
||||
/// </summary>
|
||||
/// <param name="index">The keyframe index.</param>
|
||||
/// <param name="value">The keyframe value.</param>
|
||||
public abstract void SetKeyframeValue(int index, object value);
|
||||
|
||||
/// <summary>
|
||||
/// Converts the <see cref="UseMode"/> into the <see cref="Vector2"/> mask.
|
||||
/// </summary>
|
||||
/// <param name="mode">The mode.</param>
|
||||
/// <returns>The mask.</returns>
|
||||
protected static Vector2 GetUseModeMask(UseMode mode)
|
||||
{
|
||||
return new Vector2((mode & UseMode.Horizontal) == UseMode.Horizontal ? 1.0f : 0.0f, (mode & UseMode.Vertical) == UseMode.Vertical ? 1.0f : 0.0f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters the given value using the <see cref="UseMode"/>.
|
||||
/// </summary>
|
||||
/// <param name="mode">The mode.</param>
|
||||
/// <param name="value">The value to process.</param>
|
||||
/// <param name="defaultValue">The default value.</param>
|
||||
/// <returns>The combined value.</returns>
|
||||
protected static Vector2 ApplyUseModeMask(UseMode mode, Vector2 value, Vector2 defaultValue)
|
||||
{
|
||||
return new Vector2(
|
||||
(mode & UseMode.Horizontal) == UseMode.Horizontal ? value.X : defaultValue.X,
|
||||
(mode & UseMode.Vertical) == UseMode.Vertical ? value.Y : defaultValue.Y
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The generic curve editor control.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The keyframe value type.</typeparam>
|
||||
/// <seealso cref="CurveEditorBase" />
|
||||
public abstract class CurveEditor<T> : CurveEditorBase where T : new()
|
||||
public abstract partial class CurveEditor<T> : CurveEditorBase where T : new()
|
||||
{
|
||||
/// <summary>
|
||||
/// The generic keyframe value accessor object for curve editor.
|
||||
/// </summary>
|
||||
/// <typeparam name="U">The keyframe value type.</typeparam>
|
||||
public interface IKeyframeAccess<U> where U : new()
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the default value.
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
void GetDefaultValue(out U value);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the curve components count. Vector types should return amount of component to use for value editing.
|
||||
/// </summary>
|
||||
/// <returns>The components count.</returns>
|
||||
int GetCurveComponents();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of the component for the curve.
|
||||
/// </summary>
|
||||
/// <param name="value">The keyframe value.</param>
|
||||
/// <param name="component">The component index.</param>
|
||||
/// <returns>The curve value.</returns>
|
||||
float GetCurveValue(ref U value, int component);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the curve value of the component.
|
||||
/// </summary>
|
||||
/// <param name="curve">The curve value to assign.</param>
|
||||
/// <param name="value">The keyframe value.</param>
|
||||
/// <param name="component">The component index.</param>
|
||||
void SetCurveValue(float curve, ref U value, int component);
|
||||
}
|
||||
|
||||
private class KeyframeAccess :
|
||||
IKeyframeAccess<bool>,
|
||||
IKeyframeAccess<int>,
|
||||
IKeyframeAccess<double>,
|
||||
IKeyframeAccess<float>,
|
||||
IKeyframeAccess<Vector2>,
|
||||
IKeyframeAccess<Vector3>,
|
||||
IKeyframeAccess<Vector4>,
|
||||
IKeyframeAccess<Quaternion>,
|
||||
IKeyframeAccess<Color32>,
|
||||
IKeyframeAccess<Color>
|
||||
{
|
||||
void IKeyframeAccess<bool>.GetDefaultValue(out bool value)
|
||||
{
|
||||
value = false;
|
||||
}
|
||||
|
||||
int IKeyframeAccess<bool>.GetCurveComponents()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
float IKeyframeAccess<bool>.GetCurveValue(ref bool value, int component)
|
||||
{
|
||||
return value ? 1 : 0;
|
||||
}
|
||||
|
||||
void IKeyframeAccess<bool>.SetCurveValue(float curve, ref bool value, int component)
|
||||
{
|
||||
value = curve >= 0.5f;
|
||||
}
|
||||
|
||||
void IKeyframeAccess<int>.GetDefaultValue(out int value)
|
||||
{
|
||||
value = 0;
|
||||
}
|
||||
|
||||
int IKeyframeAccess<int>.GetCurveComponents()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
float IKeyframeAccess<int>.GetCurveValue(ref int value, int component)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
void IKeyframeAccess<int>.SetCurveValue(float curve, ref int value, int component)
|
||||
{
|
||||
value = (int)curve;
|
||||
}
|
||||
|
||||
void IKeyframeAccess<double>.GetDefaultValue(out double value)
|
||||
{
|
||||
value = 0.0;
|
||||
}
|
||||
|
||||
int IKeyframeAccess<double>.GetCurveComponents()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
float IKeyframeAccess<double>.GetCurveValue(ref double value, int component)
|
||||
{
|
||||
return (float)value;
|
||||
}
|
||||
|
||||
void IKeyframeAccess<double>.SetCurveValue(float curve, ref double value, int component)
|
||||
{
|
||||
value = curve;
|
||||
}
|
||||
|
||||
void IKeyframeAccess<float>.GetDefaultValue(out float value)
|
||||
{
|
||||
value = 0.0f;
|
||||
}
|
||||
|
||||
int IKeyframeAccess<float>.GetCurveComponents()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
float IKeyframeAccess<float>.GetCurveValue(ref float value, int component)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
void IKeyframeAccess<float>.SetCurveValue(float curve, ref float value, int component)
|
||||
{
|
||||
value = curve;
|
||||
}
|
||||
|
||||
void IKeyframeAccess<Vector2>.GetDefaultValue(out Vector2 value)
|
||||
{
|
||||
value = Vector2.Zero;
|
||||
}
|
||||
|
||||
int IKeyframeAccess<Vector2>.GetCurveComponents()
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
float IKeyframeAccess<Vector2>.GetCurveValue(ref Vector2 value, int component)
|
||||
{
|
||||
return value[component];
|
||||
}
|
||||
|
||||
void IKeyframeAccess<Vector2>.SetCurveValue(float curve, ref Vector2 value, int component)
|
||||
{
|
||||
value[component] = curve;
|
||||
}
|
||||
|
||||
void IKeyframeAccess<Vector3>.GetDefaultValue(out Vector3 value)
|
||||
{
|
||||
value = Vector3.Zero;
|
||||
}
|
||||
|
||||
int IKeyframeAccess<Vector3>.GetCurveComponents()
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
|
||||
float IKeyframeAccess<Vector3>.GetCurveValue(ref Vector3 value, int component)
|
||||
{
|
||||
return value[component];
|
||||
}
|
||||
|
||||
void IKeyframeAccess<Vector3>.SetCurveValue(float curve, ref Vector3 value, int component)
|
||||
{
|
||||
value[component] = curve;
|
||||
}
|
||||
|
||||
void IKeyframeAccess<Vector4>.GetDefaultValue(out Vector4 value)
|
||||
{
|
||||
value = Vector4.Zero;
|
||||
}
|
||||
|
||||
int IKeyframeAccess<Vector4>.GetCurveComponents()
|
||||
{
|
||||
return 4;
|
||||
}
|
||||
|
||||
float IKeyframeAccess<Vector4>.GetCurveValue(ref Vector4 value, int component)
|
||||
{
|
||||
return value[component];
|
||||
}
|
||||
|
||||
void IKeyframeAccess<Vector4>.SetCurveValue(float curve, ref Vector4 value, int component)
|
||||
{
|
||||
value[component] = curve;
|
||||
}
|
||||
|
||||
public void GetDefaultValue(out Quaternion value)
|
||||
{
|
||||
value = Quaternion.Identity;
|
||||
}
|
||||
|
||||
int IKeyframeAccess<Quaternion>.GetCurveComponents()
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
|
||||
float IKeyframeAccess<Quaternion>.GetCurveValue(ref Quaternion value, int component)
|
||||
{
|
||||
return value.EulerAngles[component];
|
||||
}
|
||||
|
||||
void IKeyframeAccess<Quaternion>.SetCurveValue(float curve, ref Quaternion value, int component)
|
||||
{
|
||||
var euler = value.EulerAngles;
|
||||
euler[component] = curve;
|
||||
Quaternion.Euler(euler.X, euler.Y, euler.Z, out value);
|
||||
}
|
||||
|
||||
void IKeyframeAccess<Color>.GetDefaultValue(out Color value)
|
||||
{
|
||||
value = Color.Black;
|
||||
}
|
||||
|
||||
int IKeyframeAccess<Color>.GetCurveComponents()
|
||||
{
|
||||
return 4;
|
||||
}
|
||||
|
||||
float IKeyframeAccess<Color>.GetCurveValue(ref Color value, int component)
|
||||
{
|
||||
return value[component];
|
||||
}
|
||||
|
||||
void IKeyframeAccess<Color>.SetCurveValue(float curve, ref Color value, int component)
|
||||
{
|
||||
value[component] = curve;
|
||||
}
|
||||
|
||||
void IKeyframeAccess<Color32>.GetDefaultValue(out Color32 value)
|
||||
{
|
||||
value = Color32.Black;
|
||||
}
|
||||
|
||||
int IKeyframeAccess<Color32>.GetCurveComponents()
|
||||
{
|
||||
return 4;
|
||||
}
|
||||
|
||||
float IKeyframeAccess<Color32>.GetCurveValue(ref Color32 value, int component)
|
||||
{
|
||||
return value[component];
|
||||
}
|
||||
|
||||
void IKeyframeAccess<Color32>.SetCurveValue(float curve, ref Color32 value, int component)
|
||||
{
|
||||
value[component] = (byte)Mathf.Clamp(curve, 0, 255);
|
||||
}
|
||||
}
|
||||
|
||||
private class Popup : ContextMenuBase
|
||||
{
|
||||
private CustomEditorPresenter _presenter;
|
||||
@@ -600,446 +124,6 @@ namespace FlaxEditor.GUI
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The curve contents container control.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEngine.GUI.ContainerControl" />
|
||||
protected class ContentsBase : ContainerControl
|
||||
{
|
||||
private readonly CurveEditor<T> _editor;
|
||||
internal bool _leftMouseDown;
|
||||
private bool _rightMouseDown;
|
||||
internal Vector2 _leftMouseDownPos = Vector2.Minimum;
|
||||
private Vector2 _rightMouseDownPos = Vector2.Minimum;
|
||||
private Vector2 _movingViewLastPos;
|
||||
internal Vector2 _mousePos = Vector2.Minimum;
|
||||
internal bool _isMovingSelection;
|
||||
internal bool _isMovingTangent;
|
||||
internal bool _movedKeyframes;
|
||||
private TangentPoint _movingTangent;
|
||||
private Vector2 _movingSelectionStart;
|
||||
private Vector2[] _movingSelectionOffsets;
|
||||
private Vector2 _cmShowPos;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ContentsBase"/> class.
|
||||
/// </summary>
|
||||
/// <param name="editor">The curve editor.</param>
|
||||
public ContentsBase(CurveEditor<T> editor)
|
||||
{
|
||||
_editor = editor;
|
||||
}
|
||||
|
||||
private void UpdateSelectionRectangle()
|
||||
{
|
||||
var selectionRect = Rectangle.FromPoints(_leftMouseDownPos, _mousePos);
|
||||
|
||||
// Find controls to select
|
||||
for (int i = 0; i < Children.Count; i++)
|
||||
{
|
||||
if (Children[i] is KeyframePoint p)
|
||||
{
|
||||
p.IsSelected = p.Bounds.Intersects(ref selectionRect);
|
||||
}
|
||||
}
|
||||
|
||||
_editor.UpdateTangents();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool IntersectsContent(ref Vector2 locationParent, out Vector2 location)
|
||||
{
|
||||
// Pass all events
|
||||
location = PointFromParent(ref locationParent);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnMouseEnter(Vector2 location)
|
||||
{
|
||||
_mousePos = location;
|
||||
|
||||
base.OnMouseEnter(location);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnMouseMove(Vector2 location)
|
||||
{
|
||||
_mousePos = location;
|
||||
|
||||
// Moving view
|
||||
if (_rightMouseDown)
|
||||
{
|
||||
// Calculate delta
|
||||
Vector2 delta = location - _movingViewLastPos;
|
||||
delta *= GetUseModeMask(_editor.EnablePanning);
|
||||
if (delta.LengthSquared > 0.01f)
|
||||
{
|
||||
// Move view
|
||||
_editor.ViewOffset += delta * _editor.ViewScale;
|
||||
_movingViewLastPos = location;
|
||||
Cursor = CursorType.SizeAll;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
// Moving selection
|
||||
else if (_isMovingSelection)
|
||||
{
|
||||
var viewRect = _editor._mainPanel.GetClientArea();
|
||||
var locationKeyframes = PointToKeyframes(location, ref viewRect);
|
||||
var accessor = _editor.Accessor;
|
||||
var components = accessor.GetCurveComponents();
|
||||
for (var i = 0; i < _editor._points.Count; i++)
|
||||
{
|
||||
var p = _editor._points[i];
|
||||
if (p.IsSelected)
|
||||
{
|
||||
var k = _editor.GetKeyframe(p.Index);
|
||||
float time = _editor.GetKeyframeTime(k);
|
||||
float value = _editor.GetKeyframeValue(k, p.Component);
|
||||
|
||||
float minTime = p.Index != 0 ? _editor.GetKeyframeTime(_editor.GetKeyframe(p.Index - 1)) + Mathf.Epsilon : float.MinValue;
|
||||
float maxTime = p.Index != _editor.KeyframesCount - 1 ? _editor.GetKeyframeTime(_editor.GetKeyframe(p.Index + 1)) - Mathf.Epsilon : float.MaxValue;
|
||||
|
||||
var offset = _movingSelectionOffsets[i];
|
||||
|
||||
if (!_editor.ShowCollapsed)
|
||||
{
|
||||
// Move on value axis
|
||||
value = locationKeyframes.Y + offset.Y;
|
||||
}
|
||||
|
||||
// Let the first selected point of this keyframe to edit time
|
||||
bool isFirstSelected = false;
|
||||
for (var j = 0; j < components; j++)
|
||||
{
|
||||
var idx = p.Index * components + j;
|
||||
if (idx == i)
|
||||
{
|
||||
isFirstSelected = true;
|
||||
break;
|
||||
}
|
||||
if (_editor._points[idx].IsSelected)
|
||||
break;
|
||||
}
|
||||
if (isFirstSelected)
|
||||
{
|
||||
time = locationKeyframes.X + offset.X;
|
||||
|
||||
if (_editor.FPS.HasValue)
|
||||
{
|
||||
float fps = _editor.FPS.Value;
|
||||
time = Mathf.Floor(time * fps) / fps;
|
||||
}
|
||||
time = Mathf.Clamp(time, minTime, maxTime);
|
||||
}
|
||||
|
||||
// TODO: snapping keyframes to grid when moving
|
||||
|
||||
_editor.SetKeyframeInternal(p.Index, time, value, p.Component);
|
||||
}
|
||||
_editor.UpdateKeyframes();
|
||||
_editor.UpdateTooltips();
|
||||
if (_editor.EnablePanning == UseMode.On)
|
||||
{
|
||||
//_editor._mainPanel.ScrollViewTo(PointToParent(_editor._mainPanel, location));
|
||||
}
|
||||
Cursor = CursorType.SizeAll;
|
||||
_movedKeyframes = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Moving tangent
|
||||
else if (_isMovingTangent)
|
||||
{
|
||||
var viewRect = _editor._mainPanel.GetClientArea();
|
||||
var direction = _movingTangent.IsIn ? -1.0f : 1.0f;
|
||||
var k = _editor.GetKeyframe(_movingTangent.Index);
|
||||
var kv = _editor.GetKeyframeValue(k);
|
||||
var value = _editor.Accessor.GetCurveValue(ref kv, _movingTangent.Component);
|
||||
_movingTangent.TangentValue = direction * (PointToKeyframes(location, ref viewRect).Y - value);
|
||||
_editor.UpdateTangents();
|
||||
Cursor = CursorType.SizeNS;
|
||||
_movedKeyframes = true;
|
||||
return;
|
||||
}
|
||||
// Selecting
|
||||
else if (_leftMouseDown)
|
||||
{
|
||||
UpdateSelectionRectangle();
|
||||
return;
|
||||
}
|
||||
|
||||
base.OnMouseMove(location);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLostFocus()
|
||||
{
|
||||
// Clear flags and state
|
||||
if (_leftMouseDown)
|
||||
{
|
||||
_leftMouseDown = false;
|
||||
}
|
||||
if (_rightMouseDown)
|
||||
{
|
||||
_rightMouseDown = false;
|
||||
Cursor = CursorType.Default;
|
||||
}
|
||||
_isMovingSelection = false;
|
||||
_isMovingTangent = false;
|
||||
|
||||
base.OnLostFocus();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseDown(Vector2 location, MouseButton button)
|
||||
{
|
||||
if (base.OnMouseDown(location, button))
|
||||
{
|
||||
// Clear flags
|
||||
_isMovingSelection = false;
|
||||
_isMovingTangent = false;
|
||||
_rightMouseDown = false;
|
||||
_leftMouseDown = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Cache data
|
||||
_isMovingSelection = false;
|
||||
_isMovingTangent = false;
|
||||
_mousePos = location;
|
||||
if (button == MouseButton.Left)
|
||||
{
|
||||
_leftMouseDown = true;
|
||||
_leftMouseDownPos = location;
|
||||
}
|
||||
if (button == MouseButton.Right)
|
||||
{
|
||||
_rightMouseDown = true;
|
||||
_rightMouseDownPos = location;
|
||||
_movingViewLastPos = location;
|
||||
}
|
||||
|
||||
// Check if any node is under the mouse
|
||||
var underMouse = GetChildAt(location);
|
||||
if (underMouse is KeyframePoint keyframe)
|
||||
{
|
||||
if (_leftMouseDown)
|
||||
{
|
||||
// Check if user is pressing control
|
||||
if (Root.GetKey(KeyboardKeys.Control))
|
||||
{
|
||||
// Add to selection
|
||||
keyframe.Select();
|
||||
_editor.UpdateTangents();
|
||||
}
|
||||
// Check if node isn't selected
|
||||
else if (!keyframe.IsSelected)
|
||||
{
|
||||
// Select node
|
||||
_editor.ClearSelection();
|
||||
keyframe.Select();
|
||||
_editor.UpdateTangents();
|
||||
}
|
||||
|
||||
// Start moving selected nodes
|
||||
StartMouseCapture();
|
||||
_isMovingSelection = true;
|
||||
_movedKeyframes = false;
|
||||
var viewRect = _editor._mainPanel.GetClientArea();
|
||||
_movingSelectionStart = PointToKeyframes(location, ref viewRect);
|
||||
if (_movingSelectionOffsets == null || _movingSelectionOffsets.Length != _editor._points.Count)
|
||||
_movingSelectionOffsets = new Vector2[_editor._points.Count];
|
||||
for (int i = 0; i < _movingSelectionOffsets.Length; i++)
|
||||
_movingSelectionOffsets[i] = _editor._points[i].TimeValue - _movingSelectionStart;
|
||||
_editor.OnEditingStart();
|
||||
Focus();
|
||||
Tooltip?.Hide();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (underMouse is TangentPoint tangent && tangent.Visible)
|
||||
{
|
||||
if (_leftMouseDown)
|
||||
{
|
||||
// Start moving tangent
|
||||
StartMouseCapture();
|
||||
_isMovingTangent = true;
|
||||
_movedKeyframes = false;
|
||||
_movingTangent = tangent;
|
||||
_editor.OnEditingStart();
|
||||
Focus();
|
||||
Tooltip?.Hide();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_leftMouseDown)
|
||||
{
|
||||
// Start selecting
|
||||
StartMouseCapture();
|
||||
_editor.ClearSelection();
|
||||
_editor.UpdateTangents();
|
||||
Focus();
|
||||
return true;
|
||||
}
|
||||
if (_rightMouseDown)
|
||||
{
|
||||
// Start navigating
|
||||
StartMouseCapture();
|
||||
Focus();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Focus();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseUp(Vector2 location, MouseButton button)
|
||||
{
|
||||
_mousePos = location;
|
||||
|
||||
if (_leftMouseDown && button == MouseButton.Left)
|
||||
{
|
||||
_leftMouseDown = false;
|
||||
EndMouseCapture();
|
||||
Cursor = CursorType.Default;
|
||||
|
||||
// Editing tangent
|
||||
if (_isMovingTangent)
|
||||
{
|
||||
if (_movedKeyframes)
|
||||
{
|
||||
_editor.OnEdited();
|
||||
_editor.OnEditingEnd();
|
||||
_editor.UpdateKeyframes();
|
||||
}
|
||||
}
|
||||
// Moving keyframes
|
||||
else if (_isMovingSelection)
|
||||
{
|
||||
if (_movedKeyframes)
|
||||
{
|
||||
_editor.OnEdited();
|
||||
_editor.OnEditingEnd();
|
||||
}
|
||||
}
|
||||
// Selecting
|
||||
else
|
||||
{
|
||||
UpdateSelectionRectangle();
|
||||
}
|
||||
|
||||
_isMovingSelection = false;
|
||||
_isMovingTangent = false;
|
||||
_movedKeyframes = false;
|
||||
}
|
||||
if (_rightMouseDown && button == MouseButton.Right)
|
||||
{
|
||||
_rightMouseDown = false;
|
||||
EndMouseCapture();
|
||||
Cursor = CursorType.Default;
|
||||
|
||||
// Check if no move has been made at all
|
||||
if (Vector2.Distance(ref location, ref _rightMouseDownPos) < 3.0f)
|
||||
{
|
||||
var selectionCount = _editor.SelectionCount;
|
||||
var underMouse = GetChildAt(location);
|
||||
if (selectionCount == 0 && underMouse is KeyframePoint point)
|
||||
{
|
||||
// Select node
|
||||
selectionCount = 1;
|
||||
point.Select();
|
||||
_editor.UpdateTangents();
|
||||
}
|
||||
|
||||
var viewRect = _editor._mainPanel.GetClientArea();
|
||||
_cmShowPos = PointToKeyframes(location, ref viewRect);
|
||||
|
||||
var cm = new ContextMenu.ContextMenu();
|
||||
cm.AddButton("Add keyframe", () => _editor.AddKeyframe(_cmShowPos)).Enabled = _editor.KeyframesCount < _editor.MaxKeyframes;
|
||||
if (selectionCount == 0)
|
||||
{
|
||||
}
|
||||
else if (selectionCount == 1)
|
||||
{
|
||||
cm.AddButton("Edit keyframe", () => _editor.EditKeyframes(this, location));
|
||||
cm.AddButton("Remove keyframe", _editor.RemoveKeyframes);
|
||||
}
|
||||
else
|
||||
{
|
||||
cm.AddButton("Edit keyframes", () => _editor.EditKeyframes(this, location));
|
||||
cm.AddButton("Remove keyframes", _editor.RemoveKeyframes);
|
||||
}
|
||||
cm.AddButton("Edit all keyframes", () => _editor.EditAllKeyframes(this, location));
|
||||
if (_editor.EnableZoom != UseMode.Off || _editor.EnablePanning != UseMode.Off)
|
||||
{
|
||||
cm.AddSeparator();
|
||||
cm.AddButton("Show whole curve", _editor.ShowWholeCurve);
|
||||
cm.AddButton("Reset view", _editor.ResetView);
|
||||
}
|
||||
_editor.OnShowContextMenu(cm, selectionCount);
|
||||
cm.Show(this, location);
|
||||
}
|
||||
}
|
||||
|
||||
if (base.OnMouseUp(location, button))
|
||||
{
|
||||
// Clear flags
|
||||
_rightMouseDown = false;
|
||||
_leftMouseDown = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseWheel(Vector2 location, float delta)
|
||||
{
|
||||
if (base.OnMouseWheel(location, delta))
|
||||
return true;
|
||||
|
||||
// Zoom in/out
|
||||
if (_editor.EnableZoom != UseMode.Off && IsMouseOver && !_leftMouseDown && RootWindow.GetKey(KeyboardKeys.Control))
|
||||
{
|
||||
// TODO: preserve the view center point for easier zooming
|
||||
_editor.ViewScale += GetUseModeMask(_editor.EnableZoom) * (delta * 0.1f);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void SetScaleInternal(ref Vector2 scale)
|
||||
{
|
||||
base.SetScaleInternal(ref scale);
|
||||
|
||||
_editor.UpdateKeyframes();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the input point from curve editor contents control space into the keyframes time/value coordinates.
|
||||
/// </summary>
|
||||
/// <param name="point">The point.</param>
|
||||
/// <param name="curveContentAreaBounds">The curve contents area bounds.</param>
|
||||
/// <returns>The result.</returns>
|
||||
private Vector2 PointToKeyframes(Vector2 point, ref Rectangle curveContentAreaBounds)
|
||||
{
|
||||
// Contents -> Keyframes
|
||||
return new Vector2(
|
||||
(point.X + Location.X) / UnitsPerSecond,
|
||||
(point.Y + Location.Y - curveContentAreaBounds.Height) / -UnitsPerSecond
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The single keyframe control.
|
||||
/// </summary>
|
||||
|
||||
Reference in New Issue
Block a user