// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. using System; using System.IO; using System.Linq; using FlaxEditor.GUI.Timeline.Undo; using FlaxEditor.Utilities; using FlaxEngine; namespace FlaxEditor.GUI.Timeline.Tracks { /// /// The timeline media that represents a nested animation media event. /// /// public class NestedAnimationMedia : Media { private sealed class Proxy : ProxyBase { /// /// The nested animation to play. /// [EditorDisplay("General"), EditorOrder(10)] public Animation NestedAnimation { get => Track.Asset; set => Track.Asset = value; } /// /// If checked, the nested animation will loop when playback exceeds its duration. Otherwise it will stop play. /// [EditorDisplay("General"), EditorOrder(20)] public bool Loop { get => Track.Loop; set => Track.Loop = value; } /// /// Animation playback speed. /// [EditorDisplay("General"), EditorOrder(30), Limit(0.0f, 100.0f, 0.001f)] public float Speed { get => Track.Speed; set => Track.Speed = value; } /// /// Animation playback start time position (in seconds). Can be used to offset the nested animation start. /// [EditorDisplay("General"), EditorOrder(40)] public float StartTime { get => Track.StartTime; set => Track.StartTime = value; } /// public Proxy(NestedAnimationTrack track, NestedAnimationMedia media) : base(track, media) { } } /// public override void OnTimelineChanged(Track track) { base.OnTimelineChanged(track); PropertiesEditObject = track != null ? new Proxy((NestedAnimationTrack)track, this) : null; } } /// /// The timeline track that represents a nested animation playback. /// /// public class NestedAnimationTrack : SingleMediaAssetTrack { /// /// Gets the archetype. /// /// The archetype. public static TrackArchetype GetArchetype() { return new TrackArchetype { TypeId = 20, Name = "Nested Animation", Create = options => new NestedAnimationTrack(ref options), Load = LoadTrack, Save = SaveTrack, }; } private static void LoadTrack(int version, Track track, BinaryReader stream) { var e = (NestedAnimationTrack)track; Guid id = stream.ReadGuid(); e.Asset = FlaxEngine.Content.LoadAsync(id); var m = e.TrackMedia; m.StartFrame = (int)stream.ReadSingle(); m.DurationFrames = (int)stream.ReadSingle(); e.Speed = stream.ReadSingle(); e.StartTime = stream.ReadSingle(); } private static void SaveTrack(Track track, BinaryWriter stream) { var e = (NestedAnimationTrack)track; stream.WriteGuid(ref e.AssetID); if (e.Media.Count != 0) { var m = e.TrackMedia; stream.Write((float)m.StartFrame); stream.Write((float)m.DurationFrames); } else { stream.Write((float)0); stream.Write((float)track.Timeline.DurationFrames); } stream.Write(e.Speed); stream.Write(e.StartTime); } /// /// Nested animation playback speed. /// public float Speed = 1.0f; /// /// Nested animation playback start time position (in seconds). Can be used to offset the nested animation start. /// public float StartTime = 0.0f; /// public NestedAnimationTrack(ref TrackCreateOptions options) : base(ref options) { MinMediaCount = 1; MaxMediaCount = 1; } /// protected override void OnAssetChanged() { base.OnAssetChanged(); CheckCyclicReferences(); if (Timeline != null && Asset && !Asset.WaitForLoaded()) { using (new TrackUndoBlock(this)) TrackMedia.Duration = Asset.Length; } } /// public override void OnTimelineChanged(Timeline timeline) { base.OnTimelineChanged(timeline); CheckCyclicReferences(); } private void CheckCyclicReferences() { if (Asset && Timeline is AnimationTimeline timeline) { var refs = Asset.GetReferences(); var id = timeline._id; if (Asset.ID == id || refs.Contains(id)) { Asset = null; throw new Exception("Cannot use nested scene animation (recursion)."); } } } } }