diff --git a/Source/Editor/GUI/CurveEditor.Contents.cs b/Source/Editor/GUI/CurveEditor.Contents.cs
index 94d2ceca7..510e429ae 100644
--- a/Source/Editor/GUI/CurveEditor.Contents.cs
+++ b/Source/Editor/GUI/CurveEditor.Contents.cs
@@ -30,8 +30,10 @@ namespace FlaxEditor.GUI
internal bool _isMovingTangent;
internal bool _movedView;
internal bool _movedKeyframes;
+ internal bool _toggledSelection;
private TangentPoint _movingTangent;
private Float2 _movingSelectionStart;
+ private Float2 _movingSelectionStartPosLock;
private Float2[] _movingSelectionOffsets;
private Float2 _cmShowPos;
@@ -56,12 +58,11 @@ namespace FlaxEditor.GUI
internal void UpdateSelection(ref Rectangle selectionRect)
{
// Find controls to select
- for (int i = 0; i < Children.Count; i++)
+ var children = _children;
+ for (int i = 0; i < children.Count; i++)
{
- if (Children[i] is KeyframePoint p)
- {
+ if (children[i] is KeyframePoint p)
p.IsSelected = p.Bounds.Intersects(ref selectionRect);
- }
}
_editor.UpdateTangents();
}
@@ -72,6 +73,7 @@ namespace FlaxEditor.GUI
_isMovingSelection = true;
_movedKeyframes = false;
var viewRect = _editor._mainPanel.GetClientArea();
+ _movingSelectionStartPosLock = location;
_movingSelectionStart = PointToKeyframes(location, ref viewRect);
if (_movingSelectionOffsets == null || _movingSelectionOffsets.Length != _editor._points.Count)
_movingSelectionOffsets = new Float2[_editor._points.Count];
@@ -82,10 +84,17 @@ namespace FlaxEditor.GUI
internal void OnMove(Float2 location)
{
+ // Skip updating keyframes until move actual starts to be meaningful
+ if (Float2.Distance(ref _movingSelectionStartPosLock, ref location) < 1.5f)
+ return;
+ _movingSelectionStartPosLock = Float2.Minimum;
+
var viewRect = _editor._mainPanel.GetClientArea();
var locationKeyframes = PointToKeyframes(location, ref viewRect);
var accessor = _editor.Accessor;
var components = accessor.GetCurveComponents();
+ var snapEnabled = Root.GetKey(KeyboardKeys.Control);
+ var snapGrid = snapEnabled ? _editor.GetGridSnap() : Float2.One;
for (var i = 0; i < _editor._points.Count; i++)
{
var p = _editor._points[i];
@@ -122,7 +131,20 @@ namespace FlaxEditor.GUI
if (isFirstSelected)
{
time = locationKeyframes.X + offset.X;
+ }
+ if (snapEnabled)
+ {
+ // Snap to the grid
+ var key = new Float2(time, value);
+ key = Float2.SnapToGrid(key, snapGrid);
+ time = key.X;
+ value = key.Y;
+ }
+
+ // Clamp and snap time to the valid range
+ if (isFirstSelected)
+ {
if (_editor.FPS.HasValue)
{
float fps = _editor.FPS.Value;
@@ -131,8 +153,6 @@ namespace FlaxEditor.GUI
time = Mathf.Clamp(time, minTime, maxTime);
}
- // TODO: snapping keyframes to grid when moving
-
_editor.SetKeyframeInternal(p.Index, time, value, p.Component);
}
_editor.UpdateKeyframes();
@@ -234,7 +254,11 @@ namespace FlaxEditor.GUI
var k = _editor.GetKeyframe(_movingTangent.Index);
var kv = _editor.GetKeyframeValue(k);
var value = _editor.Accessor.GetCurveValue(ref kv, _movingTangent.Component);
- _movingTangent.TangentValue = (PointToKeyframes(location, ref viewRect).Y - value) * _editor.ViewScale.X * 2;
+ var tangent = PointToKeyframes(location, ref viewRect).Y - value;
+ if (Root.GetKey(KeyboardKeys.Control))
+ tangent = Float2.SnapToGrid(new Float2(0, tangent), _editor.GetGridSnap()).Y; // Snap tangent over Y axis
+ tangent = tangent * _editor.ViewScale.X * 2;
+ _movingTangent.TangentValue = tangent;
_editor.UpdateTangents();
Cursor = CursorType.SizeNS;
_movedKeyframes = true;
@@ -283,6 +307,7 @@ namespace FlaxEditor.GUI
}
// Cache data
+ _toggledSelection = false;
_isMovingSelection = false;
_isMovingTangent = false;
_mousePos = location;
@@ -305,13 +330,7 @@ namespace FlaxEditor.GUI
{
if (_leftMouseDown)
{
- if (Root.GetKey(KeyboardKeys.Control))
- {
- // Toggle selection
- keyframe.IsSelected = !keyframe.IsSelected;
- _editor.UpdateTangents();
- }
- else if (Root.GetKey(KeyboardKeys.Shift))
+ if (Root.GetKey(KeyboardKeys.Shift))
{
// Select range
keyframe.IsSelected = true;
@@ -335,10 +354,14 @@ namespace FlaxEditor.GUI
else if (!keyframe.IsSelected)
{
// Select node
- if (_editor.KeyframesEditorContext != null)
- _editor.KeyframesEditorContext.OnKeyframesDeselect(_editor);
- else
- _editor.ClearSelection();
+ if (!Root.GetKey(KeyboardKeys.Control))
+ {
+ if (_editor.KeyframesEditorContext != null)
+ _editor.KeyframesEditorContext.OnKeyframesDeselect(_editor);
+ else
+ _editor.ClearSelection();
+ }
+ _toggledSelection = true;
keyframe.IsSelected = true;
_editor.UpdateTangents();
}
@@ -429,6 +452,12 @@ namespace FlaxEditor.GUI
else
OnMoveEnd(location);
}
+ // Toggle selection
+ else if (!_toggledSelection && Root.GetKey(KeyboardKeys.Control) && GetChildAt(location) is KeyframePoint keyframe)
+ {
+ keyframe.IsSelected = !keyframe.IsSelected;
+ _editor.UpdateTangents();
+ }
_isMovingSelection = false;
_isMovingTangent = false;
@@ -514,11 +543,11 @@ namespace FlaxEditor.GUI
{
if (base.OnMouseDoubleClick(location, button))
return true;
-
+
// Add keyframe on double click
var child = GetChildAt(location);
- if (child is not KeyframePoint &&
- child is not TangentPoint &&
+ if (child is not KeyframePoint &&
+ child is not TangentPoint &&
_editor.KeyframesCount < _editor.MaxKeyframes)
{
var viewRect = _editor._mainPanel.GetClientArea();
@@ -545,7 +574,7 @@ namespace FlaxEditor.GUI
var viewRect = _editor._mainPanel.GetClientArea();
var locationInKeyframes = PointToKeyframes(location, ref viewRect);
var locationInEditorBefore = _editor.PointFromKeyframes(locationInKeyframes, ref viewRect);
-
+
// Scale relative to the curve size
var scale = new Float2(delta * 0.1f);
_editor._mainPanel.GetDesireClientArea(out var mainPanelArea);
diff --git a/Source/Editor/GUI/CurveEditor.cs b/Source/Editor/GUI/CurveEditor.cs
index b6e3a1e15..9a774e519 100644
--- a/Source/Editor/GUI/CurveEditor.cs
+++ b/Source/Editor/GUI/CurveEditor.cs
@@ -867,7 +867,7 @@ namespace FlaxEditor.GUI
private void DrawAxis(Float2 axis, Rectangle viewRect, float min, float max, float pixelRange)
{
- Utilities.Utils.DrawCurveTicks((decimal tick, float strength) =>
+ Utilities.Utils.DrawCurveTicks((decimal tick, double step, float strength) =>
{
var p = PointFromKeyframes(axis * (float)tick, ref viewRect);
@@ -892,6 +892,24 @@ namespace FlaxEditor.GUI
}, TickSteps, ref _tickStrengths, min, max, pixelRange);
}
+ private void SetupGrid(out Float2 min, out Float2 max, out Float2 pixelRange)
+ {
+ var viewRect = _mainPanel.GetClientArea();
+ var upperLeft = PointToKeyframes(viewRect.Location, ref viewRect);
+ var bottomRight = PointToKeyframes(viewRect.Size, ref viewRect);
+
+ min = Float2.Min(upperLeft, bottomRight);
+ max = Float2.Max(upperLeft, bottomRight);
+ pixelRange = (max - min) * ViewScale * UnitsPerSecond;
+ }
+
+ private Float2 GetGridSnap()
+ {
+ SetupGrid(out var min, out var max, out var pixelRange);
+ return new Float2(Utilities.Utils.GetCurveGridSnap(TickSteps, ref _tickStrengths, min.X, max.X, pixelRange.X),
+ Utilities.Utils.GetCurveGridSnap(TickSteps, ref _tickStrengths, min.Y, max.Y, pixelRange.Y));
+ }
+
///
/// Draws the curve.
///
@@ -921,12 +939,7 @@ namespace FlaxEditor.GUI
// Draw time and values axes
if (ShowAxes != UseMode.Off)
{
- var upperLeft = PointToKeyframes(viewRect.Location, ref viewRect);
- var bottomRight = PointToKeyframes(viewRect.Size, ref viewRect);
-
- var min = Float2.Min(upperLeft, bottomRight);
- var max = Float2.Max(upperLeft, bottomRight);
- var pixelRange = (max - min) * ViewScale * UnitsPerSecond;
+ SetupGrid(out var min, out var max, out var pixelRange);
Render2D.PushClip(ref viewRect);
diff --git a/Source/Editor/GUI/Timeline/GUI/Background.cs b/Source/Editor/GUI/Timeline/GUI/Background.cs
index b1087a682..70ae53058 100644
--- a/Source/Editor/GUI/Timeline/GUI/Background.cs
+++ b/Source/Editor/GUI/Timeline/GUI/Background.cs
@@ -176,7 +176,7 @@ namespace FlaxEditor.GUI.Timeline.GUI
// Draw vertical lines for time axis
var pixelsInRange = _timeline.Zoom;
var pixelRange = pixelsInRange * (max - min);
- var tickRange = Utilities.Utils.DrawCurveTicks((decimal tick, float strength) =>
+ var tickRange = Utilities.Utils.DrawCurveTicks((decimal tick, double step, float strength) =>
{
var time = (float)tick / _timeline.FramesPerSecond;
var x = time * zoom + Timeline.StartOffset;
diff --git a/Source/Editor/Gizmo/UIEditorGizmo.cs b/Source/Editor/Gizmo/UIEditorGizmo.cs
index 7b4e79441..28ccf6ca4 100644
--- a/Source/Editor/Gizmo/UIEditorGizmo.cs
+++ b/Source/Editor/Gizmo/UIEditorGizmo.cs
@@ -565,7 +565,7 @@ namespace FlaxEditor
var linesColor = style.ForegroundDisabled.RGBMultiplied(0.5f);
var labelsColor = style.ForegroundDisabled;
var labelsSize = 10.0f;
- Utilities.Utils.DrawCurveTicks((decimal tick, float strength) =>
+ Utilities.Utils.DrawCurveTicks((decimal tick, double step, float strength) =>
{
var p = _view.PointToParent(axis * (float)tick);
diff --git a/Source/Editor/Utilities/Utils.cs b/Source/Editor/Utilities/Utils.cs
index 36e847f2d..6197d9eb2 100644
--- a/Source/Editor/Utilities/Utils.cs
+++ b/Source/Editor/Utilities/Utils.cs
@@ -246,7 +246,7 @@ namespace FlaxEditor.Utilities
500000, 1000000, 5000000, 10000000, 100000000
};
- internal delegate void DrawCurveTick(decimal tick, float strength);
+ internal delegate void DrawCurveTick(decimal tick, double step, float strength);
internal static Int2 DrawCurveTicks(DrawCurveTick drawTick, double[] tickSteps, ref float[] tickStrengths, float min, float max, float pixelRange, float minDistanceBetweenTicks = 20, float maxDistanceBetweenTicks = 60)
{
@@ -298,13 +298,29 @@ namespace FlaxEditor.Utilities
if (l < biggestTick && (i % Mathd.RoundToInt(lNextStep / lStep) == 0))
continue;
var tick = (decimal)lStep * i;
- drawTick(tick, strength);
+ drawTick(tick, lStep, strength);
}
}
return new Int2(smallestTick, biggestTick);
}
+ internal static float GetCurveGridSnap(double[] tickSteps, ref float[] tickStrengths, float min, float max, float pixelRange, float minDistanceBetweenTicks = 20, float maxDistanceBetweenTicks = 60)
+ {
+ double gridStep = 0; // No grid
+ float gridWeight = 0.0f;
+ DrawCurveTicks((decimal tick, double step, float strength) =>
+ {
+ // Find the smallest grid step that has meaningful strength (it's the most visible to the user)
+ if (strength > gridWeight && (step < gridStep || gridStep <= 0.0) && strength > 0.5f)
+ {
+ gridStep = Math.Abs(step);
+ gridWeight = strength;
+ }
+ }, tickSteps, ref tickStrengths, min, max, pixelRange, minDistanceBetweenTicks, maxDistanceBetweenTicks);
+ return (float)gridStep;
+ }
+
///
/// Determines whether the specified path string contains any invalid character.
///