747 lines
22 KiB
C#
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
|
|
}
|
|
}
|