This commit is contained in:
thallard
2021-08-05 12:40:38 +02:00
13 changed files with 1147 additions and 1032 deletions

View 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);
}
}
}
}

View File

@@ -0,0 +1,255 @@
// 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>
/// Shows the whole curve.
/// </summary>
public abstract void ShowWholeCurve();
/// <summary>
/// Resets the view.
/// </summary>
public void ResetView()
{
ViewScale = ApplyUseModeMask(EnableZoom, Vector2.One, ViewScale);
ViewOffset = ApplyUseModeMask(EnablePanning, Vector2.Zero, ViewOffset);
UpdateKeyframes();
}
/// <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>
/// Gets the keyframe point (in keyframes space).
/// </summary>
/// <param name="index">The keyframe index.</param>
/// <param name="component">The keyframe value component index.</param>
/// <returns>The point in time/value space.</returns>
public abstract Vector2 GetKeyframePoint(int index, int component);
/// <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
);
}
}
}

View File

@@ -0,0 +1,448 @@
// 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)
{
Vector2 delta = location - _movingViewLastPos;
delta *= GetUseModeMask(_editor.EnablePanning) * _editor.ViewScale;
if (delta.LengthSquared > 0.01f)
{
_editor._mainPanel.ViewOffset += delta;
_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.IsSelected = true;
_editor.UpdateTangents();
}
// Check if node isn't selected
else if (!keyframe.IsSelected)
{
// Select node
_editor.ClearSelection();
keyframe.IsSelected = true;
_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].Point - _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.IsSelected = true;
_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
);
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -72,9 +72,11 @@ namespace FlaxEditor.GUI
internal Vector2 _leftMouseDownPos = Vector2.Minimum;
private Vector2 _rightMouseDownPos = Vector2.Minimum;
internal Vector2 _mousePos = Vector2.Minimum;
private float _mouseMoveAmount;
private Vector2 _movingViewLastPos;
internal bool _isMovingSelection;
private Vector2 _movingSelectionViewPos;
internal bool _movedKeyframes;
private float _movingSelectionStart;
private float[] _movingSelectionOffsets;
private Vector2 _cmShowPos;
/// <summary>
@@ -125,13 +127,12 @@ namespace FlaxEditor.GUI
if (_rightMouseDown)
{
// Calculate delta
Vector2 delta = location - _rightMouseDownPos;
Vector2 delta = location - _movingViewLastPos;
if (delta.LengthSquared > 0.01f && _editor.EnablePanning)
{
// Move view
_mouseMoveAmount += delta.Length;
_editor.ViewOffset += delta * _editor.ViewScale;
_rightMouseDownPos = location;
_movingViewLastPos = location;
Cursor = CursorType.SizeAll;
}
@@ -140,44 +141,38 @@ namespace FlaxEditor.GUI
// Moving selection
else if (_isMovingSelection)
{
// Calculate delta (apply view offset)
Vector2 viewDelta = _editor.ViewOffset - _movingSelectionViewPos;
_movingSelectionViewPos = _editor.ViewOffset;
var viewRect = _editor._mainPanel.GetClientArea();
var delta = location - _leftMouseDownPos - viewDelta;
_mouseMoveAmount += delta.Length;
if (delta.LengthSquared > 0.01f)
var locationKeyframes = PointToKeyframes(location, ref viewRect);
for (var i = 0; i < _editor._points.Count; i++)
{
// Move selected keyframes
var keyframeDelta = PointToKeyframes(location, ref viewRect) - PointToKeyframes(_leftMouseDownPos - viewDelta, ref viewRect);
for (var i = 0; i < _editor._points.Count; i++)
var p = _editor._points[i];
if (p.IsSelected)
{
var p = _editor._points[i];
if (p.IsSelected)
var k = _editor._keyframes[p.Index];
float minTime = p.Index != 0 ? _editor._keyframes[p.Index - 1].Time : float.MinValue;
float maxTime = p.Index != _editor._keyframes.Count - 1 ? _editor._keyframes[p.Index + 1].Time : float.MaxValue;
var offset = _movingSelectionOffsets[p.Index];
k.Time = locationKeyframes.X + offset;
if (_editor.FPS.HasValue)
{
var k = _editor._keyframes[p.Index];
float minTime = p.Index != 0 ? _editor._keyframes[p.Index - 1].Time : float.MinValue;
float maxTime = p.Index != _editor._keyframes.Count - 1 ? _editor._keyframes[p.Index + 1].Time : float.MaxValue;
k.Time += keyframeDelta.X;
if (_editor.FPS.HasValue)
{
float fps = _editor.FPS.Value;
k.Time = Mathf.Floor(k.Time * fps) / fps;
}
k.Time = Mathf.Clamp(k.Time, minTime, maxTime);
// TODO: snapping keyframes to grid when moving
_editor._keyframes[p.Index] = k;
float fps = _editor.FPS.Value;
k.Time = Mathf.Floor(k.Time * fps) / fps;
}
k.Time = Mathf.Clamp(k.Time, minTime, maxTime);
// TODO: snapping keyframes to grid when moving
_editor._keyframes[p.Index] = k;
}
_editor.UpdateKeyframes();
if (_editor.EnablePanning)
_editor._mainPanel.ScrollViewTo(PointToParent(location));
_leftMouseDownPos = location;
{
//_editor._mainPanel.ScrollViewTo(PointToParent(_editor._mainPanel, location));
}
Cursor = CursorType.SizeAll;
_movedKeyframes = true;
}
return;
@@ -234,6 +229,7 @@ namespace FlaxEditor.GUI
{
_rightMouseDown = true;
_rightMouseDownPos = location;
_movingViewLastPos = location;
}
// Check if any node is under the mouse
@@ -258,11 +254,17 @@ namespace FlaxEditor.GUI
// Start moving selected nodes
StartMouseCapture();
_mouseMoveAmount = 0;
_isMovingSelection = true;
_movingSelectionViewPos = _editor.ViewOffset;
_movedKeyframes = false;
var viewRect = _editor._mainPanel.GetClientArea();
_movingSelectionStart = PointToKeyframes(location, ref viewRect).X;
if (_movingSelectionOffsets == null || _movingSelectionOffsets.Length != _editor._keyframes.Count)
_movingSelectionOffsets = new float[_editor._keyframes.Count];
for (int i = 0; i < _movingSelectionOffsets.Length; i++)
_movingSelectionOffsets[i] = _editor._keyframes[i].Time - _movingSelectionStart;
_editor.OnEditingStart();
Focus();
Tooltip?.Hide();
return true;
}
}
@@ -303,10 +305,11 @@ namespace FlaxEditor.GUI
// Moving keyframes
if (_isMovingSelection)
{
if (_mouseMoveAmount > 3.0f)
if (_movedKeyframes)
{
_editor.OnEdited();
_editor.OnEditingEnd();
_editor.UpdateKeyframes();
}
}
// Selecting
@@ -316,6 +319,7 @@ namespace FlaxEditor.GUI
}
_isMovingSelection = false;
_movedKeyframes = false;
}
if (_rightMouseDown && button == MouseButton.Right)
{
@@ -324,7 +328,7 @@ namespace FlaxEditor.GUI
Cursor = CursorType.Default;
// Check if no move has been made at all
if (_mouseMoveAmount < 3.0f)
if (Vector2.Distance(ref location, ref _rightMouseDownPos) < 3.0f)
{
var selectionCount = _editor.SelectionCount;
var underMouse = GetChildAt(location);
@@ -362,7 +366,6 @@ namespace FlaxEditor.GUI
}
cm.Show(this, location);
}
_mouseMoveAmount = 0;
}
if (base.OnMouseUp(location, button))
@@ -1045,8 +1048,10 @@ namespace FlaxEditor.GUI
}
// Adjust contents bounds to fill the keyframes area
if (EnablePanning)
if (EnablePanning && !_contents._isMovingSelection)
{
_contents.Bounds = bounds;
}
// Offset the keyframes (parent container changed its location)
var posOffset = _contents.Location;

View File

@@ -1276,24 +1276,24 @@ namespace FlaxEditor.GUI.Timeline
case KeyboardKeys.ArrowUp:
{
int index = IndexInParent;
if (index > 0)
while (index != 0)
{
do
{
toSelect = Parent.GetChild(--index) as Track;
} while (index != -1 && toSelect != null && !toSelect.HasParentsExpanded);
toSelect = Parent.GetChild(--index) as Track;
if (toSelect != null && toSelect.HasParentsExpanded)
break;
toSelect = null;
}
break;
}
case KeyboardKeys.ArrowDown:
{
int index = IndexInParent;
if (index < Parent.ChildrenCount - 1)
while (index < Parent.ChildrenCount - 1)
{
do
{
toSelect = Parent.GetChild(++index) as Track;
} while (index != Parent.ChildrenCount && toSelect != null && !toSelect.HasParentsExpanded);
toSelect = Parent.GetChild(++index) as Track;
if (toSelect != null && toSelect.HasParentsExpanded)
break;
toSelect = null;
}
break;
}

View File

@@ -24,7 +24,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
}
}
private Image[] _thumbnails = new Image[2];
private Image[] _thumbnails = new Image[3];
/// <summary>
/// Initializes a new instance of the <see cref="CameraCutMedia"/> class.
@@ -107,6 +107,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
float orthoScale = 1.0f;
float fov = 60.0f;
float customAspectRatio = 0.0f;
view.RenderLayersMask = new LayersMask(uint.MaxValue);
// Try to evaluate camera properties based on the initial camera state
if (cam)
@@ -119,10 +120,15 @@ namespace FlaxEditor.GUI.Timeline.Tracks
orthoScale = cam.OrthographicScale;
fov = cam.FieldOfView;
customAspectRatio = cam.CustomAspectRatio;
view.RenderLayersMask = cam.RenderLayersMask;
}
// Try to evaluate camera properties based on the animated tracks
float time = req.ThumbnailIndex == 0 ? Start : Start + Duration;
float time = Start;
if (req.ThumbnailIndex == 1)
time += Duration;
else if (req.ThumbnailIndex == 2)
time += Duration * 0.5f;
foreach (var subTrack in track.SubTracks)
{
if (subTrack is MemberTrack memberTrack)
@@ -176,7 +182,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
view.NonJitteredProjection = view.Projection;
view.TemporalAAJitter = Vector4.Zero;
view.ModelLODDistanceFactor = 100.0f;
view.Flags = ViewFlags.DefaultGame & ~(ViewFlags.MotionBlur | ViewFlags.EyeAdaptation);
view.Flags = ViewFlags.DefaultGame & ~(ViewFlags.MotionBlur);
view.UpdateCachedData();
task.View = view;
}
@@ -202,7 +208,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
Bounds = new Rectangle(2, 2, CameraCutThumbnailRenderer.Width, CameraCutThumbnailRenderer.Height),
};
}
else
else if (req.ThumbnailIndex == 1)
{
image = new Image
{
@@ -211,6 +217,15 @@ namespace FlaxEditor.GUI.Timeline.Tracks
Bounds = new Rectangle(Width - 2 - CameraCutThumbnailRenderer.Width, 2, CameraCutThumbnailRenderer.Width, CameraCutThumbnailRenderer.Height),
};
}
else
{
image = new Image
{
AnchorPreset = AnchorPresets.MiddleCenter,
Parent = this,
Bounds = new Rectangle(Width * 0.5f - 1 - CameraCutThumbnailRenderer.Width * 0.5f, 2, CameraCutThumbnailRenderer.Width, CameraCutThumbnailRenderer.Height),
};
}
image.UnlockChildrenRecursive();
_thumbnails[req.ThumbnailIndex] = image;
UpdateUI();
@@ -226,14 +241,47 @@ namespace FlaxEditor.GUI.Timeline.Tracks
private void UpdateUI()
{
var width = Mathf.Min(CameraCutThumbnailRenderer.Width, (Width - 6.0f) * 0.5f);
for (int i = 0; i < _thumbnails.Length; i++)
if (_thumbnails == null)
return;
var width = Width - (_thumbnails.Length + 1) * 2;
if (width < 10.0f)
{
for (int i = 0; i < _thumbnails.Length; i++)
{
var image = _thumbnails[i];
if (image != null)
image.Visible = false;
}
return;
}
var count = Mathf.Min(Mathf.FloorToInt(width / CameraCutThumbnailRenderer.Width), _thumbnails.Length);
if (count == 0 && _thumbnails.Length != 0)
{
var image = _thumbnails[0];
if (image != null)
{
image.Width = Mathf.Min(CameraCutThumbnailRenderer.Width, width);
image.SetAnchorPreset(image.AnchorPreset, false);
image.Visible = true;
}
return;
}
for (int i = 0; i < count; i++)
{
var image = _thumbnails[i];
if (image != null)
{
image.Width = width;
image.Visible = width >= 10.0f;
image.Width = CameraCutThumbnailRenderer.Width;
image.SetAnchorPreset(image.AnchorPreset, false);
image.Visible = true;
}
}
for (int i = count; i < _thumbnails.Length; i++)
{
var image = _thumbnails[i];
if (image != null)
{
image.Visible = false;
}
}
}
@@ -260,7 +308,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
{
base.OnDurationFramesChanged();
UpdateThumbnails(new[] { 1 });
UpdateThumbnails(new[] { 1, 2 });
}
/// <inheritdoc />

View File

@@ -259,6 +259,8 @@ namespace FlaxEditor.GUI.Timeline.Tracks
{
Height = IsExpanded ? ExpandedHeight : CollapsedHeight;
UpdateCurve();
if (IsExpanded)
Curve.ShowWholeCurve();
base.OnExpandedChanged();
}