Files
FlaxEngine/Source/Editor/GUI/Timeline/Tracks/EventTrack.cs
2025-09-22 10:22:32 +02:00

504 lines
16 KiB
C#

// Copyright (c) Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using FlaxEditor.GUI.Timeline.Undo;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Json;
using FlaxEngine.Utilities;
namespace FlaxEditor.GUI.Timeline.Tracks
{
/// <summary>
/// The timeline track for invoking events on a certain points in the time.
/// </summary>
/// <seealso cref="MemberTrack" />
public class EventTrack : MemberTrack, IKeyframesEditorContext
{
/// <summary>
/// Gets the archetype.
/// </summary>
/// <returns>The archetype.</returns>
public static TrackArchetype GetArchetype()
{
return new TrackArchetype
{
TypeId = 15,
Name = "Event",
DisableSpawnViaGUI = true,
Create = options => new EventTrack(ref options),
Load = LoadTrack,
Save = SaveTrack,
};
}
private static void LoadTrack(int version, Track track, BinaryReader stream)
{
var e = (EventTrack)track;
var paramsCount = stream.ReadInt32();
e.EventParamsSizes = new int[paramsCount];
e.EventParamsTypes = new Type[paramsCount];
int eventsCount = stream.ReadInt32();
e.MemberName = LoadName(stream);
bool isInvalid = false;
for (int i = 0; i < paramsCount; i++)
{
e.EventParamsSizes[i] = stream.ReadInt32();
var paramTypeName = LoadName(stream);
e.EventParamsTypes[i] = TypeUtils.GetManagedType(paramTypeName);
if (e.EventParamsTypes[i] == null)
{
Editor.LogError($"Unknown type {paramTypeName}.");
isInvalid = true;
}
}
if (isInvalid)
{
e.Events.ResetKeyframes();
stream.ReadBytes(eventsCount * (sizeof(float) + e.EventParamsSizes.Sum()));
Editor.LogError("Cannot load track " + e.MemberName + " of type " + e.MemberTypeName + ". Failed to find the value type information.");
return;
}
e.OnEventParamsChanged();
var events = new KeyframesEditor.Keyframe[eventsCount];
var dataBuffer = paramsCount != 0 ? new byte[e.EventParamsSizes.Max()] : null;
GCHandle handle = paramsCount != 0 ? GCHandle.Alloc(dataBuffer, GCHandleType.Pinned) : new GCHandle();
for (int i = 0; i < eventsCount; i++)
{
var time = stream.ReadSingle();
var key = new EventKey
{
Parameters = new object[paramsCount]
};
for (int j = 0; j < paramsCount; j++)
{
stream.Read(dataBuffer, 0, e.EventParamsSizes[j]);
key.Parameters[j] = Utilities.Utils.ByteArrayToStructure(handle.AddrOfPinnedObject(), e.EventParamsTypes[j], e.EventParamsSizes[j]);
}
events[i] = new KeyframesEditor.Keyframe
{
Time = time,
Value = key,
};
}
if (handle.IsAllocated)
handle.Free();
e.Events.SetKeyframes(events);
}
private static void SaveTrack(Track track, BinaryWriter stream)
{
var e = (EventTrack)track;
var events = e.Events.Keyframes;
var paramsCount = e.EventParamsTypes.Length;
stream.Write(paramsCount);
stream.Write(events.Count);
SaveName(stream, e.MemberName);
for (int i = 0; i < e.EventParamsTypes.Length; i++)
{
stream.Write(e.EventParamsSizes[i]);
SaveName(stream, e.EventParamsTypes[i]?.FullName);
}
var dataBuffer = paramsCount != 0 ? new byte[e.EventParamsSizes.Max()] : null;
IntPtr ptr = paramsCount != 0 ? Marshal.AllocHGlobal(dataBuffer.Length) : IntPtr.Zero;
for (int i = 0; i < events.Count; i++)
{
var keyframe = events[i];
var key = (EventKey)keyframe.Value;
stream.Write(keyframe.Time);
for (int j = 0; j < paramsCount; j++)
{
Utilities.Utils.StructureToByteArray(key.Parameters[j], e.EventParamsSizes[j], ptr, dataBuffer);
stream.Write(dataBuffer, 0, e.EventParamsSizes[j]);
}
}
Marshal.FreeHGlobal(ptr);
}
private byte[] _eventsEditingStartData;
/// <summary>
/// The event keyframes editor.
/// </summary>
public KeyframesEditor Events;
/// <summary>
/// The event parameters data sizes collection.
/// </summary>
public int[] EventParamsSizes = Utils.GetEmptyArray<int>();
/// <summary>
/// The event parameters types collection.
/// </summary>
public Type[] EventParamsTypes = Utils.GetEmptyArray<Type>();
/// <summary>
/// The event key data.
/// </summary>
public struct EventKey : ICloneable
{
/// <summary>
/// The parameters values.
/// </summary>
[EditorDisplay("Parameters", EditorDisplayAttribute.InlineStyle), ExpandGroups]
[Collection(CanReorderItems = false, CanResize = true)]
public object[] Parameters;
/// <inheritdoc />
public override string ToString()
{
if (Parameters == null || Parameters.Length == 0)
return "()";
var sb = new StringBuilder();
sb.Append('(');
for (int i = 0; i < Parameters.Length; i++)
{
if (i != 0)
sb.Append(", ");
sb.Append(Parameters[i] ?? "<null>");
}
sb.Append(')');
return sb.ToString();
}
/// <inheritdoc />
public object Clone()
{
if (Parameters == null)
return new EventKey();
// Deep clone parameter values (especially boxed value types need to be duplicated to avoid referencing the same ones)
var parameters = new object[Parameters.Length];
for (int i = 0; i < parameters.Length; i++)
{
var p = Parameters[i];
if (p == null || p is FlaxEngine.Object)
parameters[i] = Parameters[i];
else
parameters[i] = JsonSerializer.Deserialize(JsonSerializer.Serialize(p), p.GetType());
}
return new EventKey { Parameters = parameters };
}
}
/// <inheritdoc />
public EventTrack(ref TrackCreateOptions options)
: base(ref options, true, false)
{
Height = 20.0f;
// Events editor
Events = new KeyframesEditor
{
EnableZoom = false,
EnablePanning = false,
ScrollBars = ScrollBars.None,
};
Events.Edited += OnEventsEdited;
Events.EditingStart += OnEventsEditingStart;
Events.EditingEnd += OnEventsEditingEnd;
Events.UnlockChildrenRecursive();
_addKey.Clicked += OnAddKeyClicked;
_leftKey.Clicked += OnLeftKeyClicked;
_rightKey.Clicked += OnRightKeyClicked;
}
private void OnRightKeyClicked(Image image, MouseButton button)
{
if (button == MouseButton.Left && GetNextKeyframeFrame(Timeline.CurrentTime, out var frame))
{
Timeline.OnSeek(frame);
}
}
/// <inheritdoc />
public override bool GetNextKeyframeFrame(float time, out int result)
{
for (int i = 0; i < Events.Keyframes.Count; i++)
{
var k = Events.Keyframes[i];
if (k.Time > time)
{
result = Mathf.FloorToInt(k.Time * Timeline.FramesPerSecond);
return true;
}
}
return base.GetNextKeyframeFrame(time, out result);
}
private void OnAddKeyClicked(Image image, MouseButton button)
{
if (button == MouseButton.Left)
{
// Evaluate a value
var time = Timeline.CurrentTime;
if (!TryGetValue(out var value))
value = Events.Evaluate(time);
value = ((ICloneable)value).Clone();
// Find event at the current location
for (int i = Events.Keyframes.Count - 1; i >= 0; i--)
{
var k = Events.Keyframes[i];
var frame = Mathf.FloorToInt(k.Time * Timeline.FramesPerSecond);
if (frame == Timeline.CurrentFrame)
{
// Skip if value is the same
if (k.Value == value)
return;
// Update existing event value
Events.SetKeyframe(i, value);
return;
}
}
// Add a new key
using (new TrackUndoBlock(this))
Events.AddKeyframe(new KeyframesEditor.Keyframe(time, value));
}
}
private void OnLeftKeyClicked(Image image, MouseButton button)
{
if (button == MouseButton.Left && GetPreviousKeyframeFrame(Timeline.CurrentTime, out var frame))
{
Timeline.OnSeek(frame);
}
}
/// <inheritdoc />
public override bool GetPreviousKeyframeFrame(float time, out int result)
{
for (int i = Events.Keyframes.Count - 1; i >= 0; i--)
{
var k = Events.Keyframes[i];
if (k.Time < time)
{
result = Mathf.FloorToInt(k.Time * Timeline.FramesPerSecond);
return true;
}
}
return base.GetPreviousKeyframeFrame(time, out result);
}
private void UpdateEvents()
{
if (Events == null || Timeline == null)
return;
bool wasVisible = Events.Visible;
Events.Visible = Visible;
if (!Visible)
{
if (wasVisible)
Events.ClearSelection();
return;
}
Events.KeyframesEditorContext = Timeline;
Events.CustomViewPanning = Timeline.OnKeyframesViewPanning;
Events.Bounds = new Rectangle(Timeline.StartOffset, Y + 1.0f, Timeline.Duration * Timeline.UnitsPerSecond * Timeline.Zoom, Height - 2.0f);
Events.ViewScale = new Float2(Timeline.Zoom, 1.0f);
Events.Visible = Visible;
Events.UpdateKeyframes();
}
private void OnEventsEdited()
{
Timeline.MarkAsEdited();
}
private void OnEventsEditingStart()
{
_eventsEditingStartData = EditTrackAction.CaptureData(this);
}
private void OnEventsEditingEnd()
{
var after = EditTrackAction.CaptureData(this);
if (!Utils.ArraysEqual(_eventsEditingStartData, after))
Timeline.AddBatchedUndoAction(new EditTrackAction(Timeline, this, _eventsEditingStartData, after));
_eventsEditingStartData = null;
}
private void OnEventParamsChanged()
{
// Calculate data size
EventParamsSizes = new int[EventParamsTypes.Length];
for (int i = 0; i < EventParamsTypes.Length; i++)
{
var type = EventParamsTypes[i];
EventParamsSizes[i] = Marshal.SizeOf(type.IsEnum ? Enum.GetUnderlyingType(type) : type);
}
// Build default value
var defaultValue = new EventKey
{
Parameters = new object[EventParamsTypes.Length]
};
for (int i = 0; i < EventParamsTypes.Length; i++)
defaultValue.Parameters[i] = Activator.CreateInstance(EventParamsTypes[i]);
Events.DefaultValue = defaultValue;
}
/// <inheritdoc />
protected override MemberTypes MemberTypes => MemberTypes.Method;
/// <inheritdoc />
protected override void OnMemberChanged(MemberInfo value, Type type)
{
base.OnMemberChanged(value, type);
Events.ResetKeyframes();
if (value is MethodInfo m)
{
EventParamsTypes = m.GetParameters().Select(x => x.ParameterType).ToArray();
}
else
{
EventParamsTypes = Utils.GetEmptyArray<Type>();
}
OnEventParamsChanged();
}
/// <inheritdoc />
protected override void OnVisibleChanged()
{
base.OnVisibleChanged();
Events.Visible = Visible;
}
/// <inheritdoc />
public override void OnTimelineChanged(Timeline timeline)
{
base.OnTimelineChanged(timeline);
Events.Parent = timeline?.MediaPanel;
Events.FPS = timeline?.FramesPerSecond;
UpdateEvents();
}
/// <inheritdoc />
public override void OnTimelineZoomChanged()
{
base.OnTimelineZoomChanged();
UpdateEvents();
}
/// <inheritdoc />
public override void OnTimelineArrange()
{
base.OnTimelineArrange();
UpdateEvents();
}
/// <inheritdoc />
public override void OnTimelineFpsChanged(float before, float after)
{
base.OnTimelineFpsChanged(before, after);
Events.FPS = after;
}
/// <inheritdoc />
public override void OnDestroy()
{
if (Events != null)
{
Events.Dispose();
Events = null;
}
base.OnDestroy();
}
/// <inheritdoc />
public new void OnKeyframesDeselect(IKeyframesEditor editor)
{
if (Events != null && Events.Visible)
Events.OnKeyframesDeselect(editor);
}
/// <inheritdoc />
public new void OnKeyframesSelection(IKeyframesEditor editor, ContainerControl control, Rectangle selection)
{
if (Events != null && Events.Visible)
Events.OnKeyframesSelection(editor, control, selection);
}
/// <inheritdoc />
public new int OnKeyframesSelectionCount()
{
return Events != null && Events.Visible ? Events.OnKeyframesSelectionCount() : 0;
}
/// <inheritdoc />
public new void OnKeyframesDelete(IKeyframesEditor editor)
{
if (Events != null && Events.Visible)
Events.OnKeyframesDelete(editor);
}
/// <inheritdoc />
public new void OnKeyframesMove(IKeyframesEditor editor, ContainerControl control, Float2 location, bool start, bool end)
{
if (Events != null && Events.Visible)
Events.OnKeyframesMove(editor, control, location, start, end);
}
/// <inheritdoc />
public new void OnKeyframesCopy(IKeyframesEditor editor, float? timeOffset, StringBuilder data)
{
if (Events != null && Events.Visible)
Events.OnKeyframesCopy(editor, timeOffset, data);
}
/// <inheritdoc />
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);
}
}
}