598 lines
No EOL
16 KiB
C#
598 lines
No EOL
16 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Drawing;
|
|
using System.Drawing.Drawing2D;
|
|
using System.Linq;
|
|
using System.Windows.Forms;
|
|
using JD.Collections;
|
|
using JD.Collections.Interfaces;
|
|
using JD.Kernel.StaticHelpers;
|
|
using JD.Kernel.Structs;
|
|
using JD.Sequencers.Controls;
|
|
using JD.Sequencers.Tools;
|
|
using JD.Sequencers.Windowlesses;
|
|
using CollectionChangeAction = JD.Collections.CollectionChangeAction;
|
|
|
|
namespace JD.Sequencers.Times
|
|
{
|
|
public class TrackView : WindowlessContainer
|
|
{
|
|
public enum VerticalAlignment
|
|
{
|
|
Top,
|
|
Middle,
|
|
Bottom
|
|
}
|
|
|
|
private const int DefaultInnerTrackSpacing = 4;
|
|
private const int DefaultTrackHeight = 24;
|
|
private const int DefaultTrackSpacing = 0;
|
|
|
|
private readonly WindowlessControl _trackLayer = new WindowlessControl();
|
|
private readonly TrackCollection _tracks;
|
|
private Track _activeTrack;
|
|
private int _innerTrackSpacing = DefaultInnerTrackSpacing;
|
|
private int _scrollMaximum;
|
|
|
|
private int _scrollValue;
|
|
|
|
private bool _showCurrentTime = true;
|
|
private int _trackHeight = DefaultTrackHeight;
|
|
|
|
private int _trackSpacing = DefaultTrackSpacing;
|
|
|
|
private int _visibleTrackCount;
|
|
|
|
public Track ActiveTrack
|
|
{
|
|
get { return _activeTrack; }
|
|
set
|
|
{
|
|
if (_activeTrack != value)
|
|
{
|
|
_activeTrack = value;
|
|
ActiveTrackChanged?.Invoke(this, EventArgs.Empty);
|
|
Invalidate();
|
|
}
|
|
}
|
|
}
|
|
|
|
public int InnerTrackSpacing
|
|
{
|
|
get
|
|
{
|
|
return _innerTrackSpacing;
|
|
}
|
|
set
|
|
{
|
|
if (_innerTrackSpacing != value)
|
|
{
|
|
_innerTrackSpacing = value;
|
|
Invalidate();
|
|
}
|
|
}
|
|
}
|
|
|
|
public int ScrollMaximum
|
|
{
|
|
get { return _scrollMaximum; }
|
|
private set
|
|
{
|
|
if (_scrollMaximum != value)
|
|
{
|
|
_scrollMaximum = value;
|
|
OnScrollMaximumChanged(EventArgs.Empty);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 1 unit of scroll = 1 track
|
|
/// </summary>
|
|
public int ScrollValue
|
|
{
|
|
get { return _scrollValue; }
|
|
set
|
|
{
|
|
if (_scrollValue != value)
|
|
{
|
|
_scrollValue = value;
|
|
Invalidate();
|
|
ScrollValueChanged?.Invoke(this, EventArgs.Empty);
|
|
}
|
|
}
|
|
}
|
|
|
|
public int ScrollValueInPixel
|
|
{
|
|
get { return ScrollableTracks.Take(ScrollValue).Sum(track => track.Height); }
|
|
}
|
|
|
|
public bool ShowCurrentTime
|
|
{
|
|
get { return _showCurrentTime; }
|
|
set
|
|
{
|
|
if (_showCurrentTime != value)
|
|
{
|
|
_showCurrentTime = value;
|
|
Invalidate();
|
|
}
|
|
}
|
|
}
|
|
|
|
public Timeline Timeline { get; }
|
|
|
|
public int TrackHeight
|
|
{
|
|
get { return _trackHeight; }
|
|
set
|
|
{
|
|
if (_trackHeight == value)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_trackHeight = value;
|
|
lock (_lock)
|
|
{
|
|
foreach (var track in _tracks)
|
|
{
|
|
track.Height = _trackHeight;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public VerticalAlignment TrackItemAlignment { get; set; } = VerticalAlignment.Bottom;
|
|
|
|
/// <summary>
|
|
/// The tracks contained in this track view.
|
|
/// </summary>
|
|
//public IObservableList<Track> Tracks => _tracks;
|
|
|
|
public int TrackSpacing
|
|
{
|
|
get { return _trackSpacing; }
|
|
set
|
|
{
|
|
if (_trackSpacing != value)
|
|
{
|
|
_trackSpacing = value;
|
|
Invalidate();
|
|
}
|
|
}
|
|
}
|
|
|
|
private IEnumerable<Track> ScrollableTracks
|
|
{
|
|
get
|
|
{
|
|
var scrollableTracks = new List<Track>();
|
|
lock (_lock)
|
|
{
|
|
foreach (var track in _tracks)
|
|
{
|
|
if (track.IsVisible && !track.Fixed)
|
|
{
|
|
scrollableTracks.Add(track);
|
|
}
|
|
}
|
|
}
|
|
|
|
return scrollableTracks;
|
|
}
|
|
}
|
|
|
|
private int VisibleTrackCount
|
|
{
|
|
get { return _visibleTrackCount; }
|
|
set
|
|
{
|
|
if (_visibleTrackCount != value)
|
|
{
|
|
_visibleTrackCount = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
public event EventHandler<CollectionChangedEventArgs<Track>> CollectionChanged
|
|
{
|
|
add
|
|
{
|
|
lock (_lock)
|
|
{
|
|
_tracks.CollectionChanged -= value;
|
|
_tracks.CollectionChanged += value;
|
|
}
|
|
}
|
|
remove
|
|
{
|
|
lock (_lock)
|
|
{
|
|
_tracks.CollectionChanged -= value;
|
|
}
|
|
}
|
|
}
|
|
|
|
public TrackView(IPhysicalControl owner, Timeline timeline)
|
|
: base(owner)
|
|
{
|
|
Throw.IfNull(timeline, "timeline");
|
|
Timeline = timeline;
|
|
|
|
Controls.Add(_trackLayer);
|
|
_trackLayer.CaptureInput = false;
|
|
|
|
_tracks = new TrackCollection(_trackLayer.Controls);
|
|
_tracks.CollectionChanged += m_tracks_CollectionChanged;
|
|
|
|
Timeline.CurrentTimeChanging += Timeline_CurrentTimeChanging;
|
|
Timeline.TimeOffsetChanged += Timeline_TimeOffsetChanged;
|
|
Timeline.PixelsPerTimeChanged += Timeline_TimeScaleChanged;
|
|
|
|
ToolManager.MouseDownTools.Add(new SelectingTool(this));
|
|
|
|
var draggingTool = new SequencerDraggingTool(this, timeline);
|
|
draggingTool.Starting += draggingTool_Starting;
|
|
ToolManager.MouseMoveTools.Add(draggingTool);
|
|
|
|
ToolManager.MouseMoveTools.Add(new TrackViewRubberBandingTool(this));
|
|
}
|
|
|
|
public event EventHandler ActiveTrackChanged;
|
|
|
|
public int LastVisibleTrackIndex()
|
|
{
|
|
return LastVisibleTrackIndex(ScrollValue);
|
|
}
|
|
|
|
public void ScrollIntoView(Range<int> trackRange)
|
|
{
|
|
if (trackRange.Min < ScrollValue)
|
|
{
|
|
ScrollValue = trackRange.Min;
|
|
}
|
|
else
|
|
{
|
|
var scrollValue = ScrollValue;
|
|
while (LastVisibleTrackIndex(scrollValue) < trackRange.Max && scrollValue < ScrollMaximum && scrollValue < trackRange.Min)
|
|
{
|
|
scrollValue++;
|
|
}
|
|
|
|
ScrollValue = scrollValue;
|
|
}
|
|
}
|
|
|
|
public void SelectAll<TControl>(Predicate<TControl> predicate) where TControl : WindowlessControl
|
|
{
|
|
using (Selection.BeginScopedUpdate())
|
|
{
|
|
Selection.Clear();
|
|
foreach (var control in FindControls(predicate))
|
|
{
|
|
Selection.Add(control);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ComputeChildBounds(Graphics g)
|
|
{
|
|
var trackPosition = Location.Y + TrackSpacing;
|
|
var visibleTrackCount = 0;
|
|
|
|
var tracks = GetTracks();
|
|
foreach (var track in tracks)
|
|
{
|
|
track.ComputeChildBounds(g);
|
|
}
|
|
|
|
foreach (var track in tracks)
|
|
{
|
|
if (!track.IsVisible)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (visibleTrackCount >= ScrollValue || track.Fixed)
|
|
{
|
|
// Track is visible on screen.
|
|
track.Location = new Point(0, trackPosition);
|
|
track.Width = Width;
|
|
trackPosition += track.Height + TrackSpacing;
|
|
}
|
|
else
|
|
{
|
|
// Offscreen tracks are placed outside the control's bounds
|
|
track.Location = new Point(0, -track.Height);
|
|
}
|
|
|
|
if (!track.Fixed)
|
|
{
|
|
visibleTrackCount++;
|
|
}
|
|
}
|
|
|
|
foreach (var track in tracks)
|
|
{
|
|
track.FinalizeChildBounds(g);
|
|
}
|
|
|
|
|
|
VisibleTrackCount = visibleTrackCount;
|
|
}
|
|
|
|
private readonly object _lock = new object();
|
|
public System.Collections.Generic.IReadOnlyCollection<Track> GetTracks()
|
|
{
|
|
lock (_lock)
|
|
{
|
|
return new List<Track>(_tracks);
|
|
}
|
|
}
|
|
|
|
public int GetTrackIndex(Track track)
|
|
{
|
|
lock (_lock)
|
|
{
|
|
return _tracks.IndexOf(track);
|
|
}
|
|
}
|
|
|
|
private int GetFixedHeaderSize()
|
|
{
|
|
int headerSize = 0;
|
|
lock (_lock)
|
|
{
|
|
foreach (var track in _tracks)
|
|
{
|
|
if (track.IsVisible && track.Fixed)
|
|
{
|
|
headerSize += track.Height;
|
|
}
|
|
}
|
|
}
|
|
|
|
return headerSize;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates the ScrollMaximum value by determining what is the last
|
|
/// track index that can be used to show as first track, and still see the
|
|
/// remaining tracks fully in the control.
|
|
/// </summary>
|
|
private void ComputeScrollMaximum()
|
|
{
|
|
var fixedHeaderSize = GetFixedHeaderSize();
|
|
|
|
var scrollableTracks = ScrollableTracks.ToList();
|
|
scrollableTracks.Reverse();
|
|
|
|
var availableHeight = Height - fixedHeaderSize - SystemInformation.HorizontalScrollBarHeight;
|
|
var availableScrollableTrackCount = scrollableTracks.Count(track =>
|
|
{
|
|
availableHeight -= track.Height;
|
|
return availableHeight > 0;
|
|
});
|
|
|
|
ScrollMaximum = scrollableTracks.Count - availableScrollableTrackCount;
|
|
}
|
|
|
|
private int LastVisibleTrackIndex(int scrollValue)
|
|
{
|
|
var fixedHeaderSize = GetFixedHeaderSize();
|
|
|
|
var availableHeight = Height - fixedHeaderSize - SystemInformation.HorizontalScrollBarHeight;
|
|
var trackIndex = scrollValue - 1;
|
|
|
|
foreach (var track in ScrollableTracks.Skip(scrollValue))
|
|
{
|
|
if (track.IsVisible)
|
|
{
|
|
availableHeight -= track.Height;
|
|
}
|
|
|
|
if (availableHeight < 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
trackIndex++;
|
|
}
|
|
|
|
return trackIndex;
|
|
}
|
|
|
|
#region Inner Types
|
|
|
|
private class TrackCollection : AttachedControlCollection<Track>
|
|
{
|
|
public TrackCollection(IObservableList<WindowlessControl> originalCollection)
|
|
: base(originalCollection)
|
|
{
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Paint
|
|
|
|
protected override void OnPaintSelf(PaintEventArgs e)
|
|
{
|
|
base.OnPaintSelf(e);
|
|
using (var b = new LinearGradientBrush(Bounds, System.Drawing.Color.FromArgb(100, 100, 100), System.Drawing.Color.FromArgb(70, 70, 70), 90))
|
|
{
|
|
e.Graphics.FillRectangle(b, Bounds);
|
|
}
|
|
}
|
|
|
|
protected internal override void OnPaint(PaintEventArgs e)
|
|
{
|
|
ComputeChildBounds(e.Graphics);
|
|
|
|
ComputeScrollMaximum();
|
|
|
|
base.OnPaint(e);
|
|
|
|
PaintCurrentTime(e);
|
|
}
|
|
|
|
private void PaintCurrentTime(PaintEventArgs e)
|
|
{
|
|
if (ShowCurrentTime && IsVisible && !TimePaint.Handle(this, e.Graphics))
|
|
{
|
|
Timeline.PaintCurrentTime(e.Graphics, Bounds);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Event Handlers
|
|
|
|
protected internal override void OnMouseUp(MouseEventArgs e)
|
|
{
|
|
var dragSize = SystemInformation.DragSize;
|
|
var p1 = LastMouseDownEventArgs.Location;
|
|
var p2 = e.Location;
|
|
|
|
if (System.Math.Abs(p1.X - p2.X) <= dragSize.Width / 2 && System.Math.Abs(p1.Y - p2.Y) <= dragSize.Height / 2 && e.Button == MouseButtons.Left)
|
|
{
|
|
var location = new Point(e.X, e.Y);
|
|
ActiveTrack = FindControl<Track>(location) ?? ActiveTrack;
|
|
}
|
|
|
|
base.OnMouseUp(e);
|
|
}
|
|
|
|
private void m_tracks_CollectionChanged(object sender, CollectionChangedEventArgs<Track> e)
|
|
{
|
|
_tracks.Synchronize(e, RegisterTrack, UnregisterTrack);
|
|
|
|
if (e.Action == CollectionChangeAction.ItemAdded)
|
|
{
|
|
e.Item.Initialize();
|
|
}
|
|
}
|
|
|
|
private void RegisterTrack(Track track)
|
|
{
|
|
track.TrackView = this;
|
|
track.SizeChanged += track_SizeChanged;
|
|
|
|
if (track.Height == 0)
|
|
{
|
|
track.Height = TrackHeight;
|
|
}
|
|
}
|
|
|
|
private void UnregisterTrack(Track track)
|
|
{
|
|
if (ActiveTrack == track)
|
|
{
|
|
ActiveTrack = null;
|
|
}
|
|
|
|
track.Dispose();
|
|
track.TrackView = null;
|
|
track.SizeChanged -= track_SizeChanged;
|
|
}
|
|
|
|
private void track_SizeChanged(object sender, EventArgs e)
|
|
{
|
|
ComputeScrollMaximum();
|
|
TrackSizeChanged?.Invoke(this, e);
|
|
}
|
|
|
|
private void Timeline_CurrentTimeChanging(object sender, Guide.MarkerCancelEventArgs e)
|
|
{
|
|
InvalidateTimePosition(Timeline.TimeCursor.Time);
|
|
InvalidateTimePosition(e.NewTime);
|
|
}
|
|
|
|
private void InvalidateTimePosition(float time)
|
|
{
|
|
const int timeWidth = 4;
|
|
var pixel = Timeline.TimeToPixel(time) - timeWidth / 2;
|
|
Invalidate(new Rectangle(pixel, Bounds.Top, timeWidth, Bounds.Height));
|
|
}
|
|
|
|
private void Timeline_TimeScaleChanged(object sender, EventArgs e)
|
|
{
|
|
lock (_lock)
|
|
{
|
|
foreach (var track in _tracks)
|
|
{
|
|
track.OnTimeScaleChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void Timeline_TimeOffsetChanged(object sender, EventArgs e)
|
|
{
|
|
lock (_lock)
|
|
{
|
|
foreach (var track in _tracks)
|
|
{
|
|
track.OnTimeOffsetChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void draggingTool_Starting(object sender, CancelEventArgs e)
|
|
{
|
|
OnSelectionMoving(e);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Events
|
|
|
|
public event EventHandler ScrollMaximumChanged;
|
|
|
|
protected void OnScrollMaximumChanged(EventArgs e)
|
|
{
|
|
ScrollMaximumChanged?.Invoke(this, e);
|
|
}
|
|
|
|
public event EventHandler<CancelEventArgs> SelectionMoving;
|
|
|
|
protected void OnSelectionMoving(CancelEventArgs e)
|
|
{
|
|
SelectionMoving?.Invoke(this, e);
|
|
}
|
|
|
|
public event EventHandler TrackSizeChanged;
|
|
|
|
public event EventHandler<OverridePaintEventArgs> TimePaint;
|
|
|
|
public event EventHandler ScrollValueChanged;
|
|
|
|
#endregion
|
|
|
|
public void RemoveTrack(Track track)
|
|
{
|
|
lock (_lock)
|
|
{
|
|
_tracks.Remove(track);
|
|
}
|
|
}
|
|
|
|
public void AddTrack(Track track)
|
|
{
|
|
lock (_lock)
|
|
{
|
|
_tracks.Add(track);
|
|
}
|
|
}
|
|
|
|
public void InsertTrackAt(int index, Track track)
|
|
{
|
|
lock (_lock)
|
|
{
|
|
_tracks.Insert(index, track);
|
|
}
|
|
}
|
|
}
|
|
} |