JD2022-TU1/main/tools/framework/JD.Sequencers/Times/TrackView.cs

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);
}
}
}
}