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