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.
using System;
using System.Collections.Generic;
using FlaxEngine;
using FlaxEngine.GUI;
@@ -296,5 +297,11 @@ namespace FlaxEditor.GUI
/// <inheritdoc />
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);
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, ""));
data.AppendLine(JsonSerializer.Serialize(value).RemoveNewLine());
data.AppendLine(JsonSerializer.Serialize(tangentIn).RemoveNewLine());
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));
}
/// <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 />
public override int KeyframesCount => _keyframes.Count;
@@ -2019,6 +2047,34 @@ namespace FlaxEditor.GUI
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 />
public override int KeyframesCount => _keyframes.Count;

View File

@@ -1,5 +1,7 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using FlaxEngine;
using FlaxEngine.GUI;
@@ -67,5 +69,18 @@ namespace FlaxEditor.GUI
/// <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);
/// <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.
using System;
using System.Collections.Generic;
using FlaxEngine;
using FlaxEngine.GUI;
@@ -62,5 +64,17 @@ namespace FlaxEditor.GUI
/// <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);
/// <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;
}
/// <inheritdoc />
public override string ToString()
{
return Value?.ToString() ?? string.Empty;
}
}
/// <summary>
@@ -409,8 +415,8 @@ namespace FlaxEditor.GUI
_cmShowPos = PointToKeyframes(location, ref viewRect);
var cm = new ContextMenu.ContextMenu();
cm.AddButton("Add keyframe", () => _editor.AddKeyframe(_cmShowPos)).Enabled = _editor.Keyframes.Count < _editor.MaxKeyframes;
if (selectionCount > 0)
cm.AddButton("Add keyframe", () => _editor.AddKeyframe(_cmShowPos)).Enabled = _editor.Keyframes.Count < _editor.MaxKeyframes && _editor.DefaultValue != null;
if (selectionCount > 0 && _editor.EnableKeyframesValueEdit)
{
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("Paste keyframes", () => KeyframesEditorUtils.Paste(_editor, point?.Time ?? _cmShowPos.X)).Enabled = KeyframesEditorUtils.CanPaste();
cm.AddButton("Edit all keyframes", () => _editor.EditAllKeyframes(this, location));
cm.AddButton("Select all keyframes", _editor.SelectAll);
if (_editor.EnableKeyframesValueEdit)
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", () =>
{
_editor.SelectAll();
_editor.CopyKeyframes(point);
});
}).Enabled = _editor.DefaultValue != null;
if (_editor.EnableZoom && _editor.EnablePanning)
{
cm.AddSeparator();
@@ -645,6 +652,16 @@ namespace FlaxEditor.GUI
/// </summary>
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>
/// Gets a value indicating whether user is editing the keyframes.
/// </summary>
@@ -1353,7 +1370,7 @@ namespace FlaxEditor.GUI
continue;
var k = _keyframes[i];
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);
}
}
/// <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);
}
}
/// <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;
}
/// <inheritdoc />
public ActorTrack(ref TrackCreateOptions options)
: base(ref options)
/// <summary>
/// Initializes a new instance of the <see cref="ActorTrack"/> 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>
public ActorTrack(ref TrackCreateOptions options, bool useProxyKeyframes = true)
: base(ref options, useProxyKeyframes)
{
// Select Actor button
const float buttonSize = 18;

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using System.IO;
using FlaxEditor.GUI.Timeline.Undo;
using FlaxEditor.Viewport.Previews;
@@ -476,10 +477,12 @@ namespace FlaxEditor.GUI.Timeline.Tracks
{
if (_audioMedia == null || Curve == null || Timeline == null)
return;
bool wasVisible = Curve.Visible;
Curve.Visible = Visible;
if (!Visible)
{
Curve.ClearSelection();
if (wasVisible)
Curve.ClearSelection();
return;
}
Curve.KeyframesEditorContext = Timeline;
@@ -702,5 +705,17 @@ namespace FlaxEditor.GUI.Timeline.Tracks
if (Curve != null && Curve.Visible)
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 />
public CameraCutTrack(ref TrackCreateOptions options)
: base(ref options)
: base(ref options, false)
{
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.
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
@@ -224,10 +225,12 @@ namespace FlaxEditor.GUI.Timeline.Tracks
{
if (Curve == null || Timeline == null)
return;
bool wasVisible = Curve.Visible;
Curve.Visible = Visible;
if (!Visible)
{
Curve.ClearSelection();
if (wasVisible)
Curve.ClearSelection();
return;
}
var expanded = IsExpanded;
@@ -432,52 +435,64 @@ namespace FlaxEditor.GUI.Timeline.Tracks
}
/// <inheritdoc />
public void OnKeyframesDeselect(IKeyframesEditor editor)
public new void OnKeyframesDeselect(IKeyframesEditor editor)
{
if (Curve != null && Curve.Visible)
Curve.OnKeyframesDeselect(editor);
}
/// <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)
Curve.OnKeyframesSelection(editor, control, selection);
}
/// <inheritdoc />
public int OnKeyframesSelectionCount()
public new int OnKeyframesSelectionCount()
{
return Curve != null && Curve.Visible ? Curve.OnKeyframesSelectionCount() : 0;
}
/// <inheritdoc />
public void OnKeyframesDelete(IKeyframesEditor editor)
public new void OnKeyframesDelete(IKeyframesEditor editor)
{
if (Curve != null && Curve.Visible)
Curve.OnKeyframesDelete(editor);
}
/// <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)
Curve.OnKeyframesMove(editor, control, location, start, end);
}
/// <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)
Curve.OnKeyframesCopy(editor, timeOffset, data);
}
/// <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)
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>

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
@@ -50,7 +51,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
{
e.EventParamsSizes[i] = stream.ReadInt32();
var paramTypeName = LoadName(stream);
e.EventParamsTypes[i] = Scripting.TypeUtils.GetType(paramTypeName).Type;
e.EventParamsTypes[i] = Scripting.TypeUtils.GetManagedType(paramTypeName);
if (e.EventParamsTypes[i] == null)
isInvalid = true;
}
@@ -283,9 +284,11 @@ namespace FlaxEditor.GUI.Timeline.Tracks
{
if (Events == null || Timeline == null)
return;
bool wasVisible = Events.Visible;
Events.Visible = Visible;
if (!Visible)
{
if(wasVisible)
Events.ClearSelection();
return;
}
@@ -413,51 +416,63 @@ namespace FlaxEditor.GUI.Timeline.Tracks
}
/// <inheritdoc />
public void OnKeyframesDeselect(IKeyframesEditor editor)
public new void OnKeyframesDeselect(IKeyframesEditor editor)
{
if (Events != null && Events.Visible)
Events.OnKeyframesDeselect(editor);
}
/// <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)
Events.OnKeyframesSelection(editor, control, selection);
}
/// <inheritdoc />
public int OnKeyframesSelectionCount()
public new int OnKeyframesSelectionCount()
{
return Events != null && Events.Visible ? Events.OnKeyframesSelectionCount() : 0;
}
/// <inheritdoc />
public void OnKeyframesDelete(IKeyframesEditor editor)
public new void OnKeyframesDelete(IKeyframesEditor editor)
{
if (Events != null && Events.Visible)
Events.OnKeyframesDelete(editor);
}
/// <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)
Events.OnKeyframesMove(editor, control, location, start, end);
}
/// <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)
Events.OnKeyframesCopy(editor, timeOffset, data);
}
/// <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)
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.
/// </summary>
/// <seealso cref="FlaxEditor.GUI.Timeline.Track" />
public class FolderTrack : Track
public class FolderTrack : ConductorTrack
{
/// <summary>
/// Gets the archetype.

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
@@ -55,7 +56,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
var keyframes = new KeyframesEditor.Keyframe[keyframesCount];
var dataBuffer = new byte[e.ValueSize];
var propertyType = Scripting.TypeUtils.GetType(e.MemberTypeName).Type;
var propertyType = Scripting.TypeUtils.GetManagedType(e.MemberTypeName);
if (propertyType == null)
{
e.Keyframes.ResetKeyframes();
@@ -250,10 +251,12 @@ namespace FlaxEditor.GUI.Timeline.Tracks
{
if (Keyframes == null || Timeline == null)
return;
bool wasVisible = Keyframes.Visible;
Keyframes.Visible = Visible;
if (!Visible)
{
Keyframes.ClearSelection();
if (wasVisible)
Keyframes.ClearSelection();
return;
}
Keyframes.KeyframesEditorContext = Timeline;
@@ -393,51 +396,63 @@ namespace FlaxEditor.GUI.Timeline.Tracks
}
/// <inheritdoc />
public void OnKeyframesDeselect(IKeyframesEditor editor)
public new void OnKeyframesDeselect(IKeyframesEditor editor)
{
if (Keyframes != null && Keyframes.Visible)
Keyframes.OnKeyframesDeselect(editor);
}
/// <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)
Keyframes.OnKeyframesSelection(editor, control, selection);
}
/// <inheritdoc />
public int OnKeyframesSelectionCount()
public new int OnKeyframesSelectionCount()
{
return Keyframes != null && Keyframes.Visible ? Keyframes.OnKeyframesSelectionCount() : 0;
}
/// <inheritdoc />
public void OnKeyframesDelete(IKeyframesEditor editor)
public new void OnKeyframesDelete(IKeyframesEditor editor)
{
if (Keyframes != null && Keyframes.Visible)
Keyframes.OnKeyframesDelete(editor);
}
/// <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)
Keyframes.OnKeyframesMove(editor, control, location, start, end);
}
/// <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)
Keyframes.OnKeyframesCopy(editor, timeOffset, data);
}
/// <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)
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).
/// </summary>
/// <seealso cref="FlaxEditor.GUI.Timeline.Track" />
public abstract class MemberTrack : Track
public abstract class MemberTrack : ConductorTrack
{
private float _previewValueLeft;
@@ -112,8 +112,9 @@ namespace FlaxEditor.GUI.Timeline.Tracks
/// <param name="options">The track initial options.</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>
protected MemberTrack(ref TrackCreateOptions options, bool useNavigationButtons = true, bool useValuePreview = true)
: base(ref options)
/// <param name="useProxyKeyframes">True if show sub-tracks keyframes as a proxy on this track, otherwise false.</param>
protected MemberTrack(ref TrackCreateOptions options, bool useNavigationButtons = true, bool useValuePreview = true, bool useProxyKeyframes = false)
: base(ref options, useProxyKeyframes)
{
var uiLeft = _muteCheckbox.Offsets.Left;

View File

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

View File

@@ -28,7 +28,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
/// The timeline track for animating managed objects.
/// </summary>
/// <seealso cref="FlaxEditor.GUI.Timeline.Track" />
public abstract class ObjectTrack : Track, IObjectTrack
public abstract class ObjectTrack : ConductorTrack, IObjectTrack
{
private bool _hasObject;
@@ -42,9 +42,13 @@ namespace FlaxEditor.GUI.Timeline.Tracks
/// </summary>
public abstract object Object { get; }
/// <inheritdoc />
protected ObjectTrack(ref TrackCreateOptions options)
: base(ref options)
/// <summary>
/// Initializes a new instance of the <see cref="ObjectTrack"/> 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 ObjectTrack(ref TrackCreateOptions options, bool useProxyKeyframes = true)
: base(ref options, useProxyKeyframes)
{
// Add track button
const float buttonSize = 14;

View File

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

View File

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

View File

@@ -254,6 +254,16 @@ namespace FlaxEngine
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 IncNameRegex2 = new Regex("\\((\\d+)\\)$");