JD2022-TU1/main/tools/framework/JD.Serialization/Timeline/Clips/Clip.cs

328 lines
No EOL
8.9 KiB
C#

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using JD.Core.Collections;
namespace JD.Serialization
{
public partial class Clip
{
#region Constants
public static class PropertyNames
{
public const string Curves = "Curves";
public const string StartTime = "StartTime";
public const string Duration = "Duration";
public const string IsActive = "IsActive";
}
#endregion
#region Variables
private readonly CurveCollection _curves;
private Tape _tape;
private CollisionArea _collisionsWith;
#endregion
#region Constructor
public Clip()
{
_isActive = true;
_curves = new CurveCollection(this);
_curves.CollectionChanged += _curves_CollectionChanged;
_curves.ItemChanged += _curves_ItemsChanged;
_id = IdGenerator.Instance.Generate();
if (DefaultDuration > 0)
{
Duration = DefaultDuration;
}
}
#endregion
#region Properties
public CurveCollection Curves
{
get { return _curves; }
}
public TapeTrack Track
{
get
{
return _tape?.GetOrCreateTrack<TapeTrack>(TrackId);
}
set
{
if (Track != value)
{
if (value != null)
{
TrackId = value.Id;
}
OnPropertyChanged();
}
}
}
public Tape Tape
{
get { return _tape; }
set
{
if (_tape != value)
{
if (_tape != null && value != null)
{
throw new InvalidOperationException("Cannot assign a clip to another tape.");
}
_tape = value;
OnPropertyChanged();
}
}
}
[Category("Time"), Description("")]
[DisplayName("Start Frame")]
[Browsable(false)]
public int StartFrame
{
get
{
return StartTime + ContextualOffset;
}
set
{
if (StartFrame != value)
{
StartTime = value - ContextualOffset;
}
}
}
[Category("Time")]
[DisplayName("StartTime (in beats)")]
[Description("Start time in beat")]
[Browsable(true)]
public float StartBeat
{
get { return FrameConverter.TimeFromFrame(StartFrame); }
set { StartFrame = FrameConverter.FrameFromTimeInteger(value); }
}
[Category("Time")]
[DisplayName("Duration (in beats)")]
[Description("Duration in beats")]
[Browsable(true)]
public virtual float BeatDuration
{
get { return FrameConverter.TimeFromFrame(Duration); }
set { Duration = FrameConverter.FrameFromTimeInteger(value); }
}
[Category("Time")]
[DisplayName("StopTime")]
[Description("Stop time in frames")]
[Browsable(true)]
public virtual int StopFrame
{
get
{
return StartFrame + Duration;
}
set
{
if (value >= StartFrame)
{
Duration = value - StartFrame;
}
}
}
[Category("Time")]
[DisplayName("StopTime (in beats)")]
[Description("Stop time in beat")]
[Browsable(false)]
public float StopBeat
{
get { return FrameConverter.TimeFromFrame(StopFrame); }
set { StopFrame = FrameConverter.FrameFromTimeInteger(value); }
}
public int AbsoluteStartFrame
{
get { return StartFrame - ContextualOffset; }
set { StartFrame = value + ContextualOffset; }
}
/// <summary>
/// When clips are edited as "clip references", the start frame is offsetted so the
/// user can edit the clip in its actual context.
/// </summary>
public int ContextualOffset
{
get
{
var tape = Tape;
if (tape == null)
{
return 0;
}
var editionContext = tape.EditionContext;
return editionContext?.StartFrame ?? 0;
}
}
public CollisionArea CollisionsWith
{
get { return _collisionsWith; }
set
{
if (_collisionsWith != value)
{
_collisionsWith = value;
OnPropertyChanged();
OnPropertyChanged(nameof(CollisionsWithOther));
}
}
}
public bool CollisionsWithOther
{
get { return _collisionsWith != null; }
}
public virtual IEnumerable<CollisionArea> CollisionAreas
{
get
{
yield return new CollisionArea(this)
{
CollisionGroup = Track.Id,
};
}
}
public Type Type => GetType();
/// <summary>
/// Identifier of the clip.
/// </summary>
public virtual string Name
{
get { return Type.Name; }
set { }
}
/// <summary>
/// When defined, new clips will be of that duration when created.
/// </summary>
public virtual int DefaultDuration => -1;
#endregion
#region Methods
/// <summary>
/// Computed properties are not considered in undo/redo and to make the document "dirty"
/// </summary>
public virtual bool IsComputedProperty(string propertyName)
{
return propertyName == nameof(CollisionsWith) || propertyName == nameof(CollisionsWithOther);
}
public override string ToString()
{
return Type.Name;
}
public string ToStringDetailed()
{
return $"{Type.Name} [{StartBeat},{StopBeat}] in track '{Track}'";
}
public static Clip Instantiate(Type type, TapeTrack track, int startFrame)
{
if (!track.IsClipAllowed(type))
{
throw new ArgumentException($"The track should be able to handle clips of type {type.Name}");
}
var newClip = (Clip)Activator.CreateInstance(type);
newClip.Track = track;
newClip.StartFrame = startFrame;
return newClip;
}
public static Clip Instantiate(Type type)
{
var newClip = (Clip)Activator.CreateInstance(type);
return newClip;
}
partial void OnDurationChanging(ref int value)
{
value = Math.Max(value, 1);
}
public Clip Clone()
{
Clip clone = CloneExtensions.Clone(this);
clone.RegenerateId();
return clone;
}
public virtual Clip ConvertToFit(TapeTrack track)
{
var clip = this;
clip.Track = track;
if (track.ClipTypes().Count() == 1)
{
Type conversionType = track.ClipTypes().First();
if (GetType() != conversionType)
{
var newClip = Instantiate(conversionType);
if (newClip != null)
{
newClip.Tape = Tape;
newClip.StartTime = StartTime;
newClip.Duration = Duration;
newClip.TrackId = TrackId;
clip = newClip;
}
}
}
return clip;
}
/// <summary>
/// Called after a copy paste on a clip, make sure the clips is made different from its source.
/// </summary>
public virtual void RegenerateId()
{
Id = IdGenerator.Instance.Generate();
}
private void _curves_CollectionChanged(object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
{
OnPropertyChanged(nameof(Curves));
}
private void _curves_ItemsChanged(object sender, ItemChangedEventArgs<BezierCurveFloat> args)
{
OnPropertyChanged(nameof(Curves));
}
#endregion
}
}