Add keyframes editor to proxy keyframes from subtracks on object track

#519
This commit is contained in:
Wojtek Figat
2021-09-03 12:32:33 +02:00
parent 6dcc9f85c6
commit d19d0ef0e5
21 changed files with 648 additions and 49 deletions

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System; using System;
using System.Collections.Generic;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.GUI; using FlaxEngine.GUI;
@@ -296,5 +297,11 @@ namespace FlaxEditor.GUI
/// <inheritdoc /> /// <inheritdoc />
public abstract void OnKeyframesPaste(IKeyframesEditor editor, float? timeOffset, string[] datas, ref int index); public abstract void OnKeyframesPaste(IKeyframesEditor editor, float? timeOffset, string[] datas, ref int index);
/// <inheritdoc />
public abstract void OnKeyframesGet(string trackName, Action<string, float, object> get);
/// <inheritdoc />
public abstract void OnKeyframesSet(List<KeyValuePair<float, object>> keyframes);
} }
} }

View File

@@ -615,9 +615,9 @@ namespace FlaxEditor.GUI
{ {
GetKeyframe(selectedIndices[i], out var time, out var value, out var tangentIn, out var tangentOut); GetKeyframe(selectedIndices[i], out var time, out var value, out var tangentIn, out var tangentOut);
data.AppendLine((time + offset).ToString(CultureInfo.InvariantCulture)); data.AppendLine((time + offset).ToString(CultureInfo.InvariantCulture));
data.AppendLine(JsonSerializer.Serialize(value).Replace(Environment.NewLine, "")); data.AppendLine(JsonSerializer.Serialize(value).RemoveNewLine());
data.AppendLine(JsonSerializer.Serialize(tangentIn).Replace(Environment.NewLine, "")); data.AppendLine(JsonSerializer.Serialize(tangentIn).RemoveNewLine());
data.AppendLine(JsonSerializer.Serialize(tangentOut).Replace(Environment.NewLine, "")); data.AppendLine(JsonSerializer.Serialize(tangentOut).RemoveNewLine());
} }
} }

View File

@@ -1326,6 +1326,34 @@ namespace FlaxEditor.GUI
return new Vector2(k.Time, Accessor.GetCurveValue(ref k.Value, component)); return new Vector2(k.Time, Accessor.GetCurveValue(ref k.Value, component));
} }
/// <inheritdoc />
public override void OnKeyframesGet(string trackName, Action<string, float, object> get)
{
for (int i = 0; i < _keyframes.Count; i++)
{
var k = _keyframes[i];
get(trackName, k.Time, k);
}
}
/// <inheritdoc />
public override void OnKeyframesSet(List<KeyValuePair<float, object>> keyframes)
{
OnEditingStart();
_keyframes.Clear();
if (keyframes != null)
{
foreach (var e in keyframes)
{
var k = (LinearCurve<T>.Keyframe)e.Value;
_keyframes.Add(new LinearCurve<T>.Keyframe(e.Key, k.Value));
}
}
OnKeyframesChanged();
OnEdited();
OnEditingEnd();
}
/// <inheritdoc /> /// <inheritdoc />
public override int KeyframesCount => _keyframes.Count; public override int KeyframesCount => _keyframes.Count;
@@ -2019,6 +2047,34 @@ namespace FlaxEditor.GUI
return new Vector2(k.Time, Accessor.GetCurveValue(ref k.Value, component)); return new Vector2(k.Time, Accessor.GetCurveValue(ref k.Value, component));
} }
/// <inheritdoc />
public override void OnKeyframesGet(string trackName, Action<string, float, object> get)
{
for (int i = 0; i < _keyframes.Count; i++)
{
var k = _keyframes[i];
get(trackName, k.Time, k);
}
}
/// <inheritdoc />
public override void OnKeyframesSet(List<KeyValuePair<float, object>> keyframes)
{
OnEditingStart();
_keyframes.Clear();
if (keyframes != null)
{
foreach (var e in keyframes)
{
var k = (BezierCurve<T>.Keyframe)e.Value;
_keyframes.Add(new BezierCurve<T>.Keyframe(e.Key, k.Value, k.TangentIn, k.TangentOut));
}
}
OnKeyframesChanged();
OnEdited();
OnEditingEnd();
}
/// <inheritdoc /> /// <inheritdoc />
public override int KeyframesCount => _keyframes.Count; public override int KeyframesCount => _keyframes.Count;

View File

@@ -1,5 +1,7 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.GUI; using FlaxEngine.GUI;
@@ -67,5 +69,18 @@ namespace FlaxEditor.GUI
/// <param name="datas">The pasted data text.</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> /// <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); void OnKeyframesPaste(IKeyframesEditor editor, float? timeOffset, string[] datas, ref int index);
/// <summary>
/// Called when collecting keyframes from the context.
/// </summary>
/// <param name="trackName">The name of the track.</param>
/// <param name="get">The getter function to call for all keyframes. Args are: track name, keyframe time, keyframe object.</param>
void OnKeyframesGet(string trackName, Action<string, float, object> get);
/// <summary>
/// Called when setting keyframes data (opposite to <see cref="OnKeyframesGet"/>).
/// </summary>
/// <param name="keyframes">The list of keyframes, null for empty list. Keyframe data is: time and keyframe object.</param>
void OnKeyframesSet(List<KeyValuePair<float, object>> keyframes);
} }
} }

View File

@@ -1,5 +1,7 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.GUI; using FlaxEngine.GUI;
@@ -62,5 +64,17 @@ namespace FlaxEditor.GUI
/// <param name="datas">The pasted data text.</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> /// <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); void OnKeyframesPaste(IKeyframesEditor editor, float? timeOffset, string[] datas, ref int index);
/// <summary>
/// Called when collecting keyframes from the context.
/// </summary>
/// <param name="get">The getter function to call for all keyframes. Args are: track name, keyframe time, keyframe object.</param>
void OnKeyframesGet(Action<string, float, object> get);
/// <summary>
/// Called when setting keyframes data (opposite to <see cref="OnKeyframesGet"/>).
/// </summary>
/// <param name="keyframes">The list of keyframes, null for empty list. Keyframe data is: time and keyframe object.</param>
void OnKeyframesSet(List<KeyValuePair<float, object>> keyframes);
} }
} }

View File

@@ -62,6 +62,12 @@ namespace FlaxEditor.GUI
{ {
return Time > other.Time ? 1 : 0; return Time > other.Time ? 1 : 0;
} }
/// <inheritdoc />
public override string ToString()
{
return Value?.ToString() ?? string.Empty;
}
} }
/// <summary> /// <summary>
@@ -409,8 +415,8 @@ namespace FlaxEditor.GUI
_cmShowPos = PointToKeyframes(location, ref viewRect); _cmShowPos = PointToKeyframes(location, ref viewRect);
var cm = new ContextMenu.ContextMenu(); var cm = new ContextMenu.ContextMenu();
cm.AddButton("Add keyframe", () => _editor.AddKeyframe(_cmShowPos)).Enabled = _editor.Keyframes.Count < _editor.MaxKeyframes; cm.AddButton("Add keyframe", () => _editor.AddKeyframe(_cmShowPos)).Enabled = _editor.Keyframes.Count < _editor.MaxKeyframes && _editor.DefaultValue != null;
if (selectionCount > 0) if (selectionCount > 0 && _editor.EnableKeyframesValueEdit)
{ {
cm.AddButton(selectionCount == 1 ? "Edit keyframe" : "Edit keyframes", () => _editor.EditKeyframes(this, location)); cm.AddButton(selectionCount == 1 ? "Edit keyframe" : "Edit keyframes", () => _editor.EditKeyframes(this, location));
} }
@@ -421,13 +427,14 @@ namespace FlaxEditor.GUI
cm.AddButton(totalSelectionCount == 1 ? "Copy keyframe" : "Copy keyframes", () => _editor.CopyKeyframes(point)); 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("Paste keyframes", () => KeyframesEditorUtils.Paste(_editor, point?.Time ?? _cmShowPos.X)).Enabled = KeyframesEditorUtils.CanPaste();
cm.AddButton("Edit all keyframes", () => _editor.EditAllKeyframes(this, location)); if (_editor.EnableKeyframesValueEdit)
cm.AddButton("Select all keyframes", _editor.SelectAll); cm.AddButton("Edit all keyframes", () => _editor.EditAllKeyframes(this, location));
cm.AddButton("Select all keyframes", _editor.SelectAll).Enabled = _editor._points.Count > 0;
cm.AddButton("Copy all keyframes", () => cm.AddButton("Copy all keyframes", () =>
{ {
_editor.SelectAll(); _editor.SelectAll();
_editor.CopyKeyframes(point); _editor.CopyKeyframes(point);
}); }).Enabled = _editor.DefaultValue != null;
if (_editor.EnableZoom && _editor.EnablePanning) if (_editor.EnableZoom && _editor.EnablePanning)
{ {
cm.AddSeparator(); cm.AddSeparator();
@@ -645,6 +652,16 @@ namespace FlaxEditor.GUI
/// </summary> /// </summary>
public bool EnablePanning = true; public bool EnablePanning = true;
/// <summary>
/// True if enable keyframes values editing (via popup).
/// </summary>
public bool EnableKeyframesValueEdit = true;
/// <summary>
/// True if enable view panning. Otherwise user won't be able to move the view area.
/// </summary>
public bool Enable = true;
/// <summary> /// <summary>
/// Gets a value indicating whether user is editing the keyframes. /// Gets a value indicating whether user is editing the keyframes.
/// </summary> /// </summary>
@@ -1353,7 +1370,7 @@ namespace FlaxEditor.GUI
continue; continue;
var k = _keyframes[i]; var k = _keyframes[i];
data.AppendLine((k.Time + offset).ToString(CultureInfo.InvariantCulture)); data.AppendLine((k.Time + offset).ToString(CultureInfo.InvariantCulture));
data.AppendLine(JsonSerializer.Serialize(k.Value).Replace(Environment.NewLine, "")); data.AppendLine(JsonSerializer.Serialize(k.Value).RemoveNewLine());
} }
} }
@@ -1428,5 +1445,33 @@ namespace FlaxEditor.GUI
Editor.LogWarning(ex); Editor.LogWarning(ex);
} }
} }
/// <inheritdoc />
public void OnKeyframesGet(string trackName, Action<string, float, object> get)
{
for (int i = 0; i < _keyframes.Count; i++)
{
var k = _keyframes[i];
get(trackName, k.Time, k);
}
}
/// <inheritdoc />
public void OnKeyframesSet(List<KeyValuePair<float, object>> keyframes)
{
OnEditingStart();
_keyframes.Clear();
if (keyframes != null)
{
foreach (var e in keyframes)
{
var k = (Keyframe)e.Value;
_keyframes.Add(new Keyframe(e.Key, k.Value));
}
}
OnKeyframesChanged();
OnEdited();
OnEditingEnd();
}
} }
} }

View File

@@ -2137,5 +2137,20 @@ namespace FlaxEditor.GUI.Timeline
trackContext.OnKeyframesPaste(editor, timeOffset, datas, ref index); trackContext.OnKeyframesPaste(editor, timeOffset, datas, ref index);
} }
} }
/// <inheritdoc />
public void OnKeyframesGet(Action<string, float, object> get)
{
for (int i = 0; i < _tracks.Count; i++)
{
if (_tracks[i] is IKeyframesEditorContext trackContext)
trackContext.OnKeyframesGet(get);
}
}
/// <inheritdoc />
public void OnKeyframesSet(List<KeyValuePair<float, object>> keyframes)
{
}
} }
} }

View File

@@ -65,9 +65,13 @@ namespace FlaxEditor.GUI.Timeline.Tracks
set => ActorID = value?.ID ?? Guid.Empty; set => ActorID = value?.ID ?? Guid.Empty;
} }
/// <inheritdoc /> /// <summary>
public ActorTrack(ref TrackCreateOptions options) /// Initializes a new instance of the <see cref="ActorTrack"/> class.
: base(ref options) /// </summary>
/// <param name="options">The track initial options.</param>
/// <param name="useProxyKeyframes">True if show sub-tracks keyframes as a proxy on this track, otherwise false.</param>
public ActorTrack(ref TrackCreateOptions options, bool useProxyKeyframes = true)
: base(ref options, useProxyKeyframes)
{ {
// Select Actor button // Select Actor button
const float buttonSize = 18; const float buttonSize = 18;

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using FlaxEditor.GUI.Timeline.Undo; using FlaxEditor.GUI.Timeline.Undo;
using FlaxEditor.Viewport.Previews; using FlaxEditor.Viewport.Previews;
@@ -476,10 +477,12 @@ namespace FlaxEditor.GUI.Timeline.Tracks
{ {
if (_audioMedia == null || Curve == null || Timeline == null) if (_audioMedia == null || Curve == null || Timeline == null)
return; return;
bool wasVisible = Curve.Visible;
Curve.Visible = Visible; Curve.Visible = Visible;
if (!Visible) if (!Visible)
{ {
Curve.ClearSelection(); if (wasVisible)
Curve.ClearSelection();
return; return;
} }
Curve.KeyframesEditorContext = Timeline; Curve.KeyframesEditorContext = Timeline;
@@ -702,5 +705,17 @@ namespace FlaxEditor.GUI.Timeline.Tracks
if (Curve != null && Curve.Visible) if (Curve != null && Curve.Visible)
Curve.OnKeyframesPaste(editor, timeOffset, datas, ref index); Curve.OnKeyframesPaste(editor, timeOffset, datas, ref index);
} }
/// <inheritdoc />
public void OnKeyframesGet(Action<string, float, object> get)
{
Curve?.OnKeyframesGet(Name, get);
}
/// <inheritdoc />
public void OnKeyframesSet(List<KeyValuePair<float, object>> keyframes)
{
Curve?.OnKeyframesSet(keyframes);
}
} }
} }

View File

@@ -728,7 +728,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
/// <inheritdoc /> /// <inheritdoc />
public CameraCutTrack(ref TrackCreateOptions options) public CameraCutTrack(ref TrackCreateOptions options)
: base(ref options) : base(ref options, false)
{ {
Height = CameraCutThumbnailRenderer.Height + 8; Height = CameraCutThumbnailRenderer.Height + 8;

View File

@@ -0,0 +1,356 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.GUI.Timeline.Tracks
{
/// <summary>
/// The timeline track that can proxy the sub-tracks keyframes for easier batched editing.
/// </summary>
/// <seealso cref="FlaxEditor.GUI.Timeline.Track" />
public abstract class ConductorTrack : Track, IKeyframesEditorContext
{
private sealed class ConductionUndoAction : IUndoAction
{
public bool IsDo;
public Timeline Timeline;
public string Track;
private void Refresh()
{
Refresh(Timeline.FindTrack(Track));
}
private void Refresh(Track track)
{
if (track is ConductorTrack conductor && conductor.Proxy != null)
{
conductor.LoadProxy();
foreach (var subTrack in conductor.SubTracks)
Refresh(subTrack);
}
}
public string ActionString => "Edit track";
public void Do()
{
if (IsDo)
Refresh();
}
public void Undo()
{
if (!IsDo)
Refresh();
}
public void Dispose()
{
Timeline = null;
Track = null;
}
}
private HashSet<string> _proxyTracks;
/// <summary>
/// The batched keyframes editor for all sub-tracks.
/// </summary>
protected KeyframesEditor Proxy;
/// <summary>
/// Initializes a new instance of the <see cref="ConductorTrack"/> class.
/// </summary>
/// <param name="options">The track initial options.</param>
/// <param name="useProxyKeyframes">True if show sub-tracks keyframes as a proxy on this track, otherwise false.</param>
protected ConductorTrack(ref TrackCreateOptions options, bool useProxyKeyframes = true)
: base(ref options)
{
if (useProxyKeyframes)
{
// Proxy keyframes editor
Proxy = new KeyframesEditor
{
EnableZoom = false,
EnablePanning = false,
EnableKeyframesValueEdit = false,
DefaultValue = new ProxyKey(),
ScrollBars = ScrollBars.None,
};
Proxy.Edited += OnProxyEdited;
Proxy.EditingEnd += OnProxyEditingEnd;
Proxy.UnlockChildrenRecursive();
}
}
private void UpdateProxyKeyframes()
{
if (Proxy == null || Timeline == null)
return;
bool wasVisible = Proxy.Visible;
Proxy.Visible = Visible && IsCollapsed && SubTracks.Any(x => x is IKeyframesEditorContext);
if (!Visible)
{
if (wasVisible)
Proxy.ClearSelection();
return;
}
Proxy.KeyframesEditorContext = Timeline;
Proxy.CustomViewPanning = Timeline.OnKeyframesViewPanning;
Proxy.Bounds = new Rectangle(Timeline.StartOffset, Y + 1.0f, Timeline.Duration * Timeline.UnitsPerSecond * Timeline.Zoom, Height - 2.0f);
Proxy.ViewScale = new Vector2(Timeline.Zoom, 1.0f);
if (!wasVisible)
LoadProxy();
else
Proxy.UpdateKeyframes();
}
private void OnProxyEdited()
{
Timeline.MarkAsEdited();
}
private void OnProxyEditingEnd()
{
SaveProxy();
}
/// <summary>
/// The proxy key data.
/// </summary>
public struct ProxyKey
{
/// <summary>
/// The keyframes.
/// </summary>
[EditorDisplay("Keyframes", EditorDisplayAttribute.InlineStyle), ExpandGroups]
[Collection(CanReorderItems = false, ReadOnly = true)]
public List<KeyValuePair<string, object>> Keyframes;
/// <inheritdoc />
public override string ToString()
{
if (Keyframes == null || Keyframes.Count == 0)
return string.Empty;
var sb = new StringBuilder();
for (int i = 0; i < Keyframes.Count; i++)
{
if (i != 0)
sb.Append(", ");
var k = Keyframes[i];
if (k.Value != null)
sb.Append(k.Value);
}
return sb.ToString();
}
}
private void LoadProxy()
{
if (_proxyTracks == null)
_proxyTracks = new HashSet<string>();
else
_proxyTracks.Clear();
// Build keyframes list that contains keys from all tracks to proxy
var keyframes = new List<KeyframesEditor.Keyframe>();
var eps = Proxy.FPS.HasValue ? 0.5f / Proxy.FPS.Value : Mathf.Epsilon;
Action<string, float, object> getter = (trackName, time, value) =>
{
_proxyTracks.Add(trackName);
int idx = keyframes.FindIndex(x => Mathf.Abs(x.Time - time) <= eps);
if (idx == -1)
{
idx = keyframes.Count;
keyframes.Add(new KeyframesEditor.Keyframe(time, new ProxyKey { Keyframes = new List<KeyValuePair<string, object>>() }));
}
((ProxyKey)keyframes[idx].Value).Keyframes.Add(new KeyValuePair<string, object>(trackName, value));
};
foreach (var subTrack in SubTracks)
{
var trackContext = subTrack as IKeyframesEditorContext;
trackContext?.OnKeyframesGet(getter);
}
Proxy.SetKeyframes(keyframes);
}
private void SaveProxy()
{
// Collect keyframes list per track
var perTrackKeyframes = new Dictionary<string, List<KeyValuePair<float, object>>>();
var keyframes = Proxy.Keyframes;
foreach (var keyframe in keyframes)
{
var proxy = (ProxyKey)keyframe.Value;
if (proxy.Keyframes == null)
continue;
foreach (var e in proxy.Keyframes)
{
if (!perTrackKeyframes.TryGetValue(e.Key, out var trackKeyframes))
{
trackKeyframes = new List<KeyValuePair<float, object>>();
perTrackKeyframes.Add(e.Key, trackKeyframes);
}
trackKeyframes.Add(new KeyValuePair<float, object>(keyframe.Time, e.Value));
}
}
// Apply the new keyframes to all tracks in this proxy
Timeline.AddBatchedUndoAction(new ConductionUndoAction { IsDo = false, Timeline = Timeline, Track = Name });
foreach (var trackName in _proxyTracks)
{
var track = Timeline.FindTrack(trackName);
if (track == null)
{
Editor.LogWarning(string.Format("Failed to find track {0} for keyframes proxy on track {1}.", trackName, Title ?? Name));
continue;
}
var trackContext = track as IKeyframesEditorContext;
if (trackContext == null)
{
Editor.LogWarning(string.Format("Track {0} used keyframes proxy on track {1} is invalid.", track.Title ?? track.Name, Title ?? Name));
continue;
}
perTrackKeyframes.TryGetValue(trackName, out var trackKeyframes);
trackContext.OnKeyframesSet(trackKeyframes);
}
Timeline.AddBatchedUndoAction(new ConductionUndoAction { IsDo = true, Timeline = Timeline, Track = Name });
}
/// <inheritdoc />
protected override void OnVisibleChanged()
{
base.OnVisibleChanged();
UpdateProxyKeyframes();
}
/// <inheritdoc />
protected override void OnExpandedChanged()
{
base.OnExpandedChanged();
UpdateProxyKeyframes();
}
/// <inheritdoc />
public override void OnTimelineChanged(Timeline timeline)
{
base.OnTimelineChanged(timeline);
if (Proxy != null)
{
Proxy.Parent = timeline?.MediaPanel;
Proxy.FPS = timeline?.FramesPerSecond;
UpdateProxyKeyframes();
}
}
/// <inheritdoc />
public override void OnTimelineZoomChanged()
{
base.OnTimelineZoomChanged();
UpdateProxyKeyframes();
}
/// <inheritdoc />
public override void OnTimelineArrange()
{
base.OnTimelineArrange();
UpdateProxyKeyframes();
}
/// <inheritdoc />
public override void OnTimelineFpsChanged(float before, float after)
{
base.OnTimelineFpsChanged(before, after);
Proxy.FPS = after;
}
/// <inheritdoc />
public override void OnDestroy()
{
if (Proxy != null)
{
Proxy.Dispose();
Proxy = null;
_proxyTracks = null;
}
base.OnDestroy();
}
/// <inheritdoc />
public void OnKeyframesDeselect(IKeyframesEditor editor)
{
if (Proxy != null && Proxy.Visible)
Proxy.OnKeyframesDeselect(editor);
}
/// <inheritdoc />
public void OnKeyframesSelection(IKeyframesEditor editor, ContainerControl control, Rectangle selection)
{
if (Proxy != null && Proxy.Visible)
Proxy.OnKeyframesSelection(editor, control, selection);
}
/// <inheritdoc />
public int OnKeyframesSelectionCount()
{
return Proxy != null && Proxy.Visible ? Proxy.OnKeyframesSelectionCount() : 0;
}
/// <inheritdoc />
public void OnKeyframesDelete(IKeyframesEditor editor)
{
if (Proxy != null && Proxy.Visible)
Proxy.OnKeyframesDelete(editor);
}
/// <inheritdoc />
public void OnKeyframesMove(IKeyframesEditor editor, ContainerControl control, Vector2 location, bool start, bool end)
{
if (Proxy != null && Proxy.Visible)
Proxy.OnKeyframesMove(editor, control, location, start, end);
}
/// <inheritdoc />
public void OnKeyframesCopy(IKeyframesEditor editor, float? timeOffset, StringBuilder data)
{
if (Proxy != null && Proxy.Visible)
Proxy.OnKeyframesCopy(editor, timeOffset, data);
}
/// <inheritdoc />
public void OnKeyframesPaste(IKeyframesEditor editor, float? timeOffset, string[] datas, ref int index)
{
if (Proxy != null && Proxy.Visible)
Proxy.OnKeyframesPaste(editor, timeOffset, datas, ref index);
}
/// <inheritdoc />
public void OnKeyframesGet(Action<string, float, object> get)
{
foreach (var subTrack in SubTracks)
{
if (subTrack is IKeyframesEditorContext trackContext)
trackContext.OnKeyframesGet(get);
}
}
/// <inheritdoc />
public void OnKeyframesSet(List<KeyValuePair<float, object>> keyframes)
{
}
}
}

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Reflection; using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@@ -224,10 +225,12 @@ namespace FlaxEditor.GUI.Timeline.Tracks
{ {
if (Curve == null || Timeline == null) if (Curve == null || Timeline == null)
return; return;
bool wasVisible = Curve.Visible;
Curve.Visible = Visible; Curve.Visible = Visible;
if (!Visible) if (!Visible)
{ {
Curve.ClearSelection(); if (wasVisible)
Curve.ClearSelection();
return; return;
} }
var expanded = IsExpanded; var expanded = IsExpanded;
@@ -432,52 +435,64 @@ namespace FlaxEditor.GUI.Timeline.Tracks
} }
/// <inheritdoc /> /// <inheritdoc />
public void OnKeyframesDeselect(IKeyframesEditor editor) public new void OnKeyframesDeselect(IKeyframesEditor editor)
{ {
if (Curve != null && Curve.Visible) if (Curve != null && Curve.Visible)
Curve.OnKeyframesDeselect(editor); Curve.OnKeyframesDeselect(editor);
} }
/// <inheritdoc /> /// <inheritdoc />
public void OnKeyframesSelection(IKeyframesEditor editor, ContainerControl control, Rectangle selection) public new void OnKeyframesSelection(IKeyframesEditor editor, ContainerControl control, Rectangle selection)
{ {
if (Curve != null && Curve.Visible) if (Curve != null && Curve.Visible)
Curve.OnKeyframesSelection(editor, control, selection); Curve.OnKeyframesSelection(editor, control, selection);
} }
/// <inheritdoc /> /// <inheritdoc />
public int OnKeyframesSelectionCount() public new int OnKeyframesSelectionCount()
{ {
return Curve != null && Curve.Visible ? Curve.OnKeyframesSelectionCount() : 0; return Curve != null && Curve.Visible ? Curve.OnKeyframesSelectionCount() : 0;
} }
/// <inheritdoc /> /// <inheritdoc />
public void OnKeyframesDelete(IKeyframesEditor editor) public new void OnKeyframesDelete(IKeyframesEditor editor)
{ {
if (Curve != null && Curve.Visible) if (Curve != null && Curve.Visible)
Curve.OnKeyframesDelete(editor); Curve.OnKeyframesDelete(editor);
} }
/// <inheritdoc /> /// <inheritdoc />
public void OnKeyframesMove(IKeyframesEditor editor, ContainerControl control, Vector2 location, bool start, bool end) public new void OnKeyframesMove(IKeyframesEditor editor, ContainerControl control, Vector2 location, bool start, bool end)
{ {
if (Curve != null && Curve.Visible) if (Curve != null && Curve.Visible)
Curve.OnKeyframesMove(editor, control, location, start, end); Curve.OnKeyframesMove(editor, control, location, start, end);
} }
/// <inheritdoc /> /// <inheritdoc />
public void OnKeyframesCopy(IKeyframesEditor editor, float? timeOffset, StringBuilder data) public new void OnKeyframesCopy(IKeyframesEditor editor, float? timeOffset, StringBuilder data)
{ {
if (Curve != null && Curve.Visible) if (Curve != null && Curve.Visible)
Curve.OnKeyframesCopy(editor, timeOffset, data); Curve.OnKeyframesCopy(editor, timeOffset, data);
} }
/// <inheritdoc /> /// <inheritdoc />
public void OnKeyframesPaste(IKeyframesEditor editor, float? timeOffset, string[] datas, ref int index) public new void OnKeyframesPaste(IKeyframesEditor editor, float? timeOffset, string[] datas, ref int index)
{ {
if (Curve != null && Curve.Visible) if (Curve != null && Curve.Visible)
Curve.OnKeyframesPaste(editor, timeOffset, datas, ref index); Curve.OnKeyframesPaste(editor, timeOffset, datas, ref index);
} }
/// <inheritdoc />
public new void OnKeyframesGet(Action<string, float, object> get)
{
Curve?.OnKeyframesGet(Name, get);
}
/// <inheritdoc />
public new void OnKeyframesSet(List<KeyValuePair<float, object>> keyframes)
{
Curve?.OnKeyframesSet(keyframes);
}
} }
/// <summary> /// <summary>

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
@@ -50,7 +51,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
{ {
e.EventParamsSizes[i] = stream.ReadInt32(); e.EventParamsSizes[i] = stream.ReadInt32();
var paramTypeName = LoadName(stream); var paramTypeName = LoadName(stream);
e.EventParamsTypes[i] = Scripting.TypeUtils.GetType(paramTypeName).Type; e.EventParamsTypes[i] = Scripting.TypeUtils.GetManagedType(paramTypeName);
if (e.EventParamsTypes[i] == null) if (e.EventParamsTypes[i] == null)
isInvalid = true; isInvalid = true;
} }
@@ -283,9 +284,11 @@ namespace FlaxEditor.GUI.Timeline.Tracks
{ {
if (Events == null || Timeline == null) if (Events == null || Timeline == null)
return; return;
bool wasVisible = Events.Visible;
Events.Visible = Visible; Events.Visible = Visible;
if (!Visible) if (!Visible)
{ {
if(wasVisible)
Events.ClearSelection(); Events.ClearSelection();
return; return;
} }
@@ -413,51 +416,63 @@ namespace FlaxEditor.GUI.Timeline.Tracks
} }
/// <inheritdoc /> /// <inheritdoc />
public void OnKeyframesDeselect(IKeyframesEditor editor) public new void OnKeyframesDeselect(IKeyframesEditor editor)
{ {
if (Events != null && Events.Visible) if (Events != null && Events.Visible)
Events.OnKeyframesDeselect(editor); Events.OnKeyframesDeselect(editor);
} }
/// <inheritdoc /> /// <inheritdoc />
public void OnKeyframesSelection(IKeyframesEditor editor, ContainerControl control, Rectangle selection) public new void OnKeyframesSelection(IKeyframesEditor editor, ContainerControl control, Rectangle selection)
{ {
if (Events != null && Events.Visible) if (Events != null && Events.Visible)
Events.OnKeyframesSelection(editor, control, selection); Events.OnKeyframesSelection(editor, control, selection);
} }
/// <inheritdoc /> /// <inheritdoc />
public int OnKeyframesSelectionCount() public new int OnKeyframesSelectionCount()
{ {
return Events != null && Events.Visible ? Events.OnKeyframesSelectionCount() : 0; return Events != null && Events.Visible ? Events.OnKeyframesSelectionCount() : 0;
} }
/// <inheritdoc /> /// <inheritdoc />
public void OnKeyframesDelete(IKeyframesEditor editor) public new void OnKeyframesDelete(IKeyframesEditor editor)
{ {
if (Events != null && Events.Visible) if (Events != null && Events.Visible)
Events.OnKeyframesDelete(editor); Events.OnKeyframesDelete(editor);
} }
/// <inheritdoc /> /// <inheritdoc />
public void OnKeyframesMove(IKeyframesEditor editor, ContainerControl control, Vector2 location, bool start, bool end) public new void OnKeyframesMove(IKeyframesEditor editor, ContainerControl control, Vector2 location, bool start, bool end)
{ {
if (Events != null && Events.Visible) if (Events != null && Events.Visible)
Events.OnKeyframesMove(editor, control, location, start, end); Events.OnKeyframesMove(editor, control, location, start, end);
} }
/// <inheritdoc /> /// <inheritdoc />
public void OnKeyframesCopy(IKeyframesEditor editor, float? timeOffset, StringBuilder data) public new void OnKeyframesCopy(IKeyframesEditor editor, float? timeOffset, StringBuilder data)
{ {
if (Events != null && Events.Visible) if (Events != null && Events.Visible)
Events.OnKeyframesCopy(editor, timeOffset, data); Events.OnKeyframesCopy(editor, timeOffset, data);
} }
/// <inheritdoc /> /// <inheritdoc />
public void OnKeyframesPaste(IKeyframesEditor editor, float? timeOffset, string[] datas, ref int index) public new void OnKeyframesPaste(IKeyframesEditor editor, float? timeOffset, string[] datas, ref int index)
{ {
if (Events != null && Events.Visible) if (Events != null && Events.Visible)
Events.OnKeyframesPaste(editor, timeOffset, datas, ref index); Events.OnKeyframesPaste(editor, timeOffset, datas, ref index);
} }
/// <inheritdoc />
public new void OnKeyframesGet(Action<string, float, object> get)
{
Events?.OnKeyframesGet(Name, get);
}
/// <inheritdoc />
public new void OnKeyframesSet(List<KeyValuePair<float, object>> keyframes)
{
Events?.OnKeyframesSet(keyframes);
}
} }
} }

View File

@@ -12,7 +12,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
/// The timeline track that represents a folder used to group and organize tracks. /// The timeline track that represents a folder used to group and organize tracks.
/// </summary> /// </summary>
/// <seealso cref="FlaxEditor.GUI.Timeline.Track" /> /// <seealso cref="FlaxEditor.GUI.Timeline.Track" />
public class FolderTrack : Track public class FolderTrack : ConductorTrack
{ {
/// <summary> /// <summary>
/// Gets the archetype. /// Gets the archetype.

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Reflection; using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@@ -55,7 +56,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
var keyframes = new KeyframesEditor.Keyframe[keyframesCount]; var keyframes = new KeyframesEditor.Keyframe[keyframesCount];
var dataBuffer = new byte[e.ValueSize]; var dataBuffer = new byte[e.ValueSize];
var propertyType = Scripting.TypeUtils.GetType(e.MemberTypeName).Type; var propertyType = Scripting.TypeUtils.GetManagedType(e.MemberTypeName);
if (propertyType == null) if (propertyType == null)
{ {
e.Keyframes.ResetKeyframes(); e.Keyframes.ResetKeyframes();
@@ -250,10 +251,12 @@ namespace FlaxEditor.GUI.Timeline.Tracks
{ {
if (Keyframes == null || Timeline == null) if (Keyframes == null || Timeline == null)
return; return;
bool wasVisible = Keyframes.Visible;
Keyframes.Visible = Visible; Keyframes.Visible = Visible;
if (!Visible) if (!Visible)
{ {
Keyframes.ClearSelection(); if (wasVisible)
Keyframes.ClearSelection();
return; return;
} }
Keyframes.KeyframesEditorContext = Timeline; Keyframes.KeyframesEditorContext = Timeline;
@@ -393,51 +396,63 @@ namespace FlaxEditor.GUI.Timeline.Tracks
} }
/// <inheritdoc /> /// <inheritdoc />
public void OnKeyframesDeselect(IKeyframesEditor editor) public new void OnKeyframesDeselect(IKeyframesEditor editor)
{ {
if (Keyframes != null && Keyframes.Visible) if (Keyframes != null && Keyframes.Visible)
Keyframes.OnKeyframesDeselect(editor); Keyframes.OnKeyframesDeselect(editor);
} }
/// <inheritdoc /> /// <inheritdoc />
public void OnKeyframesSelection(IKeyframesEditor editor, ContainerControl control, Rectangle selection) public new void OnKeyframesSelection(IKeyframesEditor editor, ContainerControl control, Rectangle selection)
{ {
if (Keyframes != null && Keyframes.Visible) if (Keyframes != null && Keyframes.Visible)
Keyframes.OnKeyframesSelection(editor, control, selection); Keyframes.OnKeyframesSelection(editor, control, selection);
} }
/// <inheritdoc /> /// <inheritdoc />
public int OnKeyframesSelectionCount() public new int OnKeyframesSelectionCount()
{ {
return Keyframes != null && Keyframes.Visible ? Keyframes.OnKeyframesSelectionCount() : 0; return Keyframes != null && Keyframes.Visible ? Keyframes.OnKeyframesSelectionCount() : 0;
} }
/// <inheritdoc /> /// <inheritdoc />
public void OnKeyframesDelete(IKeyframesEditor editor) public new void OnKeyframesDelete(IKeyframesEditor editor)
{ {
if (Keyframes != null && Keyframes.Visible) if (Keyframes != null && Keyframes.Visible)
Keyframes.OnKeyframesDelete(editor); Keyframes.OnKeyframesDelete(editor);
} }
/// <inheritdoc /> /// <inheritdoc />
public void OnKeyframesMove(IKeyframesEditor editor, ContainerControl control, Vector2 location, bool start, bool end) public new void OnKeyframesMove(IKeyframesEditor editor, ContainerControl control, Vector2 location, bool start, bool end)
{ {
if (Keyframes != null && Keyframes.Visible) if (Keyframes != null && Keyframes.Visible)
Keyframes.OnKeyframesMove(editor, control, location, start, end); Keyframes.OnKeyframesMove(editor, control, location, start, end);
} }
/// <inheritdoc /> /// <inheritdoc />
public void OnKeyframesCopy(IKeyframesEditor editor, float? timeOffset, StringBuilder data) public new void OnKeyframesCopy(IKeyframesEditor editor, float? timeOffset, StringBuilder data)
{ {
if (Keyframes != null && Keyframes.Visible) if (Keyframes != null && Keyframes.Visible)
Keyframes.OnKeyframesCopy(editor, timeOffset, data); Keyframes.OnKeyframesCopy(editor, timeOffset, data);
} }
/// <inheritdoc /> /// <inheritdoc />
public void OnKeyframesPaste(IKeyframesEditor editor, float? timeOffset, string[] datas, ref int index) public new void OnKeyframesPaste(IKeyframesEditor editor, float? timeOffset, string[] datas, ref int index)
{ {
if (Keyframes != null && Keyframes.Visible) if (Keyframes != null && Keyframes.Visible)
Keyframes.OnKeyframesPaste(editor, timeOffset, datas, ref index); Keyframes.OnKeyframesPaste(editor, timeOffset, datas, ref index);
} }
/// <inheritdoc />
public new void OnKeyframesGet(Action<string, float, object> get)
{
Keyframes?.OnKeyframesGet(Name, get);
}
/// <inheritdoc />
public new void OnKeyframesSet(List<KeyValuePair<float, object>> keyframes)
{
Keyframes?.OnKeyframesSet(keyframes);
}
} }
} }

View File

@@ -13,7 +13,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
/// The timeline track for animating object member (managed object). /// The timeline track for animating object member (managed object).
/// </summary> /// </summary>
/// <seealso cref="FlaxEditor.GUI.Timeline.Track" /> /// <seealso cref="FlaxEditor.GUI.Timeline.Track" />
public abstract class MemberTrack : Track public abstract class MemberTrack : ConductorTrack
{ {
private float _previewValueLeft; private float _previewValueLeft;
@@ -112,8 +112,9 @@ namespace FlaxEditor.GUI.Timeline.Tracks
/// <param name="options">The track initial options.</param> /// <param name="options">The track initial options.</param>
/// <param name="useNavigationButtons">True if show keyframe navigation buttons, otherwise false.</param> /// <param name="useNavigationButtons">True if show keyframe navigation buttons, otherwise false.</param>
/// <param name="useValuePreview">True if show current value preview, otherwise false.</param> /// <param name="useValuePreview">True if show current value preview, otherwise false.</param>
protected MemberTrack(ref TrackCreateOptions options, bool useNavigationButtons = true, bool useValuePreview = true) /// <param name="useProxyKeyframes">True if show sub-tracks keyframes as a proxy on this track, otherwise false.</param>
: base(ref options) protected MemberTrack(ref TrackCreateOptions options, bool useNavigationButtons = true, bool useValuePreview = true, bool useProxyKeyframes = false)
: base(ref options, useProxyKeyframes)
{ {
var uiLeft = _muteCheckbox.Offsets.Left; var uiLeft = _muteCheckbox.Offsets.Left;

View File

@@ -86,7 +86,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
/// <inheritdoc /> /// <inheritdoc />
public ObjectPropertyTrack(ref TrackCreateOptions options) public ObjectPropertyTrack(ref TrackCreateOptions options)
: base(ref options, false, false) : base(ref options, false, false, true)
{ {
// Add track button // Add track button
const float buttonSize = 14; const float buttonSize = 14;

View File

@@ -28,7 +28,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
/// The timeline track for animating managed objects. /// The timeline track for animating managed objects.
/// </summary> /// </summary>
/// <seealso cref="FlaxEditor.GUI.Timeline.Track" /> /// <seealso cref="FlaxEditor.GUI.Timeline.Track" />
public abstract class ObjectTrack : Track, IObjectTrack public abstract class ObjectTrack : ConductorTrack, IObjectTrack
{ {
private bool _hasObject; private bool _hasObject;
@@ -42,9 +42,13 @@ namespace FlaxEditor.GUI.Timeline.Tracks
/// </summary> /// </summary>
public abstract object Object { get; } public abstract object Object { get; }
/// <inheritdoc /> /// <summary>
protected ObjectTrack(ref TrackCreateOptions options) /// Initializes a new instance of the <see cref="ObjectTrack"/> class.
: base(ref options) /// </summary>
/// <param name="options">The track initial options.</param>
/// <param name="useProxyKeyframes">True if show sub-tracks keyframes as a proxy on this track, otherwise false.</param>
protected ObjectTrack(ref TrackCreateOptions options, bool useProxyKeyframes = true)
: base(ref options, useProxyKeyframes)
{ {
// Add track button // Add track button
const float buttonSize = 14; const float buttonSize = 14;

View File

@@ -87,7 +87,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
/// <inheritdoc /> /// <inheritdoc />
public StructPropertyTrack(ref TrackCreateOptions options) public StructPropertyTrack(ref TrackCreateOptions options)
: base(ref options, false, false) : base(ref options, false, false, true)
{ {
// Add track button // Add track button
const float buttonSize = 14; const float buttonSize = 14;

View File

@@ -378,6 +378,12 @@ namespace FlaxEngine
{ {
return Time > other.Time ? 1 : 0; return Time > other.Time ? 1 : 0;
} }
/// <inheritdoc />
public override string ToString()
{
return Value?.ToString() ?? string.Empty;
}
} }
/// <summary> /// <summary>
@@ -655,6 +661,12 @@ namespace FlaxEngine
{ {
return Time > other.Time ? 1 : 0; return Time > other.Time ? 1 : 0;
} }
/// <inheritdoc />
public override string ToString()
{
return Value?.ToString() ?? string.Empty;
}
} }
/// <summary> /// <summary>

View File

@@ -254,6 +254,16 @@ namespace FlaxEngine
return string.Concat(graphemes); return string.Concat(graphemes);
} }
/// <summary>
/// Removes any new line characters (\r or \n) from the string.
/// </summary>
/// <param name="s">The string to process.</param>
/// <returns>The single-line string.</returns>
public static string RemoveNewLine(this string s)
{
return s.Replace("\n", "").Replace("\r", "");
}
private static readonly Regex IncNameRegex1 = new Regex("(\\d+)$"); private static readonly Regex IncNameRegex1 = new Regex("(\\d+)$");
private static readonly Regex IncNameRegex2 = new Regex("\\((\\d+)\\)$"); private static readonly Regex IncNameRegex2 = new Regex("\\((\\d+)\\)$");