Add copy/paste feature to keyframes and curves editors

#519
This commit is contained in:
Wojtek Figat
2021-09-01 14:42:02 +02:00
parent d062601260
commit 0d5fa3e125
12 changed files with 519 additions and 15 deletions

View File

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

View File

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

View File

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

View File

@@ -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>

View File

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

View File

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