diff --git a/Source/Editor/GUI/CurveEditor.Access.cs b/Source/Editor/GUI/CurveEditor.Access.cs new file mode 100644 index 000000000..7cf113474 --- /dev/null +++ b/Source/Editor/GUI/CurveEditor.Access.cs @@ -0,0 +1,261 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using FlaxEngine; + +// ReSharper disable RedundantAssignment + +namespace FlaxEditor.GUI +{ + partial class CurveEditor + { + /// + /// The generic keyframe value accessor object for curve editor. + /// + /// The keyframe value type. + public interface IKeyframeAccess where U : new() + { + /// + /// Gets the default value. + /// + /// The value. + void GetDefaultValue(out U value); + + /// + /// Gets the curve components count. Vector types should return amount of component to use for value editing. + /// + /// The components count. + int GetCurveComponents(); + + /// + /// Gets the value of the component for the curve. + /// + /// The keyframe value. + /// The component index. + /// The curve value. + float GetCurveValue(ref U value, int component); + + /// + /// Sets the curve value of the component. + /// + /// The curve value to assign. + /// The keyframe value. + /// The component index. + void SetCurveValue(float curve, ref U value, int component); + } + + private class KeyframeAccess : + IKeyframeAccess, + IKeyframeAccess, + IKeyframeAccess, + IKeyframeAccess, + IKeyframeAccess, + IKeyframeAccess, + IKeyframeAccess, + IKeyframeAccess, + IKeyframeAccess, + IKeyframeAccess + { + void IKeyframeAccess.GetDefaultValue(out bool value) + { + value = false; + } + + int IKeyframeAccess.GetCurveComponents() + { + return 1; + } + + float IKeyframeAccess.GetCurveValue(ref bool value, int component) + { + return value ? 1 : 0; + } + + void IKeyframeAccess.SetCurveValue(float curve, ref bool value, int component) + { + value = curve >= 0.5f; + } + + void IKeyframeAccess.GetDefaultValue(out int value) + { + value = 0; + } + + int IKeyframeAccess.GetCurveComponents() + { + return 1; + } + + float IKeyframeAccess.GetCurveValue(ref int value, int component) + { + return value; + } + + void IKeyframeAccess.SetCurveValue(float curve, ref int value, int component) + { + value = (int)curve; + } + + void IKeyframeAccess.GetDefaultValue(out double value) + { + value = 0.0; + } + + int IKeyframeAccess.GetCurveComponents() + { + return 1; + } + + float IKeyframeAccess.GetCurveValue(ref double value, int component) + { + return (float)value; + } + + void IKeyframeAccess.SetCurveValue(float curve, ref double value, int component) + { + value = curve; + } + + void IKeyframeAccess.GetDefaultValue(out float value) + { + value = 0.0f; + } + + int IKeyframeAccess.GetCurveComponents() + { + return 1; + } + + float IKeyframeAccess.GetCurveValue(ref float value, int component) + { + return value; + } + + void IKeyframeAccess.SetCurveValue(float curve, ref float value, int component) + { + value = curve; + } + + void IKeyframeAccess.GetDefaultValue(out Vector2 value) + { + value = Vector2.Zero; + } + + int IKeyframeAccess.GetCurveComponents() + { + return 2; + } + + float IKeyframeAccess.GetCurveValue(ref Vector2 value, int component) + { + return value[component]; + } + + void IKeyframeAccess.SetCurveValue(float curve, ref Vector2 value, int component) + { + value[component] = curve; + } + + void IKeyframeAccess.GetDefaultValue(out Vector3 value) + { + value = Vector3.Zero; + } + + int IKeyframeAccess.GetCurveComponents() + { + return 3; + } + + float IKeyframeAccess.GetCurveValue(ref Vector3 value, int component) + { + return value[component]; + } + + void IKeyframeAccess.SetCurveValue(float curve, ref Vector3 value, int component) + { + value[component] = curve; + } + + void IKeyframeAccess.GetDefaultValue(out Vector4 value) + { + value = Vector4.Zero; + } + + int IKeyframeAccess.GetCurveComponents() + { + return 4; + } + + float IKeyframeAccess.GetCurveValue(ref Vector4 value, int component) + { + return value[component]; + } + + void IKeyframeAccess.SetCurveValue(float curve, ref Vector4 value, int component) + { + value[component] = curve; + } + + public void GetDefaultValue(out Quaternion value) + { + value = Quaternion.Identity; + } + + int IKeyframeAccess.GetCurveComponents() + { + return 3; + } + + float IKeyframeAccess.GetCurveValue(ref Quaternion value, int component) + { + return value.EulerAngles[component]; + } + + void IKeyframeAccess.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.GetDefaultValue(out Color value) + { + value = Color.Black; + } + + int IKeyframeAccess.GetCurveComponents() + { + return 4; + } + + float IKeyframeAccess.GetCurveValue(ref Color value, int component) + { + return value[component]; + } + + void IKeyframeAccess.SetCurveValue(float curve, ref Color value, int component) + { + value[component] = curve; + } + + void IKeyframeAccess.GetDefaultValue(out Color32 value) + { + value = Color32.Black; + } + + int IKeyframeAccess.GetCurveComponents() + { + return 4; + } + + float IKeyframeAccess.GetCurveValue(ref Color32 value, int component) + { + return value[component]; + } + + void IKeyframeAccess.SetCurveValue(float curve, ref Color32 value, int component) + { + value[component] = (byte)Mathf.Clamp(curve, 0, 255); + } + } + } +} diff --git a/Source/Editor/GUI/CurveEditor.Base.cs b/Source/Editor/GUI/CurveEditor.Base.cs new file mode 100644 index 000000000..2dfc7a889 --- /dev/null +++ b/Source/Editor/GUI/CurveEditor.Base.cs @@ -0,0 +1,232 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System; +using FlaxEngine; +using FlaxEngine.GUI; + +namespace FlaxEditor.GUI +{ + /// + /// The base class for editors. Allows to use generic curve editor without type information at compile-time. + /// + [HideInEditor] + public abstract class CurveEditorBase : ContainerControl + { + /// + /// The UI use mode flags. + /// + [Flags] + public enum UseMode + { + /// + /// Disable usage. + /// + Off = 0, + + /// + /// Allow only vertical usage. + /// + Vertical = 1, + + /// + /// Allow only horizontal usage. + /// + Horizontal = 2, + + /// + /// Allow both vertical and horizontal usage. + /// + On = Vertical | Horizontal, + } + + /// + /// Occurs when curve gets edited. + /// + public event Action Edited; + + /// + /// Occurs when curve data editing starts (via UI). + /// + public event Action EditingStart; + + /// + /// Occurs when curve data editing ends (via UI). + /// + public event Action EditingEnd; + + /// + /// The maximum amount of keyframes to use in a single curve. + /// + public int MaxKeyframes = ushort.MaxValue; + + /// + /// True if enable view zooming. Otherwise user won't be able to zoom in or out. + /// + public UseMode EnableZoom = UseMode.On; + + /// + /// True if enable view panning. Otherwise user won't be able to move the view area. + /// + public UseMode EnablePanning = UseMode.On; + + /// + /// Gets or sets the scroll bars usage. + /// + public abstract ScrollBars ScrollBars { get; set; } + + /// + /// Enables drawing start/end values continuous lines. + /// + public bool ShowStartEndLines; + + /// + /// Enables drawing background. + /// + public bool ShowBackground = true; + + /// + /// Enables drawing time and values axes (lines and labels). + /// + public bool ShowAxes = true; + + /// + /// Gets the type of the curves keyframes value. + /// + public abstract Type ValueType { get; } + + /// + /// 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. + /// + public abstract float? FPS { get; set; } + + /// + /// Gets or sets a value indicating whether show curve collapsed as a list of keyframe points rather than a full curve. + /// + public abstract bool ShowCollapsed { get; set; } + + /// + /// Gets or sets the view offset (via scroll bars). + /// + public abstract Vector2 ViewOffset { get; set; } + + /// + /// Gets or sets the view scale. + /// + public abstract Vector2 ViewScale { get; set; } + + /// + /// Gets the amount of keyframes added to the curve. + /// + public abstract int KeyframesCount { get; } + + /// + /// Called when curve gets edited. + /// + public void OnEdited() + { + Edited?.Invoke(); + } + + /// + /// Called when curve data editing starts (via UI). + /// + public void OnEditingStart() + { + EditingStart?.Invoke(); + } + + /// + /// Called when curve data editing ends (via UI). + /// + public void OnEditingEnd() + { + EditingEnd?.Invoke(); + } + + /// + /// Updates the keyframes positioning. + /// + public abstract void UpdateKeyframes(); + + /// + /// Updates the tangents positioning. + /// + public abstract void UpdateTangents(); + + /// + /// Evaluates the animation curve value at the specified time. + /// + /// The interpolated value from the curve at provided time. + /// The time to evaluate the curve at. + /// If true the curve will loop when it goes past the end or beginning. Otherwise the curve value will be clamped. + public abstract void Evaluate(out object result, float time, bool loop = false); + + /// + /// Gets the keyframes collection (as boxed objects). + /// + /// The array of boxed keyframe values of type or . + public abstract object[] GetKeyframes(); + + /// + /// Sets the keyframes collection (as boxed objects). + /// + /// The array of boxed keyframe values of type or . + public abstract void SetKeyframes(object[] keyframes); + + /// + /// Adds the new keyframe (as boxed object). + /// + /// The keyframe time. + /// The keyframe value. + public abstract void AddKeyframe(float time, object value); + + /// + /// Gets the keyframe data (as boxed objects). + /// + /// The keyframe index. + /// The keyframe time. + /// The keyframe value (boxed). + /// The keyframe 'In' tangent value (boxed). + /// The keyframe 'Out' tangent value (boxed). + public abstract void GetKeyframe(int index, out float time, out object value, out object tangentIn, out object tangentOut); + + /// + /// Gets the existing keyframe value (as boxed object). + /// + /// The keyframe index. + /// The keyframe value. + public abstract object GetKeyframe(int index); + + /// + /// Sets the existing keyframe value (as boxed object). + /// + /// The keyframe index. + /// The keyframe value. + public abstract void SetKeyframeValue(int index, object value); + + /// + /// Converts the into the mask. + /// + /// The mode. + /// The mask. + 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); + } + + /// + /// Filters the given value using the . + /// + /// The mode. + /// The value to process. + /// The default value. + /// The combined value. + 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 + ); + } + } +} diff --git a/Source/Editor/GUI/CurveEditor.Contents.cs b/Source/Editor/GUI/CurveEditor.Contents.cs new file mode 100644 index 000000000..9bbfe89d2 --- /dev/null +++ b/Source/Editor/GUI/CurveEditor.Contents.cs @@ -0,0 +1,450 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using FlaxEngine; +using FlaxEngine.GUI; + +namespace FlaxEditor.GUI +{ + partial class CurveEditor + { + /// + /// The curve contents container control. + /// + /// + protected class ContentsBase : ContainerControl + { + private readonly CurveEditor _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; + + /// + /// Initializes a new instance of the class. + /// + /// The curve editor. + public ContentsBase(CurveEditor 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(); + } + + /// + public override bool IntersectsContent(ref Vector2 locationParent, out Vector2 location) + { + // Pass all events + location = PointFromParent(ref locationParent); + return true; + } + + /// + public override void OnMouseEnter(Vector2 location) + { + _mousePos = location; + + base.OnMouseEnter(location); + } + + /// + 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); + } + + /// + 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(); + } + + /// + 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; + } + + /// + 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; + } + + /// + 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; + } + + /// + protected override void SetScaleInternal(ref Vector2 scale) + { + base.SetScaleInternal(ref scale); + + _editor.UpdateKeyframes(); + } + + /// + /// Converts the input point from curve editor contents control space into the keyframes time/value coordinates. + /// + /// The point. + /// The curve contents area bounds. + /// The result. + 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 + ); + } + } + } +} diff --git a/Source/Editor/GUI/CurveEditor.cs b/Source/Editor/GUI/CurveEditor.cs index 157e36434..c44aa232c 100644 --- a/Source/Editor/GUI/CurveEditor.cs +++ b/Source/Editor/GUI/CurveEditor.cs @@ -9,491 +9,15 @@ using FlaxEditor.GUI.ContextMenu; using FlaxEngine; using FlaxEngine.GUI; -// ReSharper disable RedundantAssignment - namespace FlaxEditor.GUI { - /// - /// The base class for editors. Allows to use generic curve editor without type information at compile-time. - /// - [HideInEditor] - public abstract class CurveEditorBase : ContainerControl - { - /// - /// The UI use mode flags. - /// - [Flags] - public enum UseMode - { - /// - /// Disable usage. - /// - Off = 0, - - /// - /// Allow only vertical usage. - /// - Vertical = 1, - - /// - /// Allow only horizontal usage. - /// - Horizontal = 2, - - /// - /// Allow both vertical and horizontal usage. - /// - On = Vertical | Horizontal, - } - - /// - /// Occurs when curve gets edited. - /// - public event Action Edited; - - /// - /// Occurs when curve data editing starts (via UI). - /// - public event Action EditingStart; - - /// - /// Occurs when curve data editing ends (via UI). - /// - public event Action EditingEnd; - - /// - /// The maximum amount of keyframes to use in a single curve. - /// - public int MaxKeyframes = ushort.MaxValue; - - /// - /// True if enable view zooming. Otherwise user won't be able to zoom in or out. - /// - public UseMode EnableZoom = UseMode.On; - - /// - /// True if enable view panning. Otherwise user won't be able to move the view area. - /// - public UseMode EnablePanning = UseMode.On; - - /// - /// Gets or sets the scroll bars usage. - /// - public abstract ScrollBars ScrollBars { get; set; } - - /// - /// Enables drawing start/end values continuous lines. - /// - public bool ShowStartEndLines; - - /// - /// Enables drawing background. - /// - public bool ShowBackground = true; - - /// - /// Enables drawing time and values axes (lines and labels). - /// - public bool ShowAxes = true; - - /// - /// Gets the type of the curves keyframes value. - /// - public abstract Type ValueType { get; } - - /// - /// 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. - /// - public abstract float? FPS { get; set; } - - /// - /// Gets or sets a value indicating whether show curve collapsed as a list of keyframe points rather than a full curve. - /// - public abstract bool ShowCollapsed { get; set; } - - /// - /// Gets or sets the view offset (via scroll bars). - /// - public abstract Vector2 ViewOffset { get; set; } - - /// - /// Gets or sets the view scale. - /// - public abstract Vector2 ViewScale { get; set; } - - /// - /// Gets the amount of keyframes added to the curve. - /// - public abstract int KeyframesCount { get; } - - /// - /// Called when curve gets edited. - /// - public void OnEdited() - { - Edited?.Invoke(); - } - - /// - /// Called when curve data editing starts (via UI). - /// - public void OnEditingStart() - { - EditingStart?.Invoke(); - } - - /// - /// Called when curve data editing ends (via UI). - /// - public void OnEditingEnd() - { - EditingEnd?.Invoke(); - } - - /// - /// Updates the keyframes positioning. - /// - public abstract void UpdateKeyframes(); - - /// - /// Updates the tangents positioning. - /// - public abstract void UpdateTangents(); - - /// - /// Evaluates the animation curve value at the specified time. - /// - /// The interpolated value from the curve at provided time. - /// The time to evaluate the curve at. - /// If true the curve will loop when it goes past the end or beginning. Otherwise the curve value will be clamped. - public abstract void Evaluate(out object result, float time, bool loop = false); - - /// - /// Gets the keyframes collection (as boxed objects). - /// - /// The array of boxed keyframe values of type or . - public abstract object[] GetKeyframes(); - - /// - /// Sets the keyframes collection (as boxed objects). - /// - /// The array of boxed keyframe values of type or . - public abstract void SetKeyframes(object[] keyframes); - - /// - /// Adds the new keyframe (as boxed object). - /// - /// The keyframe time. - /// The keyframe value. - public abstract void AddKeyframe(float time, object value); - - /// - /// Gets the keyframe data (as boxed objects). - /// - /// The keyframe index. - /// The keyframe time. - /// The keyframe value (boxed). - /// The keyframe 'In' tangent value (boxed). - /// The keyframe 'Out' tangent value (boxed). - public abstract void GetKeyframe(int index, out float time, out object value, out object tangentIn, out object tangentOut); - - /// - /// Gets the existing keyframe value (as boxed object). - /// - /// The keyframe index. - /// The keyframe value. - public abstract object GetKeyframe(int index); - - /// - /// Sets the existing keyframe value (as boxed object). - /// - /// The keyframe index. - /// The keyframe value. - public abstract void SetKeyframeValue(int index, object value); - - /// - /// Converts the into the mask. - /// - /// The mode. - /// The mask. - 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); - } - - /// - /// Filters the given value using the . - /// - /// The mode. - /// The value to process. - /// The default value. - /// The combined value. - 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 - ); - } - } - /// /// The generic curve editor control. /// /// The keyframe value type. /// - public abstract class CurveEditor : CurveEditorBase where T : new() + public abstract partial class CurveEditor : CurveEditorBase where T : new() { - /// - /// The generic keyframe value accessor object for curve editor. - /// - /// The keyframe value type. - public interface IKeyframeAccess where U : new() - { - /// - /// Gets the default value. - /// - /// The value. - void GetDefaultValue(out U value); - - /// - /// Gets the curve components count. Vector types should return amount of component to use for value editing. - /// - /// The components count. - int GetCurveComponents(); - - /// - /// Gets the value of the component for the curve. - /// - /// The keyframe value. - /// The component index. - /// The curve value. - float GetCurveValue(ref U value, int component); - - /// - /// Sets the curve value of the component. - /// - /// The curve value to assign. - /// The keyframe value. - /// The component index. - void SetCurveValue(float curve, ref U value, int component); - } - - private class KeyframeAccess : - IKeyframeAccess, - IKeyframeAccess, - IKeyframeAccess, - IKeyframeAccess, - IKeyframeAccess, - IKeyframeAccess, - IKeyframeAccess, - IKeyframeAccess, - IKeyframeAccess, - IKeyframeAccess - { - void IKeyframeAccess.GetDefaultValue(out bool value) - { - value = false; - } - - int IKeyframeAccess.GetCurveComponents() - { - return 1; - } - - float IKeyframeAccess.GetCurveValue(ref bool value, int component) - { - return value ? 1 : 0; - } - - void IKeyframeAccess.SetCurveValue(float curve, ref bool value, int component) - { - value = curve >= 0.5f; - } - - void IKeyframeAccess.GetDefaultValue(out int value) - { - value = 0; - } - - int IKeyframeAccess.GetCurveComponents() - { - return 1; - } - - float IKeyframeAccess.GetCurveValue(ref int value, int component) - { - return value; - } - - void IKeyframeAccess.SetCurveValue(float curve, ref int value, int component) - { - value = (int)curve; - } - - void IKeyframeAccess.GetDefaultValue(out double value) - { - value = 0.0; - } - - int IKeyframeAccess.GetCurveComponents() - { - return 1; - } - - float IKeyframeAccess.GetCurveValue(ref double value, int component) - { - return (float)value; - } - - void IKeyframeAccess.SetCurveValue(float curve, ref double value, int component) - { - value = curve; - } - - void IKeyframeAccess.GetDefaultValue(out float value) - { - value = 0.0f; - } - - int IKeyframeAccess.GetCurveComponents() - { - return 1; - } - - float IKeyframeAccess.GetCurveValue(ref float value, int component) - { - return value; - } - - void IKeyframeAccess.SetCurveValue(float curve, ref float value, int component) - { - value = curve; - } - - void IKeyframeAccess.GetDefaultValue(out Vector2 value) - { - value = Vector2.Zero; - } - - int IKeyframeAccess.GetCurveComponents() - { - return 2; - } - - float IKeyframeAccess.GetCurveValue(ref Vector2 value, int component) - { - return value[component]; - } - - void IKeyframeAccess.SetCurveValue(float curve, ref Vector2 value, int component) - { - value[component] = curve; - } - - void IKeyframeAccess.GetDefaultValue(out Vector3 value) - { - value = Vector3.Zero; - } - - int IKeyframeAccess.GetCurveComponents() - { - return 3; - } - - float IKeyframeAccess.GetCurveValue(ref Vector3 value, int component) - { - return value[component]; - } - - void IKeyframeAccess.SetCurveValue(float curve, ref Vector3 value, int component) - { - value[component] = curve; - } - - void IKeyframeAccess.GetDefaultValue(out Vector4 value) - { - value = Vector4.Zero; - } - - int IKeyframeAccess.GetCurveComponents() - { - return 4; - } - - float IKeyframeAccess.GetCurveValue(ref Vector4 value, int component) - { - return value[component]; - } - - void IKeyframeAccess.SetCurveValue(float curve, ref Vector4 value, int component) - { - value[component] = curve; - } - - public void GetDefaultValue(out Quaternion value) - { - value = Quaternion.Identity; - } - - int IKeyframeAccess.GetCurveComponents() - { - return 3; - } - - float IKeyframeAccess.GetCurveValue(ref Quaternion value, int component) - { - return value.EulerAngles[component]; - } - - void IKeyframeAccess.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.GetDefaultValue(out Color value) - { - value = Color.Black; - } - - int IKeyframeAccess.GetCurveComponents() - { - return 4; - } - - float IKeyframeAccess.GetCurveValue(ref Color value, int component) - { - return value[component]; - } - - void IKeyframeAccess.SetCurveValue(float curve, ref Color value, int component) - { - value[component] = curve; - } - - void IKeyframeAccess.GetDefaultValue(out Color32 value) - { - value = Color32.Black; - } - - int IKeyframeAccess.GetCurveComponents() - { - return 4; - } - - float IKeyframeAccess.GetCurveValue(ref Color32 value, int component) - { - return value[component]; - } - - void IKeyframeAccess.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 } } - /// - /// The curve contents container control. - /// - /// - protected class ContentsBase : ContainerControl - { - private readonly CurveEditor _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; - - /// - /// Initializes a new instance of the class. - /// - /// The curve editor. - public ContentsBase(CurveEditor 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(); - } - - /// - public override bool IntersectsContent(ref Vector2 locationParent, out Vector2 location) - { - // Pass all events - location = PointFromParent(ref locationParent); - return true; - } - - /// - public override void OnMouseEnter(Vector2 location) - { - _mousePos = location; - - base.OnMouseEnter(location); - } - - /// - 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); - } - - /// - 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(); - } - - /// - 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; - } - - /// - 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; - } - - /// - 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; - } - - /// - protected override void SetScaleInternal(ref Vector2 scale) - { - base.SetScaleInternal(ref scale); - - _editor.UpdateKeyframes(); - } - - /// - /// Converts the input point from curve editor contents control space into the keyframes time/value coordinates. - /// - /// The point. - /// The curve contents area bounds. - /// The result. - 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 - ); - } - } - /// /// The single keyframe control. ///