JD2022-TU1/main/tools/framework/JD.Application.TeaBox/View/WaveControl.cs

747 lines
22 KiB
C#

using JD.Application.Properties;
using JD.Application.View.Actions;
using JD.Application.View.Misc;
using JD.Application.View.Timeline;
using JD.Application.View.TrackItems;
using JD.Application.View.Tracks;
using JD.Application.View.WindowLess;
using JD.Audio.Annotations;
using JD.Audio.Sound;
using JD.Audio.SoundPlayer;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using JD.Collections;
using JD.Collections.Extensions;
using JD.Collections.Interfaces;
using JD.Interfaces.Player;
using JD.Kernel.StaticHelpers;
using JD.Kernel.Structs;
using JD.Sequencers.Controls;
using JD.Sequencers.Times;
using JD.Sequencers.Tools;
using JD.Sequencers.Windowlesses;
using TeaBox;
using TeaBox.Core.Source.Gui;
namespace JD.Application.View
{
using RangeF = Range<float>;
/// <summary>
/// The main control that holds the tracks.
/// </summary>
public partial class WaveControl : SequencerControlBase, IPlayerProvider
{
#region Variables
private readonly BeatsTimeline _timeline;
private IPlayer _player;
private Cursor _toRestore;
private Wave _wave;
private readonly AutoLoopBehavior _autoLoopBehavior;
private bool _showScrollbars = true;
private readonly List<WaveTrackBase> _waveTracks = new List<WaveTrackBase>();
private readonly SnapPainter _snapPainter = new SnapPainter();
private readonly SongBackground _background;
private readonly CommentEditor _commentEditor;
#endregion
#region Constructor
public WaveControl()
{
InitializeComponent();
_timeline = new BeatsTimeline(this);
_timeline.CurrentTimeChanged += _timeline_CurrentTimeChanged;
_autoLoopBehavior = new AutoLoopBehavior(TrackView);
_background = new SongBackground(_timeline);
_commentEditor = new CommentEditor(this) { Enabled = false };
TrackView.Controls.Insert(0, _background);
TooltipIntervalDelay = 50;
RootContainer.Controls.Add(_timeline);
var resizeTool = new ResizeTool(TrackView, Timeline);
TrackView.ToolManager.MouseDownTools.Insert(0, resizeTool);
TrackView.ToolManager.MouseMoveTools.Insert(0, resizeTool);
InitializeContextMenu();
Init();
}
void _timeline_CurrentTimeChanged(object sender, EventArgs e)
{
TimeSpan timeInSeconds = TimeSpan.FromSeconds(_timeline.ConvertTo(_timeline.TimeCursor.Time, TimeUnit.Seconds));
var frame = Math.Round(_timeline.ConvertTo(_timeline.TimeCursor.Time, TimeUnit.Frame));
TimeCursorPositionTextProvider.Instance.Text = timeInSeconds.ToString(@"mm\:ss\.f") + $" | Frame: {frame}";
}
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
// Force to update the range once the window is assigned its final size.
global::TeaBox.ObjectModel.Application.Main.Dispatcher.DispatchAsync(() =>
{
UpdateRange();
//Timeline.ZoomExtents();
Timeline.ResizingMode = global::JD.Sequencers.Times.Timeline.ResizeMode.PreserveScale;
}, forceAsync: true);
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
Wave = null;
Player = null;
_autoLoopBehavior.Dispose();
}
base.Dispose(disposing);
}
#endregion
#region Properties
protected SongBackground SongBackground
{
get { return _background; }
}
public IPlayer Player
{
get { return _player; }
set
{
if (_player != null)
{
_player.TimeChanged -= Player_TimeChanged;
}
_player = value;
_timeline.Player = value;
if (_player != null)
{
_player.TimeChanged += Player_TimeChanged;
_autoLoopBehavior.Loop = _player.Components.GetOrCreate<PlayerLoop>();
}
UpdatePlayerWidgets();
}
}
private void UpdatePlayerWidgets()
{
foreach (var widget in TrackView.Controls.Concat(Timeline.Controls).Where(w => w is LoopWidget || w is CheckpointWidget).OfType<LoopWidget>().ToArray())
{
widget.Parent.Controls.Remove(widget);
widget.Dispose();
}
if (_player != null)
{
var loop = _player.Components.GetOrCreate<PlayerLoop>();
TrackView.Controls.Insert(1, new LoopWidget(loop, _timeline));
Timeline.Controls.Add(new LoopWidget(loop, _timeline));
var checkpoint = _player.Components.GetOrCreate<Checkpoint>();
TrackView.Controls.Insert(1, new CheckpointWidget(checkpoint, _timeline));
}
}
void Player_TimeChanged(object sender, EventArgs e)
{
if (!Player.Playing)
return;
if (EditionOptions.ScrollingMode == ScrollingMode.Page)
{
var changePageRange = new Range<float>(Timeline.VisibleTimeRange.Min + 0.9f * Timeline.VisibleTimeRange.Width(), Timeline.VisibleTimeRange.Max);
if (!Timeline.VisibleTimeRange.Contains(Timeline.TimeCursor.Time))
{
Timeline.TimeOffset = Timeline.TimeCursor.Time - 0.1f * Timeline.VisibleTimeRange.Width();
}
else if (changePageRange.Contains(Timeline.TimeCursor.Time))
{
Timeline.TimeOffset = changePageRange.Min - 0.1f * Timeline.VisibleTimeRange.Width();
}
}
else if (EditionOptions.ScrollingMode == ScrollingMode.Continuous)
{
Timeline.TimeOffset = Timeline.TimeCursor.Time - Timeline.VisibleTimeRange.Width() / 2;
}
}
public BeatsTimeline Timeline
{
get { return _timeline; }
}
/// <summary>
/// When true, the user is allowed to scroll in the sound.
/// </summary>
public bool ShowScrollBars
{
get { return _showScrollbars; }
set
{
_showScrollbars = value;
_hScrollBar.Visible = value;
_vScrollBar.Visible = value;
}
}
public Wave Wave
{
get { return _wave; }
set
{
Throw.InvalidArgumentIf(value != null && value.Annotations.Markers.Count < 2, "Invalid wave", "wave");
if (_wave == value)
{
return;
}
if (_wave != null)
{
_wave.Annotations.Changed -= Annotations_Changed;
_wave.Annotations.PropertyChanged -= Annotations_PropertyChanged;
}
_wave = value;
_background.Wave = value;
if (_wave != null)
{
_wave.Annotations.Changed += Annotations_Changed;
_wave.Annotations.PropertyChanged += Annotations_PropertyChanged;
Timeline.Wave = value;
}
UpdateWaveTracks();
}
}
public Selection Selection
{
get { return TrackView.Selection; }
}
/// <summary>
/// The utillity textbox allowing to edit current comment.
/// </summary>
public CommentEditor CommentEditor
{
get { return _commentEditor; }
}
protected bool AllowEditComments
{
get { return _commentEditor.Enabled; }
}
#endregion
#region General Methods
protected virtual void UpdateRange()
{
if (Wave != null)
{
var rangeInBeat = ComputeRange();
var rangeInTime = new RangeF(Timeline.TimeUnitFromBeat(rangeInBeat.Min), Timeline.TimeUnitFromBeat(rangeInBeat.Max));
if (Timeline.TimeRange != rangeInTime)
{
Timeline.TimeRange = rangeInTime;
Timeline.ZoomExtents();
}
}
}
protected virtual Range<BeatUnit> ComputeRange()
{
return new Range<BeatUnit>(Wave.Annotations.StartBeat, Wave.Annotations.EndBeat);
}
protected override global::JD.Sequencers.Times.Timeline CreateTimeline()
{
return _timeline;
}
#endregion
#region Creation
private bool _showWaveComments;
/// <summary>
/// When true, the WaveTrack will display the comments.
/// </summary>
public bool ShowWaveComments
{
get { return _showWaveComments; }
set
{
if (value != _showWaveComments)
{
_showWaveComments = value;
UpdateWaveTracks();
}
}
}
protected void UpdateWaveTracks()
{
ClearWaveTracks();
if (_wave != null)
{
foreach (var track in CreateWaveTracks().Reverse())
{
_waveTracks.Add(track);
TrackView.InsertTrackAt(0, track);
}
UpdateRange();
}
}
private void ClearWaveTracks()
{
foreach (var track in _waveTracks)
{
track.Dispose();
TrackView.RemoveTrack(track);
}
_waveTracks.Clear();
}
/// <summary>
/// Creates the tracks that are used to display/edit the wave.
/// </summary>
protected virtual IEnumerable<WaveTrackBase> CreateWaveTracks()
{
var track = new WaveTrack(Wave, Wave.UnionChannel) { MinimumHeight = 92, MaximumHeight = 150 };
track.ShowSignatures();
if (ShowWaveComments)
{
track.ShowComments();
}
else
{
track.ShowSongSections();
}
track.Fixed = true;
foreach (var control in track.Controls)
{
control.Selectable = false;
}
yield return track;
}
protected virtual void InitializeContextMenu()
{
ContextMenu = null;
ContextMenuStrip = new TempoBlasterContextMenu(components, this);
}
#endregion
#region ScrollBars
protected override HScrollBar CreateHorizontalScrollbar()
{
return _hScrollBar;
}
protected override VScrollBar CreateVerticalScrollbar()
{
return _vScrollBar;
}
protected override void UpdateHorizontalScrollbar()
{
if (ShowScrollBars)
{
base.UpdateHorizontalScrollbar();
_hScrollBar.Visible = _hScrollBar.Minimum != _hScrollBar.Maximum;
}
}
#endregion
#region Cursor
public static bool IsStretchKeyPress
{
get { return Control.ModifierKeys.HasFlag(Keys.Shift); }
}
private void UpdateMouseCursor(Point cursorLocation)
{
var currentContainer = RootContainer.FindControl<WindowlessContainer>(cursorLocation);
var currentTool = currentContainer != null ? currentContainer.ToolManager.CurrentTool : null;
if ((currentTool == null && CanResizeHoveredObject()) || currentTool is ResizeTool)
{
_toRestore = Cursors.Default;
if (IsStretchKeyPress)
{
Cursor = Cursors.SizeWE;
}
else
{
Cursor = Cursors.VSplit;
}
}
else if ((currentTool == null && CanMoveHoveredObject()))
{
_toRestore = Cursors.Default;
Cursor = Cursors.Hand;
}
else if (currentTool is DraggingTool)
{
_toRestore = Cursors.Default;
Cursor = JD.Application.TeaBox.Properties.ResourcesCache.GrabCursor;
}
else if (_toRestore != null)
{
Cursor = _toRestore;
_toRestore = null;
}
}
protected override Cursor PanCursor(bool west)
{
return JD.Application.TeaBox.Properties.ResourcesCache.GrabCursor;
}
protected virtual bool CanMoveHoveredObject()
{
if (MouseButtons != MouseButtons.None)
{
return false;
}
var control = RootContainer.FindSelectableControl<WindowlessControl>(PointToClient(MousePosition)) as IMovable;
if (control != null && control.IsMoveAllowed)
{
return true;
}
return false;
}
protected virtual bool CanResizeHoveredObject()
{
if (MouseButtons != MouseButtons.None)
{
return false;
}
var resizeTool = TrackView.ToolManager.MouseMoveTools.FirstOf<ResizeTool>();
return resizeTool != null && resizeTool.CanStartResize(PointToClient(MousePosition));
}
#endregion
#region Zoom
protected override void HandleZoomOnMouseWheel(MouseEventArgs e)
{
ZoomOn(e.Delta, Timeline.PixelToTime(e.X));
}
public void ZoomOn(float factor, float time)
{
float zoomFactor = factor > 0.0f ? -0.15f : 0.15f;
RangeF currentRange = Timeline.VisibleTimeRange;
currentRange = currentRange.Inflate(currentRange.Width() * zoomFactor);
float currentRangeWidth = currentRange.Width();
if (currentRangeWidth > 0.1f)
{
float position = (time - Timeline.VisibleTimeRange.Min) / Timeline.VisibleTimeRange.Width();
float newPosition = (time - currentRange.Min) / currentRangeWidth;
float offset = newPosition - position;
currentRange = Offset(currentRange, currentRangeWidth * offset);
Timeline.VisibleTimeRange = currentRange;
}
}
private RangeF Offset(RangeF range, float value)
{
return new RangeF(range.Min + value, range.Max + value);
}
#endregion
#region Painting
/// <summary>
/// Overriden.
/// </summary>
/// <param name="e"></param>
protected override void OnPaint(PaintEventArgs e)
{
foreach (var debugTrack in TrackView.Controls.OfType<OverlappingTrack>())
{
debugTrack.UpdateBounds(TrackView);
}
base.OnPaint(e);
_snapPainter.PaintSnaps(TrackView, e.Graphics);
}
#endregion
#region Events
public event EventHandler WaveChanged;
/// <summary>
/// Triggers the WaveChanged event.
/// </summary>
protected virtual void OnWaveChanged(EventArgs e)
{
WaveChanged?.Invoke(this, e);
}
#endregion
#region Event Handlers
protected override void OnKeyDown(KeyEventArgs e)
{
UpdateMouseCursor(PointToClient(Cursor.Position));
base.OnKeyDown(e);
}
protected override void OnKeyUp(KeyEventArgs e)
{
UpdateMouseCursor(PointToClient(Cursor.Position));
base.OnKeyUp(e);
}
protected override void OnMouseMove(object sender, MouseEventArgs e)
{
base.OnMouseMove(sender, e);
UpdateMouseCursor(e.Location);
_snapPainter.Invalidate(TrackView);
}
protected override void OnDragOver(DragEventArgs e)
{
base.OnDragOver(e);
UpdateMouseCursor(new Point(e.X, e.Y));
_snapPainter.Invalidate(TrackView);
}
void Annotations_Changed(object sender, EventArgs e)
{
Invalidate();
}
void Annotations_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName.IsIn(AnnotationCollection.PropertyNames.StartBeat, AnnotationCollection.PropertyNames.EndBeat))
{
UpdateRange();
}
}
protected override void OnDoubleClick(EventArgs e)
{
var cursorPosition = PointToClient(Cursor.Position);
var trackItem = TrackItemFromLocation(cursorPosition);
if (trackItem != null)
{
OnDoubleClick(trackItem);
trackItem.OnDoubleClick();
}
else
{
var section = SectionFromLocation(cursorPosition);
if (section != null)
{
Timeline.TimeCursor.Time = Timeline.TimeUnitFromBeat(section.Time);
}
}
base.OnDoubleClick(e);
}
protected virtual void OnDoubleClick(LocalizedTrackItem trackItem)
{
Timeline.TimeCursor.Time = trackItem.Time;
}
protected override void OnMouseClick(MouseEventArgs e)
{
var cursorPosition = PointToClient(Cursor.Position);
var trackItem = TrackItemFromLocation(cursorPosition);
if (trackItem != null)
{
trackItem.OnMouseClick(e);
}
base.OnMouseClick(e);
}
protected LocalizedTrackItem TrackItemFromLocation(Point location)
{
return TrackView.ControlsRecursive()
.OfType<LocalizedTrackItem>()
.FirstOrDefault(item => item.Bounds.Contains(location));
}
protected SongSection SectionFromLocation(Point location)
{
var currentTrack = TrackView.FindControl<Track>(location);
if (currentTrack != null)
{
var timeUnit = _timeline.PixelToTime(location.X);
BeatUnit time = _timeline.BeatFromTimeUnit(timeUnit);
return _wave.UnitConverter.SongSectionFromTime(time);
}
return null;
}
#endregion
#region Action Handlers
public override bool PreProcessMessage(ref System.Windows.Forms.Message msg)
{
if (msg.Msg == global::TeaBox.Actions.ActionManager.WM_KEYDOWN)
{
long param = (long)msg.WParam;
Keys keys = (Keys)param | Control.ModifierKeys;
// Let the action manager handle the arrow keys.
if (keys.IsIn(Keys.Left, Keys.Right, Keys.Up, Keys.Down, Keys.Tab | Keys.Shift))
{
return false;
}
}
return base.PreProcessMessage(ref msg);
}
[ActionHandler(EditActions.Delete)]
private void HandleDelete()
{
var selection = Selection.OfType<AnnotationTrackItem>().ToArray(); // In a copy so we can safely modify the list during that time.
foreach (var item in selection)
{
item.Remove();
}
}
[ActionHandler(EditActions.SelectAll)]
private void HandleSelectAll()
{
var selection = Selection;
using (selection.BeginScopedUpdate())
{
selection.Clear();
CollectionExtensions.AddRange(selection, TrackView.ControlsRecursive().Where(control => control.Selectable));
}
}
[ActionHandler("EditorActions.ToggleSnap")]
public void ToggleSnap()
{
EditionOptions.CurrentSnappingUnit = EditionOptions.CurrentSnappingUnit == SnappingUnit.None ? EditionOptions.PreferredSnappingUnit : SnappingUnit.None;
}
[ActionHandler("EditActions.MoveUp")]
protected virtual void HandleMoveUp()
{
if (AllowEditComments)
{
MoveComments(-1);
}
}
[ActionHandler("EditActions.MoveDown")]
protected virtual void HandleMoveDown()
{
if (AllowEditComments)
{
MoveComments(+1);
}
}
private void MoveComments(int trackOffset)
{
var commentTypes = CommentTypeExtensions.CommentTypes.ToList();
var allowedRange = new Range<int>(0, commentTypes.Count - 1);
foreach (var comment in Selection.OfType<CommentTrackItem>().Select(widget => widget.Model).ToArray())
{
int currentIndex = commentTypes.IndexOf(comment.Type);
comment.Type = (CommentType)allowedRange.Clamp(currentIndex + trackOffset);
}
}
[ActionUpdateHandler("EditActions.MoveLeft")]
protected virtual void UpdateMoveLeft(ActionInfo info)
{
info.Enabled = false;
}
[ActionHandler("EditActions.MoveLeft")]
protected virtual void HandleMoveLeft()
{
}
[ActionUpdateHandler("EditActions.MoveRight")]
protected virtual void UpdateMoveRight(ActionInfo info)
{
info.Enabled = false;
}
[ActionHandler("EditActions.MoveRight")]
protected virtual void HandleMoveRight()
{
}
[ActionHandler("EditActions.SelectNext")]
protected virtual void HandleSelectNext()
{
}
[ActionHandler("EditActions.SelectPrevious")]
protected virtual void HandleSelectPrevious()
{
}
#endregion
}
}