From 499228cc98b251b1f8f0af3e3ee98ca79e6837f5 Mon Sep 17 00:00:00 2001 From: Saas Date: Sat, 13 Sep 2025 19:07:41 +0200 Subject: [PATCH 1/6] add curve editor presets --- Source/Editor/GUI/CurveEditor.Contents.cs | 37 +++++++++ Source/Editor/GUI/CurveEditor.cs | 94 +++++++++++++++++++++++ 2 files changed, 131 insertions(+) diff --git a/Source/Editor/GUI/CurveEditor.Contents.cs b/Source/Editor/GUI/CurveEditor.Contents.cs index c2831046b..75f37d457 100644 --- a/Source/Editor/GUI/CurveEditor.Contents.cs +++ b/Source/Editor/GUI/CurveEditor.Contents.cs @@ -522,6 +522,16 @@ namespace FlaxEditor.GUI cm.AddButton("Show whole curve", _editor.ShowWholeCurve); cm.AddButton("Reset view", _editor.ResetView); } + cm.AddSeparator(); + var presetCm = cm.AddChildMenu("Apply preset"); + foreach (var value in Enum.GetValues(typeof(CurvePreset))) + { + CurvePreset preset = (CurvePreset)value; + string name = Utilities.Utils.GetPropertyNameUI(preset.ToString()); + var b = presetCm.ContextMenu.AddButton(name, () => _editor.ApplyPreset(preset)); + b.Enabled = !(_editor is LinearCurveEditor && (preset != CurvePreset.Constant && preset != CurvePreset.Linear)); + } + _editor.OnShowContextMenu(cm, selectionCount); cm.Show(this, location); } @@ -619,6 +629,33 @@ namespace FlaxEditor.GUI } } + /// + /// A list of avaliable curve presets for the . + /// + public enum CurvePreset + { + /// + /// A curve where every point has the same value. + /// + Constant, + /// + /// A curve linear curve. + /// + Linear, + /// + /// A curve that starts a slowly and then accelerates until the end. + /// + EaseIn, + /// + /// A curve that starts a steep and then flattens until the end. + /// + EaseOut, + /// + /// A combination of the and preset. + /// + Smoothstep + } + /// public override void OnKeyframesDeselect(IKeyframesEditor editor) { diff --git a/Source/Editor/GUI/CurveEditor.cs b/Source/Editor/GUI/CurveEditor.cs index 706d07b32..d91eac18c 100644 --- a/Source/Editor/GUI/CurveEditor.cs +++ b/Source/Editor/GUI/CurveEditor.cs @@ -326,6 +326,28 @@ namespace FlaxEditor.GUI private Color _labelsColor; private Font _labelsFont; + /// + /// Preset values for to be applied to a . + /// + public Dictionary PresetValues = new Dictionary + { + { CurvePreset.Constant, new object[] { true, // LinearTangent + 0f, 0.5f, 0f, 0f, // Time, value, tangent in, tangent out + 1f, 0.5f, 0f, 0f } }, + { CurvePreset.EaseIn, new object[] { false, + 0f, 0f, 0f, 0f, + 1f, 1f, -1.4f, 0f } }, + { CurvePreset.EaseOut, new object[] { false, + 1f, 1f, 0f, 0f, + 0f, 0f, 0f, 1.4f } }, + { CurvePreset.Linear, new object[] { true, + 0f, 0f, 0f, 0f, + 1f, 1f, 0f, 0f } }, + { CurvePreset.Smoothstep, new object[] { false, + 0f, 0f, 0f, 0f, + 1f, 1f, 0f, 0f } }, + }; + /// /// The keyframe UI points. /// @@ -568,6 +590,28 @@ namespace FlaxEditor.GUI /// The list of indices of the keyframes to remove. protected abstract void RemoveKeyframesInternal(HashSet indicesToRemove); + /// + /// Tries to convert a float to the type of the type wildcard of the curve editor. + /// + /// The float. + /// The converted value. + public static object ConvertCurvePresetValueToCurveEditorType(float value) + { + if (typeof(T) == typeof(Float2)) + return new Float2(value); + if (typeof(T) == typeof(Float3)) + return new Float3(value); + if (typeof(T) == typeof(Float4)) + return new Float4(value); + if (typeof(T) == typeof(Vector2)) + return new Vector2(value); + if (typeof(T) == typeof(Vector3)) + return new Vector3(value); + if (typeof(T) == typeof(Vector4)) + return new Vector4(value); + return value; + } + /// /// Called when showing a context menu. Can be used to add custom buttons with actions. /// @@ -752,6 +796,17 @@ namespace FlaxEditor.GUI ShowCurve(false); } + /// + /// Applies a to the curve editor. + /// + /// The preset. + public virtual void ApplyPreset(CurvePreset preset) + { + // Remove existing keyframes + SelectAll(); + RemoveKeyframes(); + } + /// public override void Evaluate(out object result, float time, bool loop = false) { @@ -1580,6 +1635,22 @@ namespace FlaxEditor.GUI base.OnDestroy(); } + + /// + public override void ApplyPreset(CurvePreset preset) + { + base.ApplyPreset(preset); + + object[] data = PresetValues[preset]; + for (int i = 1; i < data.Length; i += 4) + { + float time = (float)data[i]; + object value = ConvertCurvePresetValueToCurveEditorType((float)data[i + 1]); + AddKeyframe(time, value); + } + + ShowWholeCurve(); + } } /// @@ -2396,5 +2467,28 @@ namespace FlaxEditor.GUI base.OnDestroy(); } + + /// + public override void ApplyPreset(CurvePreset preset) + { + base.ApplyPreset(preset); + + object[] data = PresetValues[preset]; + for (int i = 1; i < data.Length; i += 4) + { + float time = (float)data[i]; + object value = ConvertCurvePresetValueToCurveEditorType((float)data[i + 1]); + object tangentIn = ConvertCurvePresetValueToCurveEditorType((float)data[i + 2]); + object tangentOut = ConvertCurvePresetValueToCurveEditorType((float)data[i + 3]); + + AddKeyframe(time, value, tangentIn, tangentOut); + } + + SelectAll(); + if ((bool)data[0]) + SetTangentsLinear(); + + ShowWholeCurve(); + } } } From bb6c3233d2709ba6fbfcce6f3a30b7666794c54b Mon Sep 17 00:00:00 2001 From: Saas Date: Sat, 13 Sep 2025 19:22:37 +0200 Subject: [PATCH 2/6] method placement --- Source/Editor/GUI/CurveEditor.cs | 78 ++++++++++++++++---------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/Source/Editor/GUI/CurveEditor.cs b/Source/Editor/GUI/CurveEditor.cs index d91eac18c..0bbbb9354 100644 --- a/Source/Editor/GUI/CurveEditor.cs +++ b/Source/Editor/GUI/CurveEditor.cs @@ -1581,6 +1581,22 @@ namespace FlaxEditor.GUI _tangents[i].Visible = false; } + /// + public override void ApplyPreset(CurvePreset preset) + { + base.ApplyPreset(preset); + + object[] data = PresetValues[preset]; + for (int i = 1; i < data.Length; i += 4) + { + float time = (float)data[i]; + object value = ConvertCurvePresetValueToCurveEditorType((float)data[i + 1]); + AddKeyframe(time, value); + } + + ShowWholeCurve(); + } + /// protected override void DrawCurve(ref Rectangle viewRect) { @@ -1635,22 +1651,6 @@ namespace FlaxEditor.GUI base.OnDestroy(); } - - /// - public override void ApplyPreset(CurvePreset preset) - { - base.ApplyPreset(preset); - - object[] data = PresetValues[preset]; - for (int i = 1; i < data.Length; i += 4) - { - float time = (float)data[i]; - object value = ConvertCurvePresetValueToCurveEditorType((float)data[i + 1]); - AddKeyframe(time, value); - } - - ShowWholeCurve(); - } } /// @@ -2383,6 +2383,29 @@ namespace FlaxEditor.GUI } } + /// + public override void ApplyPreset(CurvePreset preset) + { + base.ApplyPreset(preset); + + object[] data = PresetValues[preset]; + for (int i = 1; i < data.Length; i += 4) + { + float time = (float)data[i]; + object value = ConvertCurvePresetValueToCurveEditorType((float)data[i + 1]); + object tangentIn = ConvertCurvePresetValueToCurveEditorType((float)data[i + 2]); + object tangentOut = ConvertCurvePresetValueToCurveEditorType((float)data[i + 3]); + + AddKeyframe(time, value, tangentIn, tangentOut); + } + + SelectAll(); + if ((bool)data[0]) + SetTangentsLinear(); + + ShowWholeCurve(); + } + /// protected override void SetScaleInternal(ref Float2 scale) { @@ -2467,28 +2490,5 @@ namespace FlaxEditor.GUI base.OnDestroy(); } - - /// - public override void ApplyPreset(CurvePreset preset) - { - base.ApplyPreset(preset); - - object[] data = PresetValues[preset]; - for (int i = 1; i < data.Length; i += 4) - { - float time = (float)data[i]; - object value = ConvertCurvePresetValueToCurveEditorType((float)data[i + 1]); - object tangentIn = ConvertCurvePresetValueToCurveEditorType((float)data[i + 2]); - object tangentOut = ConvertCurvePresetValueToCurveEditorType((float)data[i + 3]); - - AddKeyframe(time, value, tangentIn, tangentOut); - } - - SelectAll(); - if ((bool)data[0]) - SetTangentsLinear(); - - ShowWholeCurve(); - } } } From ee6fae8956048442cf1fb1bca0f290129c5d5e6f Mon Sep 17 00:00:00 2001 From: Saas Date: Sun, 14 Sep 2025 14:15:35 +0200 Subject: [PATCH 3/6] small polish stuff - Fix keyframe edit popup height - Make keyframe mouse hover color more intense - Increase keyframe size by 1 to prevent accidental miss clicks, resulting in a new keyframe added, when trying to double click to edit a keyframe --- Source/Editor/GUI/CurveEditor.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Source/Editor/GUI/CurveEditor.cs b/Source/Editor/GUI/CurveEditor.cs index 0bbbb9354..ce3845e3d 100644 --- a/Source/Editor/GUI/CurveEditor.cs +++ b/Source/Editor/GUI/CurveEditor.cs @@ -26,11 +26,12 @@ namespace FlaxEditor.GUI private List _keyframeIndices; private bool _isDirty; - public Popup(CurveEditor editor, object[] selection, List keyframeIndices = null, float height = 140.0f) - : this(editor, height) + public Popup(CurveEditor editor, object[] selection, List keyframeIndices = null, float maxHeight = 140.0f) + : this(editor, maxHeight) { _presenter.Select(selection); _presenter.OpenAllGroups(); + Size = new Float2(Size.X, Mathf.Min(_presenter.ContainerControl.Size.Y, maxHeight)); _keyframeIndices = keyframeIndices; if (keyframeIndices != null && selection.Length != keyframeIndices.Count) throw new Exception(); @@ -169,7 +170,7 @@ namespace FlaxEditor.GUI if (IsSelected) color = Editor.ContainsFocus ? style.SelectionBorder : Color.Lerp(style.ForegroundDisabled, style.SelectionBorder, 0.4f); if (IsMouseOver) - color *= 1.1f; + color *= 1.5f; Render2D.FillRectangle(rect, color); } @@ -285,7 +286,7 @@ namespace FlaxEditor.GUI /// /// The keyframes size. /// - protected static readonly Float2 KeyframesSize = new Float2(7.0f); + protected static readonly Float2 KeyframesSize = new Float2(8.0f); /// /// The colors for the keyframe points. From 6810e5f2a442e6773335fa146939349219bc1b1a Mon Sep 17 00:00:00 2001 From: Saas Date: Sun, 14 Sep 2025 16:00:07 +0200 Subject: [PATCH 4/6] add moving selected keyframes with arrow keys --- Source/Editor/GUI/CurveEditor.cs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Source/Editor/GUI/CurveEditor.cs b/Source/Editor/GUI/CurveEditor.cs index ce3845e3d..d9f79b6b9 100644 --- a/Source/Editor/GUI/CurveEditor.cs +++ b/Source/Editor/GUI/CurveEditor.cs @@ -1084,6 +1084,30 @@ namespace FlaxEditor.GUI return true; } + bool left = key == KeyboardKeys.ArrowLeft; + bool right = key == KeyboardKeys.ArrowRight; + bool up = key == KeyboardKeys.ArrowUp; + bool down = key == KeyboardKeys.ArrowDown; + + if (left || right || up || down) + { + bool shift = Root.GetKey(KeyboardKeys.Shift); + bool alt = Root.GetKey(KeyboardKeys.Alt); + float deltaValue = 10f; + if (shift || alt) + deltaValue = shift ? 2.5f : 5f; + + Float2 moveDelta = Float2.Zero; + if (left || right) + moveDelta.X = left ? -deltaValue : deltaValue; + if (up || down) + moveDelta.Y = up ? -deltaValue : deltaValue; + + _contents.OnMoveStart(Float2.Zero); + _contents.OnMove(moveDelta); + _contents.OnMoveEnd(Float2.Zero); + } + return false; } From facd7d39dc86345a9f9457487067dcf163bee926 Mon Sep 17 00:00:00 2001 From: Saas Date: Sun, 14 Sep 2025 16:02:48 +0200 Subject: [PATCH 5/6] return true if handled --- Source/Editor/GUI/CurveEditor.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Editor/GUI/CurveEditor.cs b/Source/Editor/GUI/CurveEditor.cs index d9f79b6b9..ac3d38353 100644 --- a/Source/Editor/GUI/CurveEditor.cs +++ b/Source/Editor/GUI/CurveEditor.cs @@ -1106,6 +1106,7 @@ namespace FlaxEditor.GUI _contents.OnMoveStart(Float2.Zero); _contents.OnMove(moveDelta); _contents.OnMoveEnd(Float2.Zero); + return true; } return false; From 887311d1f1257cde43839b7f88db15624a7eeefa Mon Sep 17 00:00:00 2001 From: Saas Date: Thu, 13 Nov 2025 23:00:09 +0100 Subject: [PATCH 6/6] replace raw data with (a) struct(s) --- Source/Editor/GUI/CurveEditor.cs | 132 ++++++++++++++++++++++++------- 1 file changed, 105 insertions(+), 27 deletions(-) diff --git a/Source/Editor/GUI/CurveEditor.cs b/Source/Editor/GUI/CurveEditor.cs index ac3d38353..4fb727ea1 100644 --- a/Source/Editor/GUI/CurveEditor.cs +++ b/Source/Editor/GUI/CurveEditor.cs @@ -19,6 +19,48 @@ namespace FlaxEditor.GUI /// public abstract partial class CurveEditor : CurveEditorBase where T : new() { + /// + /// Represents a single point in a . + /// + protected struct CurvePresetPoint + { + /// + /// The time. + /// + public float Time; + + /// + /// The value. + /// + public float Value; + + /// + /// The in tangent. Will be ignored in + /// + public float TangentIn; + + /// + /// The out tangent. Will be ignored in + /// + public float TangentOut; + } + + /// + /// A curve preset. + /// + protected struct CurveEditorPreset() + { + /// + /// If the tangents will be linear or smooth. + /// + public bool LinearTangents; + + /// + /// The points of the preset. + /// + public List Points; + } + private class Popup : ContextMenuBase { private CustomEditorPresenter _presenter; @@ -330,23 +372,58 @@ namespace FlaxEditor.GUI /// /// Preset values for to be applied to a . /// - public Dictionary PresetValues = new Dictionary + protected Dictionary Presets = new Dictionary { - { CurvePreset.Constant, new object[] { true, // LinearTangent - 0f, 0.5f, 0f, 0f, // Time, value, tangent in, tangent out - 1f, 0.5f, 0f, 0f } }, - { CurvePreset.EaseIn, new object[] { false, - 0f, 0f, 0f, 0f, - 1f, 1f, -1.4f, 0f } }, - { CurvePreset.EaseOut, new object[] { false, - 1f, 1f, 0f, 0f, - 0f, 0f, 0f, 1.4f } }, - { CurvePreset.Linear, new object[] { true, - 0f, 0f, 0f, 0f, - 1f, 1f, 0f, 0f } }, - { CurvePreset.Smoothstep, new object[] { false, - 0f, 0f, 0f, 0f, - 1f, 1f, 0f, 0f } }, + { CurvePreset.Constant, new CurveEditorPreset + { + LinearTangents = true, + Points = new List + { + new CurvePresetPoint { Time = 0f, Value = 0.5f, TangentIn = 0f, TangentOut = 0f }, + new CurvePresetPoint { Time = 1f, Value = 0.5f, TangentIn = 0f, TangentOut = 0f }, + } + } + }, + { CurvePreset.EaseIn, new CurveEditorPreset + { + LinearTangents = false, + Points = new List + { + new CurvePresetPoint { Time = 0f, Value = 0f, TangentIn = 0f, TangentOut = 0f }, + new CurvePresetPoint { Time = 1f, Value = 1f, TangentIn = -1.4f, TangentOut = 0f }, + } + } + }, + { CurvePreset.EaseOut, new CurveEditorPreset + { + LinearTangents = false, + Points = new List + { + new CurvePresetPoint { Time = 1f, Value = 1f, TangentIn = 0f, TangentOut = 0f }, + new CurvePresetPoint { Time = 0f, Value = 0f, TangentIn = 0f, TangentOut = 1.4f }, + } + } + }, + { CurvePreset.Linear, new CurveEditorPreset + { + LinearTangents = true, + Points = new List + { + new CurvePresetPoint { Time = 0f, Value = 0f, TangentIn = 0f, TangentOut = 0f }, + new CurvePresetPoint { Time = 1f, Value = 1f, TangentIn = 0f, TangentOut = 0f }, + } + } + }, + { CurvePreset.Smoothstep, new CurveEditorPreset + { + LinearTangents = false, + Points = new List + { + new CurvePresetPoint { Time = 0f, Value = 0f, TangentIn = 0f, TangentOut = 0f }, + new CurvePresetPoint { Time = 1f, Value = 1f, TangentIn = 0f, TangentOut = 0f }, + } + } + }, }; /// @@ -1612,11 +1689,11 @@ namespace FlaxEditor.GUI { base.ApplyPreset(preset); - object[] data = PresetValues[preset]; - for (int i = 1; i < data.Length; i += 4) + CurveEditorPreset data = Presets[preset]; + foreach (var point in data.Points) { - float time = (float)data[i]; - object value = ConvertCurvePresetValueToCurveEditorType((float)data[i + 1]); + float time = point.Time; + object value = ConvertCurvePresetValueToCurveEditorType((float)point.Value); AddKeyframe(time, value); } @@ -2414,19 +2491,20 @@ namespace FlaxEditor.GUI { base.ApplyPreset(preset); - object[] data = PresetValues[preset]; - for (int i = 1; i < data.Length; i += 4) + CurveEditorPreset data = Presets[preset]; + + foreach (var point in data.Points) { - float time = (float)data[i]; - object value = ConvertCurvePresetValueToCurveEditorType((float)data[i + 1]); - object tangentIn = ConvertCurvePresetValueToCurveEditorType((float)data[i + 2]); - object tangentOut = ConvertCurvePresetValueToCurveEditorType((float)data[i + 3]); + float time = point.Time; + object value = ConvertCurvePresetValueToCurveEditorType((float)point.Value); + object tangentIn = ConvertCurvePresetValueToCurveEditorType((float)point.TangentIn); + object tangentOut = ConvertCurvePresetValueToCurveEditorType((float)point.TangentOut); AddKeyframe(time, value, tangentIn, tangentOut); } SelectAll(); - if ((bool)data[0]) + if (data.LinearTangents) SetTangentsLinear(); ShowWholeCurve();