From 4d6282a5b4baa53a51fcf8628221b2c1be68a4d6 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 29 Jan 2025 23:54:41 +0100 Subject: [PATCH] Add focus selection to curve editor and apply margin around shown curve section #2455 --- Source/Editor/GUI/CurveEditor.cs | 110 ++++++++++++++++++++++++------- 1 file changed, 87 insertions(+), 23 deletions(-) diff --git a/Source/Editor/GUI/CurveEditor.cs b/Source/Editor/GUI/CurveEditor.cs index 149543ef9..b6e3a1e15 100644 --- a/Source/Editor/GUI/CurveEditor.cs +++ b/Source/Editor/GUI/CurveEditor.cs @@ -674,30 +674,84 @@ namespace FlaxEditor.GUI OnEditingEnd(); } - /// - public override void ShowWholeCurve() + private void ShowCurve(bool selectedOnly) { if (_points.Count == 0) return; - _mainPanel.GetDesireClientArea(out var mainPanelArea); - ViewScale = ApplyUseModeMask(EnableZoom, mainPanelArea.Size / _contents.Size, ViewScale); - Float2 minPos = Float2.Maximum; + int pass = 1; + REDO: + + // Get curve bounds in Keyframes (time and value) + Float2 posMin = Float2.Maximum, posMax = Float2.Minimum; + // TODO: include bezier curve bounds calculation to handle curve outside the bounds made out of points foreach (var point in _points) { - var pos = point.PointToParent(point.Location); - Float2.Min(ref minPos, ref pos, out minPos); + if (selectedOnly && !point.IsSelected) + continue; + var pos = point.Point; + Float2.Min(ref posMin, ref pos, out posMin); + Float2.Max(ref posMax, ref pos, out posMax); } - var minPosPoint = _contents.PointToParent(ref minPos); - var scroll = new Float2(_mainPanel.HScrollBar?.TargetValue ?? 0, _mainPanel.VScrollBar?.TargetValue ?? 0); - scroll = ApplyUseModeMask(EnablePanning, minPosPoint, scroll); - if (_mainPanel.HScrollBar != null) - _mainPanel.HScrollBar.TargetValue = scroll.X; - if (_mainPanel.VScrollBar != null) - _mainPanel.VScrollBar.TargetValue = scroll.Y; + + // Apply margin around the area + var posMargin = (posMax - posMin) * 0.05f; + posMin -= posMargin; + posMax += posMargin; + + // Convert from Keyframes to Contents + _mainPanel.GetDesireClientArea(out var viewRect); + PointFromKeyframesToContents(ref posMin, ref viewRect); + PointFromKeyframesToContents(ref posMax, ref viewRect); + var tmp = posMin; + Float2.Min(ref posMin, ref posMax, out posMin); + Float2.Max(ref posMax, ref tmp, out posMax); + var contentsSize = posMax - posMin; + + // Convert from Contents to Main Panel + posMin = _contents.PointToParent(posMin); + posMax = _contents.PointToParent(posMax); + tmp = posMin; + Float2.Min(ref posMin, ref posMax, out posMin); + Float2.Max(ref posMax, ref tmp, out posMax); + + // Update zoom (leave unchanged when focusing a single point) + var zoomMask = EnableZoom; + if (Mathf.IsZero(posMargin.X)) + zoomMask &= ~UseMode.Horizontal; + if (Mathf.IsZero(posMargin.Y)) + zoomMask &= ~UseMode.Vertical; + ViewScale = ApplyUseModeMask(zoomMask, viewRect.Size / contentsSize, ViewScale); + + // Update scroll (attempt to center the area when it's smaller than the view) + Float2 viewOffset = -posMin; + Float2 viewSize = _mainPanel.Size; + Float2 viewSizeLeft = viewSize - Float2.Clamp(posMax - posMin, Float2.Zero, viewSize); + viewOffset += viewSizeLeft * 0.5f; + viewOffset = ApplyUseModeMask(EnablePanning, viewOffset, _mainPanel.ViewOffset); + _mainPanel.ViewOffset = viewOffset; + + // Do it multiple times so the view offset can be properly calculate once the view scale gets changes + if (pass++ <= 2) + goto REDO; UpdateKeyframes(); } + /// + /// Focuses the view on the selected keyframes. + /// + public void FocusSelection() + { + // Fallback to showing whole curve if nothing is selected + ShowCurve(SelectionCount != 0); + } + + /// + public override void ShowWholeCurve() + { + ShowCurve(false); + } + /// public override void Evaluate(out object result, float time, bool loop = false) { @@ -774,10 +828,7 @@ namespace FlaxEditor.GUI point = _contents.PointFromParent(point); // Contents -> Keyframes - return new Float2( - (point.X + _contents.Location.X) / UnitsPerSecond, - (point.Y + _contents.Location.Y - curveContentAreaBounds.Height) / -UnitsPerSecond - ); + return PointFromContentsToKeyframes(ref point, ref curveContentAreaBounds); } /// @@ -789,10 +840,7 @@ namespace FlaxEditor.GUI protected Float2 PointFromKeyframes(Float2 point, ref Rectangle curveContentAreaBounds) { // Keyframes -> Contents - point = new Float2( - point.X * UnitsPerSecond - _contents.Location.X, - point.Y * -UnitsPerSecond + curveContentAreaBounds.Height - _contents.Location.Y - ); + PointFromKeyframesToContents(ref point, ref curveContentAreaBounds); // Contents -> Main Panel point = _contents.PointToParent(point); @@ -801,6 +849,22 @@ namespace FlaxEditor.GUI return _mainPanel.PointToParent(point); } + internal Float2 PointFromContentsToKeyframes(ref Float2 point, ref Rectangle curveContentAreaBounds) + { + return new Float2( + (point.X + _contents.Location.X) / UnitsPerSecond, + (point.Y + _contents.Location.Y - curveContentAreaBounds.Height) / -UnitsPerSecond + ); + } + + internal void PointFromKeyframesToContents(ref Float2 point, ref Rectangle curveContentAreaBounds) + { + point = new Float2( + point.X * UnitsPerSecond - _contents.Location.X, + point.Y * -UnitsPerSecond + curveContentAreaBounds.Height - _contents.Location.Y + ); + } + private void DrawAxis(Float2 axis, Rectangle viewRect, float min, float max, float pixelRange) { Utilities.Utils.DrawCurveTicks((decimal tick, float strength) => @@ -947,7 +1011,7 @@ namespace FlaxEditor.GUI } else if (options.FocusSelection.Process(this)) { - ShowWholeCurve(); + FocusSelection(); return true; }