328 lines
No EOL
8.9 KiB
C#
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
|
|
}
|
|
|
|
} |