693 lines
24 KiB
C#
693 lines
24 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Drawing;
|
|
using System.Linq;
|
|
using System.Windows.Forms;
|
|
using JD.Application.Utils;
|
|
using JD.Application.View;
|
|
using JD.Application.View.Misc;
|
|
using JD.Application.View.Timeline;
|
|
using JD.Application.View.WindowLess;
|
|
using JD.Audio.Annotations;
|
|
using JD.Collections;
|
|
using JD.Collections.Extensions;
|
|
using JD.Collections.Interfaces;
|
|
using JD.Kernel.Classes;
|
|
using JD.Kernel.Structs;
|
|
using JD.Maths.Utils;
|
|
using JD.Sequencers.Times;
|
|
using JD.Timeline.Document;
|
|
using JD.Timeline.Karaoke;
|
|
using JD.Timeline.Model;
|
|
using JD.Timeline.Model.Clips;
|
|
using JD.Timeline.Plugin;
|
|
using JD.Timeline.View.Actions;
|
|
using JD.Timeline.View.Actions.Refactor;
|
|
using JD.Timeline.View.ClipView.Widgets;
|
|
using JD.Timeline.View.DataBinding;
|
|
using TeaBox;
|
|
using JD.Serialization;
|
|
|
|
namespace JD.Timeline.View.ClipView
|
|
{
|
|
/// <summary>
|
|
/// The ClipViewControl is based on the WindowLess framework. It allows manage clips (add clip, move, delete, ...)
|
|
/// </summary>
|
|
public class ClipViewControl : WaveControl
|
|
{
|
|
#region Variables
|
|
|
|
private readonly Tape _tape;
|
|
private readonly TapeViewReplicator _tapeViewReplicator;
|
|
private readonly ClipViewDragDropHandler _clipViewDragDropHandler;
|
|
private readonly ActorInvalidator _actorInvalidator;
|
|
private readonly LyricsSplittingTool _lyricsSplittingTool;
|
|
private readonly ClipCollisionComputer _clipCollisionComputer;
|
|
|
|
#endregion
|
|
|
|
#region Constructor
|
|
|
|
public ClipViewControl(Tape tape)
|
|
{
|
|
_tape = tape;
|
|
_tape.PropertyChanged += Tape_PropertyChanged;
|
|
_tape.EditionContextChanged += tape_EditionContextChanged;
|
|
SongBackground.Paint += SongBackground_Paint;
|
|
tape_EditionContextChanged(null, null);
|
|
Tape_PropertyChanged(null, null);
|
|
|
|
Timeline.ShowInBeats = true;
|
|
|
|
_tapeViewReplicator = new TapeViewReplicator(_tape, TrackView);
|
|
_clipViewDragDropHandler = new ClipViewDragDropHandler(this);
|
|
|
|
var draggingTool = TrackView.ToolManager.MouseMoveTools.FirstOf<SequencerDraggingTool>();
|
|
new DraggingToolExtension(draggingTool);
|
|
new DraggingModerator(draggingTool);
|
|
|
|
TooltipOnBottom = true;
|
|
TooltipTextFormat = new StringFormat { LineAlignment = StringAlignment.Center, Alignment = StringAlignment.Near };
|
|
|
|
_actorInvalidator = new ActorInvalidator(TrackView);
|
|
|
|
_lyricsSplittingTool = new LyricsSplittingTool(TrackView);
|
|
TrackView.ToolManager.MouseDownTools.Insert(0, _lyricsSplittingTool);
|
|
TrackView.ToolManager.MouseUpTools.Insert(0, _lyricsSplittingTool);
|
|
TrackView.ToolManager.MouseMoveTools.Insert(0, _lyricsSplittingTool);
|
|
|
|
_clipCollisionComputer = new ClipCollisionComputer(tape);
|
|
}
|
|
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
if (disposing)
|
|
{
|
|
_tape.PropertyChanged -= Tape_PropertyChanged;
|
|
_tape.EditionContextChanged -= tape_EditionContextChanged;
|
|
|
|
Timeline.Dispose();
|
|
_tapeViewReplicator.Dispose();
|
|
_actorInvalidator.Dispose();
|
|
}
|
|
|
|
base.Dispose(disposing);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Properties
|
|
|
|
public Tape Tape => _tape;
|
|
|
|
public new IEnumerable<ClipWidget> Selection => TrackView.Selection.OfType<ClipWidget>();
|
|
|
|
public void AddToSelection(Clip model)
|
|
{
|
|
var toSelect = TrackView.ControlsRecursive().OfType<ClipWidget>().FirstOrDefault(view => view.Model == model);
|
|
if (toSelect != null)
|
|
{
|
|
TrackView.Selection.Add(toSelect);
|
|
}
|
|
}
|
|
|
|
public TapeTrackWidget this[TapeTrack _track] => _tapeViewReplicator[_track];
|
|
|
|
public IEnumerable<ClipWidget> ClipWidgets => TrackView.ControlsRecursive().OfType<ClipWidget>();
|
|
|
|
public void ClearSelection()
|
|
{
|
|
TrackView.Selection.Clear();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Wave Control Specialization
|
|
|
|
/// <summary>
|
|
/// The range with a little buffer (waiting for the start and end beats).
|
|
/// </summary>
|
|
protected override Range<BeatUnit> ComputeRange()
|
|
{
|
|
return new Range<BeatUnit>(Wave.Annotations.StartBeat - new BeatUnit(10), ComputeEndBeat());
|
|
}
|
|
|
|
private BeatUnit ComputeEndBeat()
|
|
{
|
|
return _tape.Clips.Select(clip => new BeatUnit(clip.StartBeat + clip.BeatDuration)).Concat(Wave.Annotations.EndBeat).Max();
|
|
}
|
|
|
|
protected override void InitializeContextMenu()
|
|
{
|
|
TeaBox.ObjectModel.Application.Main.Actions.CreateContextMenu("Application/ClipView/ContextMenu", this).AssignOnControl(this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resize tracks on shift wheel.
|
|
/// </summary>
|
|
protected override void HandleZoomOnMouseWheel(MouseEventArgs e)
|
|
{
|
|
if (ModifierKeys == Keys.Shift)
|
|
{
|
|
int increment = e.Delta < 0 ? -2 : 2;
|
|
foreach (var track in TrackView.GetTracks())
|
|
{
|
|
track.Height = (track.Height + increment).Clamp(10, 300);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
base.HandleZoomOnMouseWheel(e);
|
|
}
|
|
}
|
|
|
|
void SongBackground_Paint(object sender, PaintEventArgs e)
|
|
{
|
|
if (_tape.EditionContext != null)
|
|
{
|
|
foreach (var section in _tape.EditionContext.EnumerateLoopSections())
|
|
{
|
|
int x = Timeline.PixelFromFrame(section.End);
|
|
e.Graphics.DrawLine(Pens.Gray, x, Top, x, Bottom);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Event Creation Helper
|
|
|
|
public TapeTrack TrackFromPoint(int y)
|
|
{
|
|
return (from track in TrackView.GetTracks().OfType<TapeTrackWidget>()
|
|
where track.IsVisible && y.IsBetween(track.Bounds.Top, track.Bounds.Bottom)
|
|
select track.Track).FirstOrDefault();
|
|
}
|
|
|
|
public float FrameFromPoint(int x)
|
|
{
|
|
BeatUnit beat = Timeline.BeatFromTimeUnit(Timeline.PixelToTime(x));
|
|
var snapped = Timeline.Wave.UnitConverter.Snap(beat, EditionOptions.CurrentSnappingUnit);
|
|
return Timeline.FrameFromTime(Timeline.TimeUnitFromBeat(snapped));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Handlers
|
|
|
|
private void Tape_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
|
{
|
|
if (Wave != _tape.TryGetWave())
|
|
{
|
|
Wave = _tape.TryGetWave();
|
|
Invalidate();
|
|
}
|
|
}
|
|
|
|
private void tape_EditionContextChanged(object sender, EventArgs e)
|
|
{
|
|
var editionContext = _tape.EditionContext;
|
|
var activeArea = Range<BeatUnit>.Empty;
|
|
|
|
if (editionContext != null)
|
|
{
|
|
var start = editionContext.StartBeat;
|
|
activeArea = new Range<BeatUnit>(new BeatUnit(start), new BeatUnit(start + editionContext.BeatDuration));
|
|
}
|
|
else if (Wave != null)
|
|
{
|
|
activeArea = Wave.UnitConverter.TimeRange;
|
|
}
|
|
|
|
SongBackground.ActiveArea = activeArea;
|
|
}
|
|
|
|
protected override void OnDoubleClick(LocalizedTrackItem trackItem)
|
|
{
|
|
// Do not move time cursor when double clicking on a tape reference.
|
|
var tapeReference = trackItem as TapeReferenceClipWidget;
|
|
if (tapeReference != null)
|
|
{
|
|
tapeReference.Model.OpenDocument();
|
|
return;
|
|
}
|
|
|
|
// Do not move time cursor when double clicking on a clip with actor
|
|
var clipWithActor = trackItem as ClipWithActorWidget;
|
|
if (clipWithActor != null)
|
|
{
|
|
TeaBox.ObjectModel.Application.Main.Actions.Execute("TimelineActions.BrowseForActor");
|
|
return;
|
|
}
|
|
|
|
base.OnDoubleClick(trackItem);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Action Handlers
|
|
|
|
[ActionHandler("EditActions.MoveUp")]
|
|
protected override void HandleMoveUp()
|
|
{
|
|
Selection.Where(widget => widget.IsMoveAllowed).Select(clip => clip.Model).MoveUp(this.TrackView);
|
|
}
|
|
|
|
[ActionHandler("EditActions.MoveDown")]
|
|
protected override void HandleMoveDown()
|
|
{
|
|
Selection.Where(widget => widget.IsMoveAllowed).Select(clip => clip.Model).MoveDown(this.TrackView);
|
|
}
|
|
|
|
protected override void UpdateMoveLeft(ActionInfo info)
|
|
{
|
|
info.Enabled = Selection.Any(widget => widget.IsMoveAllowed);
|
|
}
|
|
|
|
protected override void UpdateMoveRight(ActionInfo info)
|
|
{
|
|
info.Enabled = Selection.Any(widget => widget.IsMoveAllowed);
|
|
}
|
|
|
|
[ActionHandler("EditActions.MoveLeft")]
|
|
protected override void HandleMoveLeft()
|
|
{
|
|
var pivot = Selection.Where(clip => clip.IsCurrentControl).Select(clip => clip.Model).FirstOrDefault();
|
|
Selection.Where(widget => widget.IsMoveAllowed).Select(clip => clip.Model).MoveHorizontal(Wave.UnitConverter, left: true, pivot: pivot, snap: EditionOptions.CurrentSnappingUnit);
|
|
}
|
|
|
|
[ActionHandler("EditActions.MoveRight")]
|
|
protected override void HandleMoveRight()
|
|
{
|
|
var pivot = Selection.Where(clip => clip.IsCurrentControl).Select(clip => clip.Model).FirstOrDefault();
|
|
Selection.Where(widget => widget.IsMoveAllowed).Select(clip => clip.Model).MoveHorizontal(Wave.UnitConverter, left: false, pivot: pivot, snap: EditionOptions.CurrentSnappingUnit);
|
|
}
|
|
|
|
[ActionHandler("EditActions.SelectNext")]
|
|
protected override void HandleSelectNext()
|
|
{
|
|
SelectNextClip();
|
|
}
|
|
|
|
[ActionHandler("EditActions.SelectPrevious")]
|
|
protected override void HandleSelectPrevious()
|
|
{
|
|
SelectPreviousClip();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Selects next clip, by cycling on clips sorted by start time and then track index
|
|
/// </summary>
|
|
public void SelectNextClip()
|
|
{
|
|
if (!Selection.Any())
|
|
return;
|
|
|
|
var lastClipInSelection = Selection.Select(clip => clip.Model).MaxElement(x => x.StopBeat);
|
|
var clipsSorted = (from clip in Tape.Clips
|
|
orderby clip.StartBeat, clip.Track.Index
|
|
select clip).ToList();
|
|
|
|
int index = clipsSorted.IndexOf(lastClipInSelection);
|
|
var nextClip = clipsSorted[(index + 1) % clipsSorted.Count];
|
|
|
|
if (nextClip != null)
|
|
{
|
|
TrackView.Selection.Clear();
|
|
AddToSelection(nextClip);
|
|
if (!Timeline.VisibleTimeRange.Contains(nextClip.StopBeat))
|
|
{
|
|
Timeline.TimeOffset += nextClip.StopBeat - Timeline.VisibleTimeRange.Max;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Selects previous clip, by cycling on clips sorted by start time and then track index
|
|
/// </summary>
|
|
public void SelectPreviousClip()
|
|
{
|
|
if (!Selection.Any())
|
|
return;
|
|
|
|
var firstClipInSelection = Selection.Select(clip => clip.Model).MinElement(x => x.StartBeat);
|
|
var clipsSorted = (from clip in Tape.Clips
|
|
orderby clip.StartBeat, clip.Track.Index
|
|
select clip).ToList();
|
|
|
|
int index = clipsSorted.IndexOf(firstClipInSelection);
|
|
var previousClip = clipsSorted[(index - 1) % clipsSorted.Count];
|
|
|
|
if (previousClip != null)
|
|
{
|
|
TrackView.Selection.Clear();
|
|
AddToSelection(previousClip);
|
|
if (!Timeline.VisibleTimeRange.Contains(previousClip.StartBeat))
|
|
{
|
|
Timeline.TimeOffset -= Timeline.VisibleTimeRange.Min - previousClip.StartBeat;
|
|
}
|
|
}
|
|
}
|
|
|
|
[ActionUpdateHandler(EditActions.Delete)]
|
|
private void UpdateDelete(ActionInfo info)
|
|
{
|
|
info.Enabled = Selection.Any();
|
|
}
|
|
|
|
[ActionHandler(EditActions.Delete)]
|
|
private void HandleDelete()
|
|
{
|
|
using (TrackView.Selection.BeginScopedUpdate())
|
|
{
|
|
foreach (var clip in Selection.Where(widget => widget.IsMoveAllowed).Select(@event => @event.Model).ToArray())
|
|
{
|
|
_tape.Clips.Remove(clip);
|
|
}
|
|
}
|
|
}
|
|
|
|
[ActionUpdateHandler(EditActions.Copy)]
|
|
private void UpdateCopy(ActionInfo actionInfo)
|
|
{
|
|
actionInfo.Enabled = Selection.Any();
|
|
}
|
|
|
|
[ActionHandler(EditActions.Copy)]
|
|
private void HandleCopy()
|
|
{
|
|
ToClipboard();
|
|
}
|
|
|
|
private class ClipboardClipData
|
|
{
|
|
[UAFSerializable(4294967295)]
|
|
public Clip Clip { get; set; }
|
|
[UAFSerializable(4294967295)]
|
|
public int TrackIndex { get; set; }
|
|
}
|
|
|
|
private void ToClipboard()
|
|
{
|
|
var clipsToCopy = new UAFList<ClipboardClipData>();
|
|
clipsToCopy.AddRange(Selection.Select(clip => new ClipboardClipData() { Clip = clip.Model, TrackIndex = clip.Model.Track.Index }));
|
|
Clipboard.SetText(new LuaSerializer().Serialize(clipsToCopy), TextDataFormat.UnicodeText);
|
|
}
|
|
|
|
[ActionUpdateHandler(EditActions.Cut)]
|
|
private void UpdateCut(ActionInfo actionInfo)
|
|
{
|
|
actionInfo.Enabled = Selection.Any();
|
|
}
|
|
|
|
[ActionHandler(EditActions.Cut)]
|
|
private void HandleCut()
|
|
{
|
|
ToClipboard();
|
|
HandleDelete();
|
|
}
|
|
|
|
[ActionHandler(EditActions.Paste)]
|
|
private void HandlePaste()
|
|
{
|
|
Paste(inPlace: false);
|
|
}
|
|
|
|
[ActionHandler("TimelineActions.PasteInPlace")]
|
|
private void HandlePasteInPlace()
|
|
{
|
|
Paste(inPlace: true);
|
|
}
|
|
|
|
private void Paste(bool inPlace)
|
|
{
|
|
var clipboardClipDataList = new UAFList<ClipboardClipData>();
|
|
try
|
|
{
|
|
var pastedData = Clipboard.GetText();
|
|
new LuaSerializer().Deserialize(pastedData, clipboardClipDataList);
|
|
foreach (var clipboardClipData in clipboardClipDataList)
|
|
{
|
|
clipboardClipData.Clip.RegenerateId();
|
|
}
|
|
}
|
|
catch (Exception)
|
|
{
|
|
MessageService.MessageService.ShowBalloon(TimeSpan.FromSeconds(10), "Could not paste the data", "Data was not in correct format", ToolTipIcon.Warning);
|
|
return;
|
|
}
|
|
|
|
var clipsConverted = new UAFList<Clip>();
|
|
|
|
int? mouseTrackId = ComputeTargetTrackToPaste();
|
|
int minTrackIndexInClips = (clipboardClipDataList.Count > 0) ? clipboardClipDataList.Select(x => x.TrackIndex).Min() : 0;
|
|
int destTrackId = mouseTrackId != null ? (int)mouseTrackId : minTrackIndexInClips;
|
|
foreach (var clipboardClipData in clipboardClipDataList)
|
|
{
|
|
//We get or create the track where we want to paste the clip
|
|
int distFromMin = clipboardClipData.TrackIndex - minTrackIndexInClips;
|
|
var trackToPasteInto = _tape.GetOrCreateTrackAt(destTrackId + distFromMin);
|
|
|
|
clipboardClipData.Clip.Tape = Tape;
|
|
|
|
//We convert the clip to fit the destination track
|
|
var clipConverted = clipboardClipData.Clip.ConvertToFit(trackToPasteInto);
|
|
clipConverted.Track = trackToPasteInto;
|
|
clipsConverted.Add(clipConverted);
|
|
}
|
|
clipsConverted.MakeUnique();
|
|
_tape.Clips.AddRange(clipsConverted);
|
|
|
|
if (!inPlace)
|
|
{
|
|
MoveClipsToCursor(clipsConverted.ToArray());
|
|
}
|
|
TrackView.Selection.Clear();
|
|
TrackView.Selection.AddRange(clipsConverted.Select(clip => _tapeViewReplicator[clip]).NotNulls());
|
|
ScrollIntoView(clipsConverted);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Finds on which track to paste the current clips.
|
|
/// </summary>
|
|
private int? ComputeTargetTrackToPaste()
|
|
{
|
|
int? targetIndex = null;
|
|
var track = TrackView.FindControl<TapeTrackWidget>(PointToClient(Cursor.Position));
|
|
if (track != null)
|
|
{
|
|
targetIndex = track.Track.Index;
|
|
}
|
|
else if (TrackView.ActiveTrack is TapeTrackWidget)
|
|
{
|
|
targetIndex = ((TapeTrackWidget)TrackView.ActiveTrack).Track.Index;
|
|
}
|
|
return targetIndex;
|
|
}
|
|
|
|
/// <summary>
|
|
/// After a paste operation moves the clip so the first clip matches the current time.
|
|
/// </summary>
|
|
private void MoveClipsToCursor(Clip[] clips)
|
|
{
|
|
if (clips.Length > 0)
|
|
{
|
|
Point cursorLocation = PointToClient(Cursor.Position);
|
|
|
|
BeatUnit beat = Timeline.BeatFromTimeUnit(Timeline.PixelToTime(cursorLocation.X));
|
|
var snapped = Timeline.Wave.UnitConverter.Snap(beat, EditionOptions.CurrentSnappingUnit, 0.2f, 0.8f);
|
|
int currentFrame = (int)Timeline.FrameFromTime(Timeline.TimeUnitFromBeat(snapped));
|
|
|
|
float currentTime = Timeline.PixelToTime(cursorLocation.X);
|
|
if (Timeline.TimeCursor.IsDragging || !Timeline.VisibleTimeRange.Contains(currentTime))
|
|
{
|
|
currentFrame = (int)Math.Round(Timeline.FrameFromTime(Timeline.TimeCursor.Time));
|
|
}
|
|
|
|
int leftMost = clips.Min(clip => clip.StartFrame);
|
|
int offset = leftMost - currentFrame;
|
|
|
|
foreach (var clip in clips)
|
|
{
|
|
clip.StartFrame -= offset;
|
|
}
|
|
}
|
|
}
|
|
|
|
[ActionHandler(ZoomExtentsActions.ZoomExtents)]
|
|
public void ZoomExtents()
|
|
{
|
|
IEnumerable<Clip> toZoom = Selection.Select(view => view.Model).ToArray();
|
|
if (toZoom.IsEmpty())
|
|
{
|
|
// If no selection has been detected, we only retrieved the visible tracks.
|
|
int lastVisibleTrackIndex = TrackView.LastVisibleTrackIndex();
|
|
toZoom = _tape.Clips.Where(clip => clip.Track.Index >= TrackView.ScrollValue && clip.Track.Index <= lastVisibleTrackIndex).ToList();
|
|
}
|
|
|
|
Range<float> clipRange;
|
|
if (toZoom.IsEmpty())
|
|
{
|
|
clipRange = Timeline.TimeRange;
|
|
}
|
|
else
|
|
{
|
|
var min = toZoom.Min(clip => clip.StartBeat);
|
|
var max = toZoom.Max(clip => clip.StartBeat + clip.BeatDuration);
|
|
clipRange = new Range<float>(min, max);
|
|
clipRange = clipRange.Inflate(clipRange.Width() * .1f);
|
|
clipRange = Timeline.TimeRange.ClampRange(clipRange);
|
|
}
|
|
Timeline.ZoomExtents(clipRange);
|
|
|
|
ScrollIntoView(toZoom);
|
|
}
|
|
|
|
public void ScrollIntoView(Clip clip)
|
|
{
|
|
ScrollIntoView(new[] { clip });
|
|
}
|
|
|
|
public void ScrollIntoView(IEnumerable<Clip> clips)
|
|
{
|
|
if (clips.Any())
|
|
{
|
|
var tracksOfClips = clips.Select(clip => clip.Track.Index).ToArray();
|
|
if (tracksOfClips.Any())
|
|
{
|
|
TrackView.ScrollIntoView(new Range<int>(tracksOfClips.Min(), tracksOfClips.Max()));
|
|
}
|
|
|
|
var start = Timeline.TimeFromFrame(clips.Min(clip => clip.StartFrame));
|
|
var end = Timeline.TimeFromFrame(clips.Max(clip => clip.StopFrame));
|
|
var visibleRange = Timeline.VisibleTimeRange;
|
|
|
|
if (visibleRange.Min > start)
|
|
{
|
|
Timeline.VisibleTimeRange = new Range<float>(start, visibleRange.Max);
|
|
}
|
|
else if (visibleRange.Max < end)
|
|
{
|
|
Timeline.VisibleTimeRange = new Range<float>(visibleRange.Min, end);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
public void CenterView(Clip clip)
|
|
{
|
|
CenterView(new[] { clip });
|
|
}
|
|
|
|
public void CenterView(IEnumerable<Clip> clips)
|
|
{
|
|
if (clips.Any())
|
|
{
|
|
var tracksOfClips = clips.Select(clip => clip.Track.Index).ToArray();
|
|
if (tracksOfClips.Any())
|
|
{
|
|
TrackView.ScrollIntoView(new Range<int>(tracksOfClips.Min(), tracksOfClips.Max()));
|
|
}
|
|
|
|
var start = Timeline.TimeFromFrame(clips.Min(clip => clip.StartFrame));
|
|
var end = Timeline.TimeFromFrame(clips.Max(clip => clip.StopFrame));
|
|
var center = start + (end - start)/2;
|
|
var visibleRange = Timeline.VisibleTimeRange;
|
|
Timeline.TimeOffset = center - Timeline.VisibleTimeRange.Width()/2;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Nested
|
|
|
|
/// <summary>
|
|
/// Listens to the Actor collection and invalidates clips when their actor change.
|
|
/// </summary>
|
|
private class ActorInvalidator : IDisposable
|
|
{
|
|
private readonly TrackView _trackView;
|
|
private Set<ClipWithActorWidget> _clipsWithUnresolvedActors = new Set<ClipWithActorWidget>();
|
|
|
|
public ActorInvalidator(TrackView trackView)
|
|
{
|
|
_trackView = trackView;
|
|
if (TimelinePlugin.Instance != null)
|
|
{
|
|
TimelinePlugin.Instance.World.Actors.Refreshing += Actors_Refreshing;
|
|
TimelinePlugin.Instance.World.Actors.Refreshed += Actors_Refreshed;
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (TimelinePlugin.Instance != null)
|
|
{
|
|
TimelinePlugin.Instance.World.Actors.Refreshing -= Actors_Refreshing;
|
|
TimelinePlugin.Instance.World.Actors.Refreshed -= Actors_Refreshed;
|
|
}
|
|
}
|
|
|
|
private IEnumerable<ClipWithActorWidget> ComputeUnresolvedClips()
|
|
{
|
|
return _trackView.FindControls<ClipWithActorWidget>(clip => clip.Model.IsUnresolved());
|
|
}
|
|
|
|
void Actors_Refreshing(object sender, EventArgs e)
|
|
{
|
|
_clipsWithUnresolvedActors = ComputeUnresolvedClips().ToSet();
|
|
}
|
|
|
|
void Actors_Refreshed(object sender, EventArgs e)
|
|
{
|
|
ComputeUnresolvedClips().FindDifferences().Synchronize(_clipsWithUnresolvedActors, InvalidateClip, InvalidateClip);
|
|
}
|
|
|
|
private void InvalidateClip(ClipWithActorWidget widget)
|
|
{
|
|
widget.Invalidate();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Disable the live preview in the engine when dragging clips ... engine responds slowly and it doesnt really help anyway to preview those values.
|
|
/// </summary>
|
|
private class DraggingModerator
|
|
{
|
|
private IDisposable _suspendEventHandle;
|
|
|
|
public DraggingModerator(SequencerDraggingTool tool)
|
|
{
|
|
tool.Started += tool_Started;
|
|
tool.Ended += tool_Ended;
|
|
}
|
|
|
|
void tool_Ended(object sender, EventArgs e)
|
|
{
|
|
Dispose();
|
|
}
|
|
|
|
void tool_Started(object sender, EventArgs e)
|
|
{
|
|
Dispose();
|
|
if (TimelinePlugin.Instance != null)
|
|
{
|
|
_suspendEventHandle = TimelinePlugin.Instance.BatchOperation.Begin();
|
|
}
|
|
}
|
|
|
|
private void Dispose()
|
|
{
|
|
DisposableHelper.SafeDispose(ref _suspendEventHandle);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
[Serializable]
|
|
public class ClipClipboardData
|
|
{
|
|
public byte[] Content { get; set; }
|
|
}
|
|
}
|