@@ -202,8 +202,19 @@ namespace FlaxEditor.GUI
|
||||
/// Adds the new keyframe (as boxed object).
|
||||
/// </summary>
|
||||
/// <param name="time">The keyframe time.</param>
|
||||
/// <param name="value">The keyframe value.</param>
|
||||
public abstract void AddKeyframe(float time, object value);
|
||||
/// <param name="value">The keyframe value (boxed).</param>
|
||||
/// <returns>The index of the keyframe.</returns>
|
||||
public abstract int AddKeyframe(float time, object value);
|
||||
|
||||
/// <summary>
|
||||
/// Adds the new keyframe (as boxed object).
|
||||
/// </summary>
|
||||
/// <param name="time">The keyframe time.</param>
|
||||
/// <param name="value">The keyframe value (boxed).</param>
|
||||
/// <param name="tangentIn">The keyframe 'In' tangent value (boxed).</param>
|
||||
/// <param name="tangentOut">The keyframe 'Out' tangent value (boxed).</param>
|
||||
/// <returns>The index of the keyframe.</returns>
|
||||
public abstract int AddKeyframe(float time, object value, object tangentIn, object tangentOut);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the keyframe data (as boxed objects).
|
||||
@@ -279,5 +290,11 @@ namespace FlaxEditor.GUI
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract void OnKeyframesMove(IKeyframesEditor editor, ContainerControl control, Vector2 location, bool start, bool end);
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract void OnKeyframesCopy(IKeyframesEditor editor, float? timeOffset, System.Text.StringBuilder data);
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract void OnKeyframesPaste(IKeyframesEditor editor, float? timeOffset, string[] datas, ref int index);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnKeyframesCopy(IKeyframesEditor editor, float? timeOffset, StringBuilder data)
|
||||
{
|
||||
List<int> selectedIndices = null;
|
||||
for (int i = 0; i < _points.Count; i++)
|
||||
{
|
||||
var p = _points[i];
|
||||
if (p.IsSelected)
|
||||
{
|
||||
if (selectedIndices == null)
|
||||
selectedIndices = new List<int>();
|
||||
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, ""));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,6 +154,11 @@ namespace FlaxEditor.GUI
|
||||
/// </summary>
|
||||
public Vector2 Point => Editor.GetKeyframePoint(Index, Component);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the time of the keyframe point.
|
||||
/// </summary>
|
||||
public float Time => Editor.GetKeyframeTime(Index);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Draw()
|
||||
{
|
||||
@@ -483,7 +488,14 @@ namespace FlaxEditor.GUI
|
||||
public abstract void Evaluate(out T result, float time, bool loop = false);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the time of the keyframe
|
||||
/// Gets the time of the keyframe.
|
||||
/// </summary>
|
||||
/// <param name="index">The keyframe index.</param>
|
||||
/// <returns>The keyframe time.</returns>
|
||||
protected abstract float GetKeyframeTime(int index);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the time of the keyframe.
|
||||
/// </summary>
|
||||
/// <param name="keyframe">The keyframe object.</param>
|
||||
/// <returns>The keyframe time.</returns>
|
||||
@@ -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.
|
||||
/// </summary>
|
||||
/// <param name="k">The keyframe to add.</param>
|
||||
public void AddKeyframe(LinearCurve<T>.Keyframe k)
|
||||
/// <returns>The index of the keyframe.</returns>
|
||||
public int AddKeyframe(LinearCurve<T>.Keyframe k)
|
||||
{
|
||||
if (FPS.HasValue)
|
||||
{
|
||||
@@ -990,6 +1038,7 @@ namespace FlaxEditor.GUI
|
||||
|
||||
OnKeyframesChanged();
|
||||
OnEdited();
|
||||
return pos;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1098,6 +1147,12 @@ namespace FlaxEditor.GUI
|
||||
curve.Evaluate(out result, time, loop);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override float GetKeyframeTime(int index)
|
||||
{
|
||||
return _keyframes[index].Time;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override float GetKeyframeTime(object keyframe)
|
||||
{
|
||||
@@ -1220,9 +1275,15 @@ namespace FlaxEditor.GUI
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void AddKeyframe(float time, object value)
|
||||
public override int AddKeyframe(float time, object value)
|
||||
{
|
||||
AddKeyframe(new LinearCurve<T>.Keyframe(time, (T)value));
|
||||
return AddKeyframe(new LinearCurve<T>.Keyframe(time, (T)value));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int AddKeyframe(float time, object value, object tangentIn, object tangentOut)
|
||||
{
|
||||
return AddKeyframe(new LinearCurve<T>.Keyframe(time, (T)value));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -1449,7 +1510,8 @@ namespace FlaxEditor.GUI
|
||||
/// Adds the new keyframe.
|
||||
/// </summary>
|
||||
/// <param name="k">The keyframe to add.</param>
|
||||
public void AddKeyframe(BezierCurve<T>.Keyframe k)
|
||||
/// <returns>The index of the keyframe.</returns>
|
||||
public int AddKeyframe(BezierCurve<T>.Keyframe k)
|
||||
{
|
||||
if (FPS.HasValue)
|
||||
{
|
||||
@@ -1463,6 +1525,7 @@ namespace FlaxEditor.GUI
|
||||
|
||||
OnKeyframesChanged();
|
||||
OnEdited();
|
||||
return pos;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1760,6 +1823,12 @@ namespace FlaxEditor.GUI
|
||||
curve.Evaluate(out result, time, loop);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override float GetKeyframeTime(int index)
|
||||
{
|
||||
return _keyframes[index].Time;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override float GetKeyframeTime(object keyframe)
|
||||
{
|
||||
@@ -1892,9 +1961,15 @@ namespace FlaxEditor.GUI
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void AddKeyframe(float time, object value)
|
||||
public override int AddKeyframe(float time, object value)
|
||||
{
|
||||
AddKeyframe(new BezierCurve<T>.Keyframe(time, (T)value));
|
||||
return AddKeyframe(new BezierCurve<T>.Keyframe(time, (T)value));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int AddKeyframe(float time, object value, object tangentIn, object tangentOut)
|
||||
{
|
||||
return AddKeyframe(new BezierCurve<T>.Keyframe(time, (T)value, (T)tangentIn, (T)tangentOut));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -50,5 +50,22 @@ namespace FlaxEditor.GUI
|
||||
/// <param name="start">The movement start flag.</param>
|
||||
/// <param name="end">The movement end flag.</param>
|
||||
void OnKeyframesMove(IKeyframesEditor editor, ContainerControl control, Vector2 location, bool start, bool end);
|
||||
|
||||
/// <summary>
|
||||
/// Called when keyframes selection should be copied.
|
||||
/// </summary>
|
||||
/// <param name="editor">The source editor.</param>
|
||||
/// <param name="timeOffset">The additional time offset to apply to the copied keyframes (optional).</param>
|
||||
/// <param name="data">The result copy data text stream.</param>
|
||||
void OnKeyframesCopy(IKeyframesEditor editor, float? timeOffset, System.Text.StringBuilder data);
|
||||
|
||||
/// <summary>
|
||||
/// Called when keyframes should be pasted (from clipboard).
|
||||
/// </summary>
|
||||
/// <param name="editor">The source editor.</param>
|
||||
/// <param name="timeOffset">The additional time offset to apply to the pasted keyframes (optional).</param>
|
||||
/// <param name="datas">The pasted data text.</param>
|
||||
/// <param name="index">The counter for the current data index. Set to -1 until the calling editor starts paste operation.</param>
|
||||
void OnKeyframesPaste(IKeyframesEditor editor, float? timeOffset, string[] datas, ref int index);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,5 +45,22 @@ namespace FlaxEditor.GUI
|
||||
/// <param name="start">The movement start flag.</param>
|
||||
/// <param name="end">The movement end flag.</param>
|
||||
void OnKeyframesMove(IKeyframesEditor editor, ContainerControl control, Vector2 location, bool start, bool end);
|
||||
|
||||
/// <summary>
|
||||
/// Called when keyframes selection should be copied.
|
||||
/// </summary>
|
||||
/// <param name="editor">The source editor.</param>
|
||||
/// <param name="timeOffset">The additional time offset to apply to the copied keyframes (optional).</param>
|
||||
/// <param name="data">The result copy data text stream.</param>
|
||||
void OnKeyframesCopy(IKeyframesEditor editor, float? timeOffset, System.Text.StringBuilder data);
|
||||
|
||||
/// <summary>
|
||||
/// Called when keyframes should be pasted (from clipboard).
|
||||
/// </summary>
|
||||
/// <param name="editor">The source editor.</param>
|
||||
/// <param name="timeOffset">The additional time offset to apply to the pasted keyframes (optional).</param>
|
||||
/// <param name="datas">The pasted data text.</param>
|
||||
/// <param name="index">The counter for the current data index. Set to -1 until the calling editor starts paste operation.</param>
|
||||
void OnKeyframesPaste(IKeyframesEditor editor, float? timeOffset, string[] datas, ref int index);
|
||||
}
|
||||
}
|
||||
|
||||
50
Source/Editor/GUI/KeyframesEditorUtils.cs
Normal file
50
Source/Editor/GUI/KeyframesEditorUtils.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Text;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.GUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Utilities for <see cref="IKeyframesEditor"/> and <see cref="IKeyframesEditorContext"/>
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
/// </summary>
|
||||
public bool IsSelected;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the time of the keyframe.
|
||||
/// </summary>
|
||||
public float Time => Editor._keyframes[Index].Time;
|
||||
|
||||
/// <inheritdoc />
|
||||
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);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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, ""));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2570,5 +2570,39 @@ namespace FlaxEditor.GUI.Timeline
|
||||
trackContext.OnKeyframesMove(editor, _backgroundArea, location, start, end);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -688,5 +688,19 @@ namespace FlaxEditor.GUI.Timeline.Tracks
|
||||
if (Curve != null && Curve.Visible)
|
||||
Curve.OnKeyframesMove(editor, control, location, start, end);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OnKeyframesCopy(IKeyframesEditor editor, float? timeOffset, System.Text.StringBuilder data)
|
||||
{
|
||||
if (Curve != null && Curve.Visible)
|
||||
Curve.OnKeyframesCopy(editor, timeOffset, data);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OnKeyframesPaste(IKeyframesEditor editor, float? timeOffset, string[] datas, ref int index)
|
||||
{
|
||||
if (Curve != null && Curve.Visible)
|
||||
Curve.OnKeyframesPaste(editor, timeOffset, datas, ref index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -464,6 +464,20 @@ namespace FlaxEditor.GUI.Timeline.Tracks
|
||||
if (Curve != null && Curve.Visible)
|
||||
Curve.OnKeyframesMove(editor, control, location, start, end);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OnKeyframesCopy(IKeyframesEditor editor, float? timeOffset, StringBuilder data)
|
||||
{
|
||||
if (Curve != null && Curve.Visible)
|
||||
Curve.OnKeyframesCopy(editor, timeOffset, data);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OnKeyframesPaste(IKeyframesEditor editor, float? timeOffset, string[] datas, ref int index)
|
||||
{
|
||||
if (Curve != null && Curve.Visible)
|
||||
Curve.OnKeyframesPaste(editor, timeOffset, datas, ref index);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -445,5 +445,19 @@ namespace FlaxEditor.GUI.Timeline.Tracks
|
||||
if (Events != null && Events.Visible)
|
||||
Events.OnKeyframesMove(editor, control, location, start, end);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OnKeyframesCopy(IKeyframesEditor editor, float? timeOffset, StringBuilder data)
|
||||
{
|
||||
if (Events != null && Events.Visible)
|
||||
Events.OnKeyframesCopy(editor, timeOffset, data);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OnKeyframesPaste(IKeyframesEditor editor, float? timeOffset, string[] datas, ref int index)
|
||||
{
|
||||
if (Events != null && Events.Visible)
|
||||
Events.OnKeyframesPaste(editor, timeOffset, datas, ref index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -425,5 +425,19 @@ namespace FlaxEditor.GUI.Timeline.Tracks
|
||||
if (Keyframes != null && Keyframes.Visible)
|
||||
Keyframes.OnKeyframesMove(editor, control, location, start, end);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OnKeyframesCopy(IKeyframesEditor editor, float? timeOffset, StringBuilder data)
|
||||
{
|
||||
if (Keyframes != null && Keyframes.Visible)
|
||||
Keyframes.OnKeyframesCopy(editor, timeOffset, data);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OnKeyframesPaste(IKeyframesEditor editor, float? timeOffset, string[] datas, ref int index)
|
||||
{
|
||||
if (Keyframes != null && Keyframes.Visible)
|
||||
Keyframes.OnKeyframesPaste(editor, timeOffset, datas, ref index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user