387 lines
14 KiB
C#
387 lines
14 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Windows.Forms;
|
|
using JD.Application.View.Timeline;
|
|
using JD.Collections.Extensions;
|
|
using JD.Timeline.Model;
|
|
using JD.Timeline.Model.Clips;
|
|
using JD.Timeline.Model.Utility;
|
|
using JD.Timeline.Plugin;
|
|
using JD.Timeline.View.ClipView;
|
|
using JD.Timeline.View.ClipView.Widgets;
|
|
using JD.Timeline.View.Pads;
|
|
using TeaBox;
|
|
using JD.Core;
|
|
using JD.Core.ProjectManaging;
|
|
using JD.Kernel.Classes;
|
|
using JD.Kernel.StaticHelpers;
|
|
using JD.Sequencers.Windowlesses;
|
|
using JD.Serialization;
|
|
using TeaBox.Core.Source.Gui;
|
|
using Path = System.IO.Path;
|
|
|
|
namespace JD.Timeline.View.Actions
|
|
{
|
|
enum DragDropType
|
|
{
|
|
File,
|
|
MoveModel,
|
|
PictoModel,
|
|
Lyrics
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handles the drag drop logic on the Track View.
|
|
/// </summary>
|
|
public class ClipViewDragDropHandler
|
|
{
|
|
private const int ClipDurationInPixel = 200;
|
|
private readonly List<Clip> _draggingClips = new List<Clip>();
|
|
private readonly ClipViewControl _clipView;
|
|
private IDisposable _suspendEngineReplicationScope;
|
|
|
|
|
|
private DragDropType DragDropType { get; set; }
|
|
|
|
public ClipViewDragDropHandler(ClipViewControl clipView)
|
|
{
|
|
Throw.IfNull(clipView, "trackView");
|
|
|
|
_clipView = clipView;
|
|
clipView.DragOver += TrackViewDragOver;
|
|
clipView.DragDrop += TrackViewDragDrop;
|
|
clipView.DragLeave += TrackViewDragLeave;
|
|
}
|
|
|
|
#region Event Handlers
|
|
|
|
void TrackViewDragDrop(object sender, DragEventArgs e)
|
|
{
|
|
// Commit the operation.
|
|
InitializeSoundSets();
|
|
SetDraggingEdges(Edge.None);
|
|
|
|
_clipView.ClearSelection();
|
|
foreach (var clip in _draggingClips)
|
|
{
|
|
_clipView.AddToSelection(clip);
|
|
}
|
|
_draggingClips.Clear();
|
|
DisposableHelper.SafeDispose(ref _suspendEngineReplicationScope);
|
|
}
|
|
|
|
void TrackViewDragOver(object sender, DragEventArgs e)
|
|
{
|
|
bool canDrop = CanDrop;
|
|
bool isDragging = _draggingClips.Count != 0;
|
|
|
|
if (canDrop && !isDragging)
|
|
{
|
|
StartDrag(e);
|
|
}
|
|
else if (!canDrop && isDragging)
|
|
{
|
|
CancelDrag();
|
|
}
|
|
|
|
if (_draggingClips.Count > 0)
|
|
{
|
|
AssignClipBounds();
|
|
}
|
|
|
|
e.Effect = _draggingClips.Count > 0 ? DragDropEffects.Copy : DragDropEffects.None;
|
|
}
|
|
|
|
void TrackViewDragLeave(object sender, EventArgs e)
|
|
{
|
|
CancelDrag();
|
|
}
|
|
|
|
private void CancelDrag()
|
|
{
|
|
SetDraggingEdges(Edge.None);
|
|
foreach (var clip in _draggingClips)
|
|
{
|
|
_clipView.Tape.Clips.Remove(clip);
|
|
}
|
|
_draggingClips.Clear();
|
|
_clipView.Invalidate();
|
|
DisposableHelper.SafeDispose(ref _suspendEngineReplicationScope);
|
|
}
|
|
|
|
private void StartDrag(DragEventArgs e)
|
|
{
|
|
DisposableHelper.SafeDispose(ref _suspendEngineReplicationScope);
|
|
_suspendEngineReplicationScope = TimelinePlugin.Instance.BatchOperation.Begin();
|
|
_draggingClips.Clear();
|
|
_draggingClips.AddRange(CreateClipsForDragData(e));
|
|
|
|
// validate after the events are created so we can test if the current list count fits the space
|
|
// in the tracks.
|
|
if (!CanDrop)
|
|
{
|
|
CancelDrag();
|
|
}
|
|
else
|
|
{
|
|
AssignClipBounds();
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Utility
|
|
|
|
private void SetDraggingEdges(Edge edges)
|
|
{
|
|
foreach (var widget in DraggingClipWidgets)
|
|
{
|
|
widget.DraggingEdges = edges;
|
|
}
|
|
}
|
|
|
|
private IEnumerable<ClipWidget> DraggingClipWidgets
|
|
{
|
|
get { return _draggingClips.Select(clip => _clipView.ClipWidgets.FirstOrDefault(widget => widget.Model == clip)).NotNulls(); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// True if the current position allows dropping the current events.
|
|
/// </summary>
|
|
protected bool CanDrop
|
|
{
|
|
get
|
|
{
|
|
var target = DropTarget;
|
|
return target?.Index + _draggingClips.Count - 1 < _clipView.Tape.Tracks.Count;
|
|
}
|
|
}
|
|
|
|
private TapeTrack DropTarget
|
|
{
|
|
get
|
|
{
|
|
var point = _clipView.PointToClient(Cursor.Position);
|
|
return _clipView.TrackFromPoint(point.Y);
|
|
}
|
|
}
|
|
|
|
private IEnumerable<Clip> CreateClipsForDragData(DragEventArgs e)
|
|
{
|
|
if (e.Data.GetDataPresent(System.Windows.DataFormats.Serializable))
|
|
{
|
|
var data = e.Data.GetData(System.Windows.DataFormats.Serializable);
|
|
var point = _clipView.PointToClient(Cursor.Position);
|
|
int startFrame = (int)_clipView.FrameFromPoint(point.X);
|
|
var moveModelDragData = data as MoveModelDragData;
|
|
if (moveModelDragData != null)
|
|
{
|
|
if (DropTarget is MoveTrack)
|
|
{
|
|
DragDropType = DragDropType.MoveModel;
|
|
yield return DropTarget.InstantiateMotionClip(moveModelDragData.ModelName, startFrame);
|
|
}
|
|
}
|
|
var pictoModelDragData = data as PictoModelDragData;
|
|
if (pictoModelDragData != null)
|
|
{
|
|
if (DropTarget is PictoTrack)
|
|
{
|
|
DragDropType = DragDropType.PictoModel;
|
|
yield return DropTarget.InstantiatePictogramClip(pictoModelDragData.ModelName, startFrame);
|
|
}
|
|
}
|
|
var lyricParagraph = data as LyricParagraph;
|
|
if (lyricParagraph != null)
|
|
{
|
|
if (DropTarget is KaraokeTrack)
|
|
{
|
|
DragDropType = DragDropType.Lyrics;
|
|
foreach (var line in lyricParagraph.Lines)
|
|
{
|
|
var clip = DropTarget.InstantiateKaraokeClip(line.ToString(), startFrame);
|
|
clip.IsEndOfLine = true;
|
|
yield return clip;
|
|
}
|
|
}
|
|
}
|
|
var lyricLine = data as LyricLine;
|
|
if (lyricLine != null)
|
|
{
|
|
if (DropTarget is KaraokeTrack)
|
|
{
|
|
DragDropType = DragDropType.Lyrics;
|
|
var clip = DropTarget.InstantiateKaraokeClip(lyricLine.ToString(), startFrame);
|
|
clip.IsEndOfLine = true;
|
|
yield return clip;
|
|
}
|
|
}
|
|
}
|
|
else if (e.Data.GetDataPresent(DataFormats.FileDrop))
|
|
{
|
|
DragDropType = DragDropType.File;
|
|
var files = (string[])e.Data.GetData(DataFormats.FileDrop);
|
|
foreach (var file in files)
|
|
{
|
|
string extension = Path.GetExtension(file);
|
|
string relativeFile;
|
|
if (DataRoot.ExtractRelativePath(file, out relativeFile))
|
|
{
|
|
foreach (var newClip in CreateClipsForExtension(extension, relativeFile))
|
|
{
|
|
yield return newClip;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
string dataFolder = DataPath.Value.Replace("\\", "/");
|
|
string message = $"Dropped file is not in data folder.\n (The path must be under \"{dataFolder}\")";
|
|
MessageService.MessageService.ShowBalloon(TimeSpan.FromSeconds(15), "Could not import file", message, ToolTipIcon.Error);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private IEnumerable<Clip> CreateClipsForExtension(string extension, string relativeFile)
|
|
{
|
|
string actorName = Path.GetFileNameWithoutExtension(relativeFile);
|
|
|
|
switch (extension.ToLower())
|
|
{
|
|
case FileExtensions.Animation:
|
|
yield return new AnimationClip { ActorName = actorName };
|
|
break;
|
|
|
|
case FileExtensions.Actor:
|
|
case FileExtensions.Template:
|
|
{
|
|
var absolutePath = new JD.Serialization.Path(relativeFile).getAbsolute();
|
|
var components = (JD.Serialization.Serializers.LuaSerializer.LoadObjectFromFile(absolutePath) as Actor_Template)?.COMPONENTS;
|
|
|
|
// create audio clip present in actor
|
|
if (components != null && components.Any())
|
|
{
|
|
var soundComponents = components.OfType<SoundComponent_Template>().ToList();
|
|
if (soundComponents.Any())
|
|
{
|
|
yield return new SoundSetClip { SoundSetPath = new JD.Serialization.Path(relativeFile) };
|
|
}
|
|
|
|
// stop if actor contains only audio components
|
|
if (components.Count == soundComponents.Count)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
var existingActors = TimelinePlugin.Instance.World.Actors.Select(actor => actor.Name).ToArray();
|
|
|
|
var spawner = new SpawnActorClip
|
|
{
|
|
ActorPath = new JD.Serialization.Path(relativeFile),
|
|
ActorName = actorName.MakeUnique(existingActors)
|
|
};
|
|
yield return spawner;
|
|
break;
|
|
}
|
|
|
|
case FileExtensions.SoundSet:
|
|
{
|
|
string path = Path.ChangeExtension(relativeFile, "tpl");
|
|
yield return new SoundSetClip { SoundSetPath = new JD.Serialization.Path(path) };
|
|
break;
|
|
}
|
|
|
|
case FileExtensions.Tape:
|
|
{
|
|
yield return new TapeReferenceClip { TapeId = relativeFile };
|
|
break;
|
|
}
|
|
|
|
case FileExtensions.Vibration:
|
|
{
|
|
yield return new VibrationClip { VibrationFilePath = new JD.Serialization.Path(relativeFile) };
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void InitializeSoundSets()
|
|
{
|
|
foreach (SoundSetClip soundSet in _draggingClips.OfType<SoundSetClip>())
|
|
{
|
|
soundSet.CreateTemplateFile();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Assigns the bounds corresponding to current cursor location.
|
|
/// </summary>
|
|
private void AssignClipBounds()
|
|
{
|
|
SetDraggingEdges(Edge.Left | Edge.Right);
|
|
|
|
var point = _clipView.PointToClient(Cursor.Position);
|
|
TapeTrack track = DropTarget;
|
|
int startFrame = (int)_clipView.FrameFromPoint(point.X);
|
|
var clipsToConvert = _draggingClips.Where(x => x.TrackId != DropTarget.Id || x.Tape == null).ToList();
|
|
foreach (var clip in clipsToConvert)
|
|
{
|
|
var newClip = clip.ConvertToFit(DropTarget);
|
|
if (_clipView.Tape.Clips.Contains(clip))
|
|
{
|
|
_clipView.Tape.Clips.Remove(clip);
|
|
}
|
|
_draggingClips.Remove(clip);
|
|
_clipView.Tape.Clips.Add(newClip);
|
|
_draggingClips.Add(newClip);
|
|
}
|
|
|
|
foreach (var newClip in _draggingClips)
|
|
{
|
|
newClip.StartFrame = startFrame;
|
|
newClip.Track = track;
|
|
//The default behavior is to cascade every clips on different tracks
|
|
//we don't want that for lyrics
|
|
if (newClip != _draggingClips.Last() && DragDropType != DragDropType.Lyrics)
|
|
{
|
|
track = _clipView.Tape.GetOrCreateNextTrack(track);
|
|
}
|
|
}
|
|
|
|
if (DragDropType == DragDropType.File)
|
|
{
|
|
int duration = ConvertPixelDurationToFrame(ClipDurationInPixel);
|
|
foreach (var newClip in _draggingClips)
|
|
{
|
|
newClip.Duration = duration;
|
|
}
|
|
}
|
|
if (DragDropType == DragDropType.Lyrics)
|
|
{
|
|
//When we drag&drop lyrics from lyrics pad, we want to stretch all lyrics lines equally
|
|
//inside music structure section, at the time under the mouse cursor
|
|
var sectionStart = _clipView.Timeline.Wave().Annotations.SongSections.Reverse().FirstOrDefault(x => x.Time.Value < _draggingClips.First().StartBeat);
|
|
var sectionEnd = _clipView.Timeline.Wave().Annotations.SongSections.FirstOrDefault(x => x.Time.Value > _draggingClips.First().StartBeat);
|
|
if (sectionStart != null && sectionEnd != null)
|
|
{
|
|
var clipDuration = (sectionEnd.Time.Value - sectionStart.Time.Value) / _draggingClips.Count;
|
|
for (int i = 0 ; i < _draggingClips.Count ; i++)
|
|
{
|
|
var clip = _draggingClips[i];
|
|
clip.StartBeat = i > 0 ? _draggingClips[i - 1].StopBeat : sectionStart.Time.Value;
|
|
clip.StopBeat = i < _draggingClips.Count ? clip.StartBeat + clipDuration : sectionEnd.Time.Value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private int ConvertPixelDurationToFrame(int frameLength)
|
|
{
|
|
return (int)_clipView.Timeline.FrameFromPixelDuration(frameLength);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|