diff --git a/Source/Editor/GUI/CurveEditor.Base.cs b/Source/Editor/GUI/CurveEditor.Base.cs index d767d8ba9..930be3b65 100644 --- a/Source/Editor/GUI/CurveEditor.Base.cs +++ b/Source/Editor/GUI/CurveEditor.Base.cs @@ -202,8 +202,19 @@ namespace FlaxEditor.GUI /// Adds the new keyframe (as boxed object). /// /// The keyframe time. - /// The keyframe value. - public abstract void AddKeyframe(float time, object value); + /// The keyframe value (boxed). + /// The index of the keyframe. + public abstract int AddKeyframe(float time, object value); + + /// + /// Adds the new keyframe (as boxed object). + /// + /// The keyframe time. + /// The keyframe value (boxed). + /// The keyframe 'In' tangent value (boxed). + /// The keyframe 'Out' tangent value (boxed). + /// The index of the keyframe. + public abstract int AddKeyframe(float time, object value, object tangentIn, object tangentOut); /// /// Gets the keyframe data (as boxed objects). @@ -279,5 +290,11 @@ namespace FlaxEditor.GUI /// public abstract void OnKeyframesMove(IKeyframesEditor editor, ContainerControl control, Vector2 location, bool start, bool end); + + /// + public abstract void OnKeyframesCopy(IKeyframesEditor editor, float? timeOffset, System.Text.StringBuilder data); + + /// + public abstract void OnKeyframesPaste(IKeyframesEditor editor, float? timeOffset, string[] datas, ref int index); } } diff --git a/Source/Editor/GUI/CurveEditor.Contents.cs b/Source/Editor/GUI/CurveEditor.Contents.cs index c544fc602..fc273269e 100644 --- a/Source/Editor/GUI/CurveEditor.Contents.cs +++ b/Source/Editor/GUI/CurveEditor.Contents.cs @@ -1,7 +1,13 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Text; +using FlaxEditor.Scripting; using FlaxEngine; using FlaxEngine.GUI; +using FlaxEngine.Json; namespace FlaxEditor.GUI { @@ -440,12 +446,21 @@ namespace FlaxEditor.GUI if (Vector2.Distance(ref location, ref _rightMouseDownPos) < 3.0f) { var selectionCount = _editor.SelectionCount; - var underMouse = GetChildAt(location); - if (selectionCount == 0 && underMouse is KeyframePoint point) + var point = GetChildAt(location) as KeyframePoint; + if (selectionCount == 0 && point != null) { // Select node selectionCount = 1; point.IsSelected = true; + if (_editor.ShowCollapsed) + { + for (int i = 0; i < _editor._points.Count; i++) + { + var p = _editor._points[i]; + if (p.Index == point.Index) + p.IsSelected = point.IsSelected; + } + } _editor.UpdateTangents(); } @@ -459,11 +474,12 @@ namespace FlaxEditor.GUI cm.AddButton(selectionCount == 1 ? "Edit keyframe" : "Edit keyframes", () => _editor.EditKeyframes(this, location)); } var totalSelectionCount = _editor.KeyframesEditorContext?.OnKeyframesSelectionCount() ?? selectionCount; - Debug.Log(totalSelectionCount); if (totalSelectionCount > 0) { cm.AddButton(totalSelectionCount == 1 ? "Remove keyframe" : "Remove keyframes", _editor.RemoveKeyframes); + cm.AddButton(totalSelectionCount == 1 ? "Copy keyframe" : "Copy keyframes", () => _editor.CopyKeyframes(point)); } + cm.AddButton("Paste keyframes", () => KeyframesEditorUtils.Paste(_editor, point?.Time ?? _cmShowPos.X)).Enabled = KeyframesEditorUtils.CanPaste(); cm.AddButton("Edit all keyframes", () => _editor.EditAllKeyframes(this, location)); if (_editor.EnableZoom != UseMode.Off || _editor.EnablePanning != UseMode.Off) { @@ -568,5 +584,94 @@ namespace FlaxEditor.GUI else _contents.OnMove(location); } + + /// + public override void OnKeyframesCopy(IKeyframesEditor editor, float? timeOffset, StringBuilder data) + { + List selectedIndices = null; + for (int i = 0; i < _points.Count; i++) + { + var p = _points[i]; + if (p.IsSelected) + { + if (selectedIndices == null) + selectedIndices = new List(); + if (!selectedIndices.Contains(p.Index)) + selectedIndices.Add(p.Index); + } + } + if (selectedIndices == null) + return; + var offset = timeOffset ?? 0.0f; + data.AppendLine(KeyframesEditorUtils.CopyPrefix); + data.AppendLine(ValueType.FullName); + for (int i = 0; i < selectedIndices.Count; i++) + { + GetKeyframe(selectedIndices[i], out var time, out var value, out var tangentIn, out var tangentOut); + data.AppendLine((time + offset).ToString(CultureInfo.InvariantCulture)); + data.AppendLine(JsonSerializer.Serialize(value).Replace(Environment.NewLine, "")); + data.AppendLine(JsonSerializer.Serialize(tangentIn).Replace(Environment.NewLine, "")); + data.AppendLine(JsonSerializer.Serialize(tangentOut).Replace(Environment.NewLine, "")); + } + } + + /// + public override void OnKeyframesPaste(IKeyframesEditor editor, float? timeOffset, string[] datas, ref int index) + { + if (index == -1) + { + if (editor == this) + index = 0; + else + return; + } + else if (index >= datas.Length) + return; + var data = datas[index]; + var offset = timeOffset ?? 0.0f; + try + { + var lines = data.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries); + if (lines.Length < 4) + return; + var type = TypeUtils.GetManagedType(lines[0]); + if (type == null) + throw new Exception($"Unknown type {lines[0]}."); + if (type != DefaultValue.GetType()) + throw new Exception($"Mismatching keyframes data type {type.FullName} when pasting into {DefaultValue.GetType().FullName}."); + var count = (lines.Length - 1) / 4; + OnEditingStart(); + index++; + for (int i = 0; i < count; i++) + { + var time = float.Parse(lines[i * 4 + 1], CultureInfo.InvariantCulture) + offset; + var value = JsonSerializer.Deserialize(lines[i * 4 + 2], type); + var tangentIn = JsonSerializer.Deserialize(lines[i * 4 + 3], type); + var tangentOut = JsonSerializer.Deserialize(lines[i * 4 + 4], type); + if (FPS.HasValue) + { + float fps = FPS.Value; + time = Mathf.Floor(time * fps) / fps; + } + + var pos = AddKeyframe(time, value, tangentIn, tangentOut); + for (int j = 0; j < _points.Count; j++) + { + var p = _points[j]; + if (p.Index == pos) + p.IsSelected = true; + } + } + OnEditingEnd(); + UpdateKeyframes(); + UpdateTangents(); + } + catch (Exception ex) + { + Editor.LogWarning("Failed to paste keyframes."); + Editor.LogWarning(ex.Message); + Editor.LogWarning(ex); + } + } } } diff --git a/Source/Editor/GUI/CurveEditor.cs b/Source/Editor/GUI/CurveEditor.cs index ef42577b8..b4f1d9333 100644 --- a/Source/Editor/GUI/CurveEditor.cs +++ b/Source/Editor/GUI/CurveEditor.cs @@ -154,6 +154,11 @@ namespace FlaxEditor.GUI /// public Vector2 Point => Editor.GetKeyframePoint(Index, Component); + /// + /// Gets the time of the keyframe point. + /// + public float Time => Editor.GetKeyframeTime(Index); + /// public override void Draw() { @@ -483,7 +488,14 @@ namespace FlaxEditor.GUI public abstract void Evaluate(out T result, float time, bool loop = false); /// - /// Gets the time of the keyframe + /// Gets the time of the keyframe. + /// + /// The keyframe index. + /// The keyframe time. + protected abstract float GetKeyframeTime(int index); + + /// + /// Gets the time of the keyframe. /// /// The keyframe object. /// The keyframe time. @@ -613,6 +625,27 @@ namespace FlaxEditor.GUI RemoveKeyframesInner(); } + private void CopyKeyframes(KeyframePoint point = null) + { + float? timeOffset = null; + if (point != null) + { + timeOffset = -point.Time; + } + else + { + for (int i = 0; i < _points.Count; i++) + { + if (_points[i].IsSelected) + { + timeOffset = -_points[i].Time; + break; + } + } + } + KeyframesEditorUtils.Copy(this, timeOffset); + } + private void RemoveKeyframesInner() { if (SelectionCount == 0) @@ -906,6 +939,20 @@ namespace FlaxEditor.GUI return true; } break; + case KeyboardKeys.C: + if (Root.GetKey(KeyboardKeys.Control)) + { + CopyKeyframes(); + return true; + } + break; + case KeyboardKeys.V: + if (Root.GetKey(KeyboardKeys.Control)) + { + KeyframesEditorUtils.Paste(this); + return true; + } + break; } return false; } @@ -976,7 +1023,8 @@ namespace FlaxEditor.GUI /// Adds the new keyframe. /// /// The keyframe to add. - public void AddKeyframe(LinearCurve.Keyframe k) + /// The index of the keyframe. + public int AddKeyframe(LinearCurve.Keyframe k) { if (FPS.HasValue) { @@ -990,6 +1038,7 @@ namespace FlaxEditor.GUI OnKeyframesChanged(); OnEdited(); + return pos; } /// @@ -1098,6 +1147,12 @@ namespace FlaxEditor.GUI curve.Evaluate(out result, time, loop); } + /// + protected override float GetKeyframeTime(int index) + { + return _keyframes[index].Time; + } + /// protected override float GetKeyframeTime(object keyframe) { @@ -1220,9 +1275,15 @@ namespace FlaxEditor.GUI } /// - public override void AddKeyframe(float time, object value) + public override int AddKeyframe(float time, object value) { - AddKeyframe(new LinearCurve.Keyframe(time, (T)value)); + return AddKeyframe(new LinearCurve.Keyframe(time, (T)value)); + } + + /// + public override int AddKeyframe(float time, object value, object tangentIn, object tangentOut) + { + return AddKeyframe(new LinearCurve.Keyframe(time, (T)value)); } /// @@ -1449,7 +1510,8 @@ namespace FlaxEditor.GUI /// Adds the new keyframe. /// /// The keyframe to add. - public void AddKeyframe(BezierCurve.Keyframe k) + /// The index of the keyframe. + public int AddKeyframe(BezierCurve.Keyframe k) { if (FPS.HasValue) { @@ -1463,6 +1525,7 @@ namespace FlaxEditor.GUI OnKeyframesChanged(); OnEdited(); + return pos; } /// @@ -1760,6 +1823,12 @@ namespace FlaxEditor.GUI curve.Evaluate(out result, time, loop); } + /// + protected override float GetKeyframeTime(int index) + { + return _keyframes[index].Time; + } + /// protected override float GetKeyframeTime(object keyframe) { @@ -1892,9 +1961,15 @@ namespace FlaxEditor.GUI } /// - public override void AddKeyframe(float time, object value) + public override int AddKeyframe(float time, object value) { - AddKeyframe(new BezierCurve.Keyframe(time, (T)value)); + return AddKeyframe(new BezierCurve.Keyframe(time, (T)value)); + } + + /// + public override int AddKeyframe(float time, object value, object tangentIn, object tangentOut) + { + return AddKeyframe(new BezierCurve.Keyframe(time, (T)value, (T)tangentIn, (T)tangentOut)); } /// diff --git a/Source/Editor/GUI/IKeyframesEditor.cs b/Source/Editor/GUI/IKeyframesEditor.cs index b7e37e9f4..40ee390d6 100644 --- a/Source/Editor/GUI/IKeyframesEditor.cs +++ b/Source/Editor/GUI/IKeyframesEditor.cs @@ -50,5 +50,22 @@ namespace FlaxEditor.GUI /// The movement start flag. /// The movement end flag. void OnKeyframesMove(IKeyframesEditor editor, ContainerControl control, Vector2 location, bool start, bool end); + + /// + /// Called when keyframes selection should be copied. + /// + /// The source editor. + /// The additional time offset to apply to the copied keyframes (optional). + /// The result copy data text stream. + void OnKeyframesCopy(IKeyframesEditor editor, float? timeOffset, System.Text.StringBuilder data); + + /// + /// Called when keyframes should be pasted (from clipboard). + /// + /// The source editor. + /// The additional time offset to apply to the pasted keyframes (optional). + /// The pasted data text. + /// The counter for the current data index. Set to -1 until the calling editor starts paste operation. + void OnKeyframesPaste(IKeyframesEditor editor, float? timeOffset, string[] datas, ref int index); } } diff --git a/Source/Editor/GUI/IKeyframesEditorContext.cs b/Source/Editor/GUI/IKeyframesEditorContext.cs index d66a03b19..6f3cdfb5e 100644 --- a/Source/Editor/GUI/IKeyframesEditorContext.cs +++ b/Source/Editor/GUI/IKeyframesEditorContext.cs @@ -45,5 +45,22 @@ namespace FlaxEditor.GUI /// The movement start flag. /// The movement end flag. void OnKeyframesMove(IKeyframesEditor editor, ContainerControl control, Vector2 location, bool start, bool end); + + /// + /// Called when keyframes selection should be copied. + /// + /// The source editor. + /// The additional time offset to apply to the copied keyframes (optional). + /// The result copy data text stream. + void OnKeyframesCopy(IKeyframesEditor editor, float? timeOffset, System.Text.StringBuilder data); + + /// + /// Called when keyframes should be pasted (from clipboard). + /// + /// The source editor. + /// The additional time offset to apply to the pasted keyframes (optional). + /// The pasted data text. + /// The counter for the current data index. Set to -1 until the calling editor starts paste operation. + void OnKeyframesPaste(IKeyframesEditor editor, float? timeOffset, string[] datas, ref int index); } } diff --git a/Source/Editor/GUI/KeyframesEditorUtils.cs b/Source/Editor/GUI/KeyframesEditorUtils.cs new file mode 100644 index 000000000..e55a79fb1 --- /dev/null +++ b/Source/Editor/GUI/KeyframesEditorUtils.cs @@ -0,0 +1,50 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System; +using System.Text; +using FlaxEngine; + +namespace FlaxEditor.GUI +{ + /// + /// Utilities for and + /// + internal static class KeyframesEditorUtils + { + public const string CopyPrefix = "KEYFRAMES!?"; + + public static void Copy(IKeyframesEditor editor, float? timeOffset = null) + { + var data = new StringBuilder(); + if (editor.KeyframesEditorContext != null) + editor.KeyframesEditorContext.OnKeyframesCopy(editor, timeOffset, data); + else + editor.OnKeyframesCopy(editor, timeOffset, data); + Clipboard.Text = data.ToString(); + } + + public static bool CanPaste() + { + return Clipboard.Text.Contains(CopyPrefix); + } + + public static void Paste(IKeyframesEditor editor, float? timeOffset = null) + { + var data = Clipboard.Text; + if (string.IsNullOrEmpty(data)) + return; + var datas = data.Split(new[] { CopyPrefix }, StringSplitOptions.RemoveEmptyEntries); + if (datas.Length == 0) + return; + if (editor.KeyframesEditorContext != null) + editor.KeyframesEditorContext.OnKeyframesDeselect(editor); + else + editor.OnKeyframesDeselect(editor); + int index = -1; + if (editor.KeyframesEditorContext != null) + editor.KeyframesEditorContext.OnKeyframesPaste(editor, timeOffset, datas, ref index); + else + editor.OnKeyframesPaste(editor, timeOffset, datas, ref index); + } + } +} diff --git a/Source/Editor/GUI/Timeline/GUI/KeyframesEditor.cs b/Source/Editor/GUI/Timeline/GUI/KeyframesEditor.cs index fdfad2bfa..db96d9b37 100644 --- a/Source/Editor/GUI/Timeline/GUI/KeyframesEditor.cs +++ b/Source/Editor/GUI/Timeline/GUI/KeyframesEditor.cs @@ -2,11 +2,15 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; +using System.Text; using FlaxEditor.CustomEditors; using FlaxEditor.GUI.ContextMenu; +using FlaxEditor.Scripting; using FlaxEngine; using FlaxEngine.GUI; +using FlaxEngine.Json; namespace FlaxEditor.GUI { @@ -393,8 +397,8 @@ namespace FlaxEditor.GUI if (Vector2.Distance(ref location, ref _rightMouseDownPos) < 3.0f) { var selectionCount = _editor.SelectionCount; - var underMouse = GetChildAt(location); - if (selectionCount == 0 && underMouse is KeyframePoint point) + var point = GetChildAt(location) as KeyframePoint; + if (selectionCount == 0 && point != null) { // Select node selectionCount = 1; @@ -411,11 +415,12 @@ namespace FlaxEditor.GUI cm.AddButton(selectionCount == 1 ? "Edit keyframe" : "Edit keyframes", () => _editor.EditKeyframes(this, location)); } var totalSelectionCount = _editor.KeyframesEditorContext?.OnKeyframesSelectionCount() ?? selectionCount; - Debug.Log(totalSelectionCount); if (totalSelectionCount > 0) { cm.AddButton(totalSelectionCount == 1 ? "Remove keyframe" : "Remove keyframes", _editor.RemoveKeyframes); + cm.AddButton(totalSelectionCount == 1 ? "Copy keyframe" : "Copy keyframes", () => _editor.CopyKeyframes(point)); } + cm.AddButton("Paste keyframes", () => KeyframesEditorUtils.Paste(_editor, point?.Time ?? _cmShowPos.X)).Enabled = KeyframesEditorUtils.CanPaste(); cm.AddButton("Edit all keyframes", () => _editor.EditAllKeyframes(this, location)); if (_editor.EnableZoom && _editor.EnablePanning) { @@ -499,6 +504,11 @@ namespace FlaxEditor.GUI /// public bool IsSelected; + /// + /// Gets the time of the keyframe. + /// + public float Time => Editor._keyframes[Index].Time; + /// public override void Draw() { @@ -1028,6 +1038,27 @@ namespace FlaxEditor.GUI RemoveKeyframesInner(); } + private void CopyKeyframes(KeyframePoint point = null) + { + float? timeOffset = null; + if (point != null) + { + timeOffset = -point.Time; + } + else + { + for (int i = 0; i < _points.Count; i++) + { + if (_points[i].IsSelected) + { + timeOffset = -_points[i].Time; + break; + } + } + } + KeyframesEditorUtils.Copy(this, timeOffset); + } + private void RemoveKeyframesInner() { if (SelectionCount == 0) @@ -1218,6 +1249,20 @@ namespace FlaxEditor.GUI return true; } break; + case KeyboardKeys.C: + if (Root.GetKey(KeyboardKeys.Control)) + { + CopyKeyframes(); + return true; + } + break; + case KeyboardKeys.V: + if (Root.GetKey(KeyboardKeys.Control)) + { + KeyframesEditorUtils.Paste(this); + return true; + } + break; } return false; } @@ -1279,5 +1324,93 @@ namespace FlaxEditor.GUI else _contents.OnMove(location); } + + /// + public void OnKeyframesCopy(IKeyframesEditor editor, float? timeOffset, StringBuilder data) + { + if (SelectionCount == 0) + return; + var offset = timeOffset ?? 0.0f; + data.AppendLine(KeyframesEditorUtils.CopyPrefix); + data.AppendLine(DefaultValue.GetType().FullName); + for (int i = 0; i < _keyframes.Count; i++) + { + if (!_points[i].IsSelected) + continue; + var k = _keyframes[i]; + data.AppendLine((k.Time + offset).ToString(CultureInfo.InvariantCulture)); + data.AppendLine(JsonSerializer.Serialize(k.Value).Replace(Environment.NewLine, "")); + } + } + + /// + public void OnKeyframesPaste(IKeyframesEditor editor, float? timeOffset, string[] datas, ref int index) + { + if (index == -1) + { + if (editor == this) + index = 0; + else + return; + } + else if (index >= datas.Length) + return; + var data = datas[index]; + var offset = timeOffset ?? 0.0f; + var eps = FPS.HasValue ? 1.0f / FPS.Value : Mathf.Epsilon; + try + { + var lines = data.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries); + if (lines.Length < 3 || lines.Length % 2 == 0) + return; + var type = TypeUtils.GetManagedType(lines[0]); + if (type == null) + throw new Exception($"Unknown type {lines[0]}."); + if (type != DefaultValue.GetType()) + throw new Exception($"Mismatching keyframes data type {type.FullName} when pasting into {DefaultValue.GetType().FullName}."); + var count = (lines.Length - 1) / 2; + var modified = false; + index++; + for (int i = 0; i < count; i++) + { + var k = new Keyframe + { + Time = float.Parse(lines[i * 2 + 1], CultureInfo.InvariantCulture) + offset, + Value = JsonSerializer.Deserialize(lines[i * 2 + 2], type), + }; + if (FPS.HasValue) + { + float fps = FPS.Value; + k.Time = Mathf.Floor(k.Time * fps) / fps; + } + int pos = 0; + while (pos < _keyframes.Count && _keyframes[pos].Time < k.Time) + pos++; + if (_keyframes.Count > pos && Mathf.Abs(_keyframes[pos].Time - k.Time) < eps) + { + // Skip if the keyframe value won't change + if (JsonSerializer.ValueEquals(_keyframes[pos].Value, k.Value)) + continue; + } + + if (!modified) + { + modified = true; + OnEditingStart(); + } + + AddKeyframe(k); + _points[pos].IsSelected = true; + } + if (modified) + OnEditingEnd(); + } + catch (Exception ex) + { + Editor.LogWarning("Failed to paste keyframes."); + Editor.LogWarning(ex.Message); + Editor.LogWarning(ex); + } + } } } diff --git a/Source/Editor/GUI/Timeline/Timeline.cs b/Source/Editor/GUI/Timeline/Timeline.cs index 3b3efa062..e87c797b6 100644 --- a/Source/Editor/GUI/Timeline/Timeline.cs +++ b/Source/Editor/GUI/Timeline/Timeline.cs @@ -2570,5 +2570,39 @@ namespace FlaxEditor.GUI.Timeline trackContext.OnKeyframesMove(editor, _backgroundArea, location, start, end); } } + + /// + public void OnKeyframesCopy(IKeyframesEditor editor, float? timeOffset, System.Text.StringBuilder data) + { + var area = _backgroundArea; + var hScroll = area.HScrollBar.Visible && area.HScrollBar.Enabled; + if (hScroll && !timeOffset.HasValue) + { + // Offset copied keyframes relative to the current view start + timeOffset = (area.HScrollBar.Value - StartOffset * 2.0f) / (UnitsPerSecond * Zoom); + } + for (int i = 0; i < _tracks.Count; i++) + { + if (_tracks[i] is IKeyframesEditorContext trackContext) + trackContext.OnKeyframesCopy(editor, timeOffset, data); + } + } + + /// + public void OnKeyframesPaste(IKeyframesEditor editor, float? timeOffset, string[] datas, ref int index) + { + var area = _backgroundArea; + var hScroll = area.HScrollBar.Visible && area.HScrollBar.Enabled; + if (hScroll && !timeOffset.HasValue) + { + // Offset pasted keyframes relative to the current view start + timeOffset = (area.HScrollBar.Value - StartOffset * 2.0f) / (UnitsPerSecond * Zoom); + } + for (int i = 0; i < _tracks.Count; i++) + { + if (_tracks[i] is IKeyframesEditorContext trackContext) + trackContext.OnKeyframesPaste(editor, timeOffset, datas, ref index); + } + } } } diff --git a/Source/Editor/GUI/Timeline/Tracks/AudioTrack.cs b/Source/Editor/GUI/Timeline/Tracks/AudioTrack.cs index 8e5159ed2..e1ddac28c 100644 --- a/Source/Editor/GUI/Timeline/Tracks/AudioTrack.cs +++ b/Source/Editor/GUI/Timeline/Tracks/AudioTrack.cs @@ -688,5 +688,19 @@ namespace FlaxEditor.GUI.Timeline.Tracks if (Curve != null && Curve.Visible) Curve.OnKeyframesMove(editor, control, location, start, end); } + + /// + public void OnKeyframesCopy(IKeyframesEditor editor, float? timeOffset, System.Text.StringBuilder data) + { + if (Curve != null && Curve.Visible) + Curve.OnKeyframesCopy(editor, timeOffset, data); + } + + /// + public void OnKeyframesPaste(IKeyframesEditor editor, float? timeOffset, string[] datas, ref int index) + { + if (Curve != null && Curve.Visible) + Curve.OnKeyframesPaste(editor, timeOffset, datas, ref index); + } } } diff --git a/Source/Editor/GUI/Timeline/Tracks/CurvePropertyTrack.cs b/Source/Editor/GUI/Timeline/Tracks/CurvePropertyTrack.cs index 1bfc18f9b..fb88d6957 100644 --- a/Source/Editor/GUI/Timeline/Tracks/CurvePropertyTrack.cs +++ b/Source/Editor/GUI/Timeline/Tracks/CurvePropertyTrack.cs @@ -464,6 +464,20 @@ namespace FlaxEditor.GUI.Timeline.Tracks if (Curve != null && Curve.Visible) Curve.OnKeyframesMove(editor, control, location, start, end); } + + /// + public void OnKeyframesCopy(IKeyframesEditor editor, float? timeOffset, StringBuilder data) + { + if (Curve != null && Curve.Visible) + Curve.OnKeyframesCopy(editor, timeOffset, data); + } + + /// + public void OnKeyframesPaste(IKeyframesEditor editor, float? timeOffset, string[] datas, ref int index) + { + if (Curve != null && Curve.Visible) + Curve.OnKeyframesPaste(editor, timeOffset, datas, ref index); + } } /// diff --git a/Source/Editor/GUI/Timeline/Tracks/EventTrack.cs b/Source/Editor/GUI/Timeline/Tracks/EventTrack.cs index 3e9e0fefb..b5f94bda2 100644 --- a/Source/Editor/GUI/Timeline/Tracks/EventTrack.cs +++ b/Source/Editor/GUI/Timeline/Tracks/EventTrack.cs @@ -445,5 +445,19 @@ namespace FlaxEditor.GUI.Timeline.Tracks if (Events != null && Events.Visible) Events.OnKeyframesMove(editor, control, location, start, end); } + + /// + public void OnKeyframesCopy(IKeyframesEditor editor, float? timeOffset, StringBuilder data) + { + if (Events != null && Events.Visible) + Events.OnKeyframesCopy(editor, timeOffset, data); + } + + /// + public void OnKeyframesPaste(IKeyframesEditor editor, float? timeOffset, string[] datas, ref int index) + { + if (Events != null && Events.Visible) + Events.OnKeyframesPaste(editor, timeOffset, datas, ref index); + } } } diff --git a/Source/Editor/GUI/Timeline/Tracks/KeyframesPropertyTrack.cs b/Source/Editor/GUI/Timeline/Tracks/KeyframesPropertyTrack.cs index c78f96fea..d76f6c1fd 100644 --- a/Source/Editor/GUI/Timeline/Tracks/KeyframesPropertyTrack.cs +++ b/Source/Editor/GUI/Timeline/Tracks/KeyframesPropertyTrack.cs @@ -425,5 +425,19 @@ namespace FlaxEditor.GUI.Timeline.Tracks if (Keyframes != null && Keyframes.Visible) Keyframes.OnKeyframesMove(editor, control, location, start, end); } + + /// + public void OnKeyframesCopy(IKeyframesEditor editor, float? timeOffset, StringBuilder data) + { + if (Keyframes != null && Keyframes.Visible) + Keyframes.OnKeyframesCopy(editor, timeOffset, data); + } + + /// + public void OnKeyframesPaste(IKeyframesEditor editor, float? timeOffset, string[] datas, ref int index) + { + if (Keyframes != null && Keyframes.Visible) + Keyframes.OnKeyframesPaste(editor, timeOffset, datas, ref index); + } } }