using QuikDawEditor.EditingClasses; using QuikDawEditor.MiscClasses; using QuikDawEditor.Undo; using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Runtime.CompilerServices; using System.Text.Json; using System.Text.RegularExpressions; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using static QuikDawEditor.EDITING.AudioMethods; using static QuikDawEditor.EDITING.MidiMethods; using static QuikDawEditor.EDITING.MiscMethods; using static QuikDawEditor.EDITING.StaticProperties; namespace QuikDawEditor; public partial class TrackContentControl : UserControl, INotifyPropertyChanged { public string RelRes() { ReleaseResources(); return "Resources released"; } public delegate void ChildClipMovedHandler(double xPos); public event ChildClipMovedHandler ChildClipMoved; public event PropertyChangedEventHandler PropertyChanged; private void NotifyPropertyChanged([CallerMemberName] String propertyName = "") { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } public delegate void UpdateProjectEndPointHandler(); public event UpdateProjectEndPointHandler UpdateProjectEndPoint; public void UpdateEndPoint() { UpdateProjectEndPoint(); } public void RefreshClips() { ClipUnitsContainer.Items.Refresh(); } public TrackContentControl() { InitializeComponent(); } private void ClipUnitsContainer_MouseDoubleClick(object sender, MouseButtonEventArgs e) { //projPlayer.StopPlayTimer(); // must stop mastermixingsampleprovider reading temporarily e.Handled = true; Track thisTrack = (Track)this.DataContext; if (thisTrack.trackType != TrackType.Midi) return; //Create a new empty midi clip double AddPositionMs = GetNearestSnapMs(PixelsToMsec(e.GetPosition(this).X), editingProject.CurrentSnapTo); Clip nextClip = thisTrack.Clips.Where(c => c.ClipLeftMs > AddPositionMs).FirstOrDefault(); int insertIdx = (nextClip == null) ? thisTrack.Clips.Count : nextClip.GetIndexInTrack; double newClipLeftMs = AddPositionMs; double newClipSourceLenMs = projPlayer.IsTimePlayScale ? 8000 : Math.Truncate(editingProject.BeatsPerMeasure * MillisecondsPerBeat); double maxnewClipWidthMs = (nextClip == null) ? newClipSourceLenMs : Math.Min(newClipSourceLenMs, nextClip.ClipLeftMs - newClipLeftMs); Clip newMidiClip = new Clip("", ClipType.Midi) { ClipSourceLenMilliseconds = newClipSourceLenMs, ClipWidthMs = newClipSourceLenMs }; Clip addedMidiClip = AddNewMidiClipToTrack(newMidiClip, thisTrack, insertIdx, AddPositionMs, maxnewClipWidthMs); undoActions.Add(new ClipAddUndo(addedMidiClip.UndoClipID)); } bool MouseDownOnClipLabel = false; Point ClipLabelRelativeMouseDown; double MouseDownOnTrackContentControlX = 0; double MouseDownOnTrackContentControlY = 0; bool ClipsWereMoved = false; double setClipVSMs; double premoveVS = 0; public void ClipControl_MouseDownOnClipLabel(ClipControl clipControl, MouseButtonEventArgs e) { e.Handled = true; TrackContentControlChildBeingModified = true; Clip thisClip = (Clip)clipControl.DataContext; MouseDownOnTrackContentControlX = e.GetPosition(this).X; MouseDownOnTrackContentControlY = e.GetPosition(this).Y; ClipLabelRelativeMouseDown = e.GetPosition(clipControl); MouseDownOnClipLabel = true; LastMouseX = e.GetPosition(this).X; premoveVS = thisClip.ClipVirtualStartMs; } public void ClipControl_MouseUpOnClipLabel(ClipControl clipControl, MouseButtonEventArgs e) { clipControl.ClipTopLabel.ReleaseMouseCapture(); vChangeMs = 0; Clip thisClip = (Clip)clipControl.DataContext; switch (ClipsWereMoved) { case true: double msMoved = thisClip.ClipVirtualStartMs - premoveVS; editingProject.NeedsSaving = true; if (thisClip == editingProject.GetProjectRightMostClip(editingProject.Tracks)) projPlayer.UpdateEndPoint(); ClipsWereMoved = false; if (editingProject.GetAllSelectedClips.Contains(thisClip)) { foreach (Clip c in editingProject.GetAllSelectedClips) c.ResetMe(projPlayer.CurrentPlayingPosMS); undoActions.Add(new ClipsVirtualStartsChangedUndoClass(editingProject.GetAllSelectedClips.ConvertAll(cc => cc.UndoClipID), msMoved)); } else { undoActions.Add(new ClipVirtualStartChangedUndoClass(thisClip.UndoClipID, premoveVS)); if (thisClip == thisClip.myTrack.GetCurrentPlayingClip) thisClip.ResetMe(projPlayer.CurrentPlayingPosMS); } break; case false: if (Keyboard.IsKeyDown(Key.LeftCtrl)) //multi-select clips thisClip.IsSelected = !thisClip.IsSelected; else //Select only this clip (or group) { bool clipSelectionState = thisClip.IsSelected; foreach (Track t in editingProject.GetAllTracksInProject) foreach (Clip c in t.Clips) c.IsSelected = false; thisClip.IsSelected = !clipSelectionState; if (thisClip.IsMidiClip) editingProject.pianoRoll.SetClip(thisClip); } break; } MouseDownOnClipLabel = false; MouseDownMovingOnClipLabel = false; //e.Handled = true; TrackContentControlChildBeingModified = false; } bool MouseDownMovingOnClipLabel = false; double LastMouseX; double MaxChangeLeft; double MaxChangeRight; double vChangeMs = 0; public void ClipControl_MouseMoveOnClipLabel(ClipControl clipControl, MouseEventArgs e) { if (projPlayer.AutoScrollingOn) return; Clip thisClip = (Clip)clipControl.DataContext; ClipControl thisCC = clipControl; if (Keyboard.IsKeyDown(Key.LeftCtrl)) { //Drag-drop of clipto same or different track if (MouseDownOnClipLabel) { double posXch = e.GetPosition(thisCC).X - ClipLabelRelativeMouseDown.X; double posYch = e.GetPosition(thisCC).Y - ClipLabelRelativeMouseDown.Y; //Debug.WriteLine("posxch=" + posXch.ToString() + "::::posych=" + posYch.ToString()); if (Math.Abs(posXch) > 3 | Math.Abs(posYch) > 3) { editingProject.clipFloatCanvas.offsetPoint = ClipLabelRelativeMouseDown; editingProject.clipFloatCanvas.imgSource = GetControlAsBitmapSource(thisCC.MainGrid); editingProject.clipFloatCanvas.sourceClip = thisClip; editingProject.clipFloatCanvas.SourceTrack = thisClip.myTrack; editingProject.clipFloatCanvas.Width = editingProject.clipFloatCanvas.imgSource.Width; editingProject.clipFloatCanvas.Height = editingProject.clipFloatCanvas.imgSource.Height; editingProject.clipFloatCanvas.IsVisible = true; MouseDownOnClipLabel = false; MouseDownMovingOnClipLabel = false; var ret = DragDrop.DoDragDrop(thisCC, "clipdrag", DragDropEffects.Copy | DragDropEffects.Move); ClipDropCompleted(); projPlayer.ClipSelectionRect.IsVisible = false; MouseDownOnClipLabel = false; return; } return; } } if (MouseDownMovingOnClipLabel) { //movement only within same track double moveDiffX = e.GetPosition(this).X - LastMouseX; switch (thisClip.IsSelected) { case true: //This is a selected clip, so do the same for any other selected clips List allSelectedClips = editingProject.GetAllSelectedClips; MaxChangeLeft = Math.Max(0, allSelectedClips.Where(sc => sc.IsSelectedLeftMostClipOnTrack).ToList().Min(cl => cl.GetDistanceToPreviousClipMs)); MaxChangeRight = Math.Max(0, allSelectedClips.Where(sc => sc.IsSelectedRightMostClipOnTrack).ToList().Min(cl => cl.GetDistanceToNextClipMs)); if ((Math.Sign(moveDiffX) == -1 && moveDiffX < -MaxChangeLeft) || (Math.Sign(moveDiffX) == 1 && moveDiffX > MaxChangeRight)) { vChangeMs = 0; return; } double diffToAdd = 0; if (editingProject.CurrentSnapTo == 2) { diffToAdd = (Math.Sign(moveDiffX) == 1) ? Math.Min(PixelsToMsec(moveDiffX), MaxChangeRight) : Math.Max(PixelsToMsec(moveDiffX), -MaxChangeLeft); foreach (Clip clip in allSelectedClips) clip.ClipVirtualStartMs = clip.ClipVirtualStartMs + diffToAdd; } else { //snap set vChangeMs += PixelsToMsec(moveDiffX); double snapResMs = GetSnapMsVal(editingProject.CurrentSnapTo); if (Math.Abs(vChangeMs) >= snapResMs) { diffToAdd = (Math.Sign(moveDiffX) == 1) ? Math.Min(vChangeMs, Math.Max(snapResMs, MaxChangeRight)) : Math.Min(vChangeMs, Math.Max(-snapResMs, -MaxChangeLeft)); foreach (Clip clip in allSelectedClips) { double newClipLeftMs = clip.ClipVirtualStartMs + clip.ClipRelativeLoopStartMs + diffToAdd; double nearestSnapMs = GetNearestSnapMs(newClipLeftMs, editingProject.CurrentSnapTo); clip.ClipVirtualStartMs = Math.Max(clip.GetLeftLimitMs - clip.ClipRelativeLoopStartMs, Math.Min(clip.GetRightLimitMs - clip.ClipRelativeLoopEndMs, nearestSnapMs - clip.ClipRelativeLoopStartMs)); } vChangeMs = 0; } } break; case false: //Just this clip MaxChangeLeft = thisClip.GetDistanceToPreviousClipMs; MaxChangeRight = thisClip.GetDistanceToNextClipMs; if ((Math.Sign(moveDiffX) == -1 && moveDiffX < -MaxChangeLeft) || (Math.Sign(moveDiffX) == 1 && moveDiffX > MaxChangeRight)) return; if (editingProject.CurrentSnapTo == 2) { //No snap double newClipVSMs = thisClip.ClipVirtualStartMs + PixelsToMsec(moveDiffX); thisClip.ClipVirtualStartMs = Math.Max(thisClip.GetLeftLimitMs - thisClip.ClipRelativeLoopStartMs, Math.Min(thisClip.GetRightLimitMs - thisClip.ClipRelativeLoopEndMs, newClipVSMs)); } else { //Snap to selected vChangeMs += PixelsToMsec(moveDiffX); double snapResMs = GetSnapMsVal(editingProject.CurrentSnapTo); if (Math.Abs(vChangeMs) >= snapResMs) { double newClipLeftMs = thisClip.ClipVirtualStartMs + thisClip.ClipRelativeLoopStartMs + vChangeMs; double nearestSnapMs = GetNearestSnapMs(newClipLeftMs, editingProject.CurrentSnapTo); thisClip.ClipVirtualStartMs = Math.Max(thisClip.GetLeftLimitMs - thisClip.ClipRelativeLoopStartMs, Math.Min(thisClip.GetRightLimitMs - thisClip.ClipRelativeLoopEndMs, nearestSnapMs - thisClip.ClipRelativeLoopStartMs)); vChangeMs = 0; } } ChildClipMoved(e.GetPosition(this).X * editingProject.ViewXZoomFac); ClipsWereMoved = true; break; } ClipsWereMoved = true; LastMouseX = e.GetPosition(this).X; return; } if (MouseDownOnClipLabel) { MouseDownOnClipLabel = false; //Normal moving of clip within track MouseDownMovingOnClipLabel = true; MouseDownOnTrackContentControlX = e.GetPosition(this).X; setClipVSMs = thisClip.ClipVirtualStartMs; clipControl.ClipTopLabel.CaptureMouse(); } } private void AutomationLaneControl_PreviewDragOver(object sender, DragEventArgs e) { e.Effects = DragDropEffects.None; e.Handled = true; ((Track)this.DataContext).DragOverAutomationLane = true; } private void ClipDropCompleted() { if (editingProject.clipFloatCanvas.sourceClip != null) editingProject.clipFloatCanvas.sourceClip.IsVisible = true; editingProject.clipFloatCanvas.IsVisible = false; editingProject.UpdateAllClips(editingProject.Tracks); } bool MouseDownOnTrackBottom = false; bool MouseOverTrackBottom = false; bool clipsContainerWasResized = false; private void clipsContainer_MouseMove(object sender, MouseEventArgs e) { if (MouseDownOnTrackBottom) { e.Handled = true; Track thisTrack = (Track)this.DataContext; switch (thisTrack.AutomationLanesVisible) { case true: thisTrack.TrackHeight = Math.Max(80, e.GetPosition(this).Y - 60); break; case false: thisTrack.TrackHeight = Math.Max(80, e.GetPosition(this).Y); break; } clipsContainerWasResized = true; return; } if (e.GetPosition(this).Y > this.ActualHeight - 6) { MouseOverTrackBottom = true; Cursor = Cursors.SizeNS; } else { MouseOverTrackBottom = false; Cursor = Cursors.Arrow; } } double previousTrackHeight = 1; private void clipsContainer_MouseDown(object sender, MouseButtonEventArgs e) { MouseDownOnTrackBottom = MouseOverTrackBottom; if (MouseDownOnTrackBottom) { Track thisTrack = (Track)this.DataContext; previousTrackHeight = thisTrack.TrackHeight; e.Handled = true; editingProject.ClipGainSliderVisibility = Visibility.Hidden; this.CaptureMouse(); } } private void clipsContainer_MouseUp(object sender, MouseButtonEventArgs e) { if (clipsContainerWasResized) { editingProject.NeedsSaving = true; clipsContainerWasResized = false; Track thisTrack = (Track)this.DataContext; undoActions.Add(new TrackHeightResizeUndo(thisTrack.UndoTrackID, previousTrackHeight)); } MouseDownOnTrackBottom = false; this.ReleaseMouseCapture(); } private void SubTrackContentControl_ChildClipMoved(double xPos) { ChildClipMoved(xPos); } private void clipsContainer_SizeChanged(object sender, SizeChangedEventArgs e) { TrackContentControl tccont = this; Track thisTrack = (Track)tccont.DataContext; double endMs = PixelsToMsec(tccont.ActualWidth); foreach (AutomationLane aclip in thisTrack.AutomationLanes) aclip.ClipEndAutoPoint.sourcePointms = endMs; foreach (AutomationLane aclip in editingProject.MasterTrack.AutomationLanes) aclip.ClipEndAutoPoint.sourcePointms = endMs; } Rect checkClipRect = new Rect(); private void clipsContainer_DragOver(object sender, DragEventArgs e) { e.Effects = DragDropEffects.None; TrackContentControl thisTCC = (TrackContentControl)sender; Track thisTrack = (Track)thisTCC.DataContext; e.Handled = true; if (thisTrack.DragOverAutomationLane) { thisTrack.DragOverAutomationLane = false; return; } if (thisTrack.IsSubmixTrack) { return; } if (e.Data.GetDataPresent(DataFormats.FileDrop)) { string file = ((string[])e.Data.GetData(DataFormats.FileDrop))[0]; string ext = System.IO.Path.GetExtension(file).ToLower(); switch (thisTrack.trackType) { case TrackType.Audio: if (Regex.IsMatch(ext, @"(\.mp3$|\.wav$)")) e.Effects = DragDropEffects.Copy; break; } } if (e.Data.GetDataPresent(DataFormats.StringFormat) && e.Data.GetData(DataFormats.StringFormat).ToString() == "clipdrag") { switch (thisTrack.trackType) { case TrackType.Audio: if (editingProject.clipFloatCanvas.sourceClip.clipType == ClipType.Midi) return; break; case TrackType.Midi: if (editingProject.clipFloatCanvas.sourceClip.clipType == ClipType.Audio) return; break; } double relCpointXMsec = PixelsToMsec(editingProject.clipFloatCanvas.Left); checkClipRect = new Rect(relCpointXMsec, 0, editingProject.clipFloatCanvas.sourceClip.ClipWidthMs, 60); DragDropEffects currDDE = Keyboard.IsKeyDown(Key.LeftCtrl) ? DragDropEffects.Copy : DragDropEffects.Move; bool ContainsCurrentPlayPoint = (projPlayer.CurrentPlayingPosMS > relCpointXMsec) && (projPlayer.CurrentPlayingPosMS < relCpointXMsec + editingProject.clipFloatCanvas.sourceClip.ClipWidthMs); bool UnderPlayPointAndPlaying = projPlayer.IsProjectPlaying && ContainsCurrentPlayPoint; e.Effects = (thisTrack.Clips.Where(c => c.OverlapsWithRect(checkClipRect)).Count() == 0 && !UnderPlayPointAndPlaying) ? currDDE : DragDropEffects.None; } } private void clipsContainer_Drop(object sender, DragEventArgs e) { e.Handled = true; if (e.Effects == DragDropEffects.None) return; TrackContentControl thisTCC = (TrackContentControl)sender; Track thisTrack = (Track)thisTCC.DataContext; if (e.Data.GetDataPresent(DataFormats.FileDrop)) { string[] files = (string[])e.Data.GetData(DataFormats.FileDrop); if (files.Length > 1) { MessageBox.Show("Only one file at a time", "", MessageBoxButton.OK, MessageBoxImage.Exclamation, MessageBoxResult.OK, MessageBoxOptions.DefaultDesktopOnly); return; } string fileToAdd = files[0]; string ext = System.IO.Path.GetExtension(fileToAdd).ToLower(); double AddPositionMs = PixelsToMsec(e.GetPosition(this).X); switch (thisTrack.trackType) { case TrackType.Audio: if (Regex.IsMatch(ext, @"(\.mp3$|\.wav$)")) AddNewAudioClipToTrack(files[0], thisTrack, AddPositionMs, -1, false, false); break; } return; } if (e.Data.GetDataPresent(DataFormats.StringFormat) && e.Data.GetData(DataFormats.StringFormat).ToString() == "clipdrag") { double AddPositionMs = PixelsToMsec(editingProject.clipFloatCanvas.Left); Clip insertBeforeClip = thisTrack.Clips.Where(c => c.ClipLeftMs > AddPositionMs).FirstOrDefault(); int insertIdx = insertBeforeClip == null ? thisTrack.Clips.Count : insertBeforeClip.GetIndexInTrack; if (Keyboard.IsKeyDown(Key.LeftCtrl)) //keyboard state must be reevaluated in drop { Dispatcher.InvokeAsync(() => { string clipJson = GetJsonForClip(editingProject.clipFloatCanvas.sourceClip); Clip dupeClip = (Clip)JsonSerializer.Deserialize(clipJson, typeof(Clip)); dupeClip.UndoClipID = Guid.NewGuid().ToString(); dupeClip.ClipName = editingProject.clipFloatCanvas.sourceClip.ClipName + " (*)"; dupeClip.ClipVirtualStartMs = AddPositionMs - dupeClip.ClipRelativeLoopStartMs; thisTrack.Clips.Insert(insertIdx, dupeClip); thisTrack.UpdateMyClipsToOwner(); if (dupeClip.IsAudioClip) { //Create clipsampleprovider for clip thisTrack.AddClipSampleProviderToClip(dupeClip); dupeClip.ApplyWaveBitmapSourcesToImage(); } undoActions.Add(new ClipAddUndo(dupeClip.UndoClipID)); editingProject.NeedsSaving = true; }, DispatcherPriority.Background); } else { //move clip to new position int removeIdx = editingProject.clipFloatCanvas.SourceTrack.Clips.IndexOf(editingProject.clipFloatCanvas.sourceClip); undoActions.Add(new ClipMoveUndo(editingProject.clipFloatCanvas.SourceTrack, removeIdx, editingProject.clipFloatCanvas.sourceClip, editingProject.clipFloatCanvas.sourceClip.ClipVirtualStartMs)); editingProject.clipFloatCanvas.sourceClip.ClipVirtualStartMs = AddPositionMs - editingProject.clipFloatCanvas.sourceClip.ClipRelativeLoopStartMs; editingProject.clipFloatCanvas.sourceClip.IsVisible = true; if (thisTrack != editingProject.clipFloatCanvas.SourceTrack) { thisTrack.Clips.Insert(insertIdx, editingProject.clipFloatCanvas.sourceClip); editingProject.clipFloatCanvas.SourceTrack.Clips.Remove(editingProject.clipFloatCanvas.sourceClip); } } thisTrack.SortAndUpdateClipIndexes(); editingProject.clipFloatCanvas.SourceTrack.UpdateMyClipsToOwner(); return; } } }