diff --git a/src/BizHawk.Client.Common/inputAdapters/MultitrackAdapter.cs b/src/BizHawk.Client.Common/inputAdapters/MultitrackAdapter.cs new file mode 100644 index 00000000000..7bd3ab1603a --- /dev/null +++ b/src/BizHawk.Client.Common/inputAdapters/MultitrackAdapter.cs @@ -0,0 +1,58 @@ +#nullable enable + +using System.Collections.Generic; +using BizHawk.Emulation.Common; + +namespace BizHawk.Client.Common +{ + /// + /// Used to enable recording a subset of a controller's buttons, while keeping the existing inputs for the other buttons. + /// Also used to allow TAStudio to clear (or otherwise edit) a subset of input columns. + /// + internal class MultitrackAdapter : IController + { + /// + /// Input states in this definition will come from . All others will come from . + /// + public ControllerDefinition ActiveDefinition { get; set; } + + public IController ActiveSource { get; set; } + + public IController BackingSource { get; set; } + + public ControllerDefinition Definition => BackingSource.Definition; + + public MultitrackAdapter(IController active, IController backing, ControllerDefinition activeDefinition) + { + ActiveSource = active; + BackingSource = backing; + ActiveDefinition = activeDefinition; + } + + public bool IsPressed(string button) + { + if (ActiveDefinition.BoolButtons.Contains(button)) + { + return ActiveSource.IsPressed(button); + } + else + { + return BackingSource.IsPressed(button); + } + } + public int AxisValue(string name) + { + if (ActiveDefinition.Axes.ContainsKey(name)) + { + return ActiveSource.AxisValue(name); + } + else + { + return BackingSource.AxisValue(name); + } + } + + public IReadOnlyCollection<(string Name, int Strength)> GetHapticsSnapshot() => throw new NotImplementedException(); // don't use this + public void SetHapticChannelStrength(string name, int strength) => throw new NotImplementedException(); // don't use this + } +} diff --git a/src/BizHawk.Client.Common/movie/bk2/Bk2Movie.cs b/src/BizHawk.Client.Common/movie/bk2/Bk2Movie.cs index 3c14d94c7ef..88fba4306e4 100644 --- a/src/BizHawk.Client.Common/movie/bk2/Bk2Movie.cs +++ b/src/BizHawk.Client.Common/movie/bk2/Bk2Movie.cs @@ -5,7 +5,16 @@ namespace BizHawk.Client.Common { public partial class Bk2Movie : BasicMovieInfo, IMovie { - private Bk2Controller _adapter; + private IController _defaultValueController; + protected IController DefaultValueController + { + get + { + // LogKey isn't available at construction time, so we have to create this instance when it is accessed. + _defaultValueController ??= new Bk2Controller(Session.MovieController.Definition, LogKey); + return _defaultValueController; + } + } public Bk2Movie(IMovieSession session, string filename) : base(filename) { @@ -18,6 +27,17 @@ public virtual void Attach(IEmulator emulator) Emulator = emulator; } + private ControllerDefinition/*?*/ _activeControllerInputs = null; + public virtual ControllerDefinition/*?*/ ActiveControllerInputs + { + get => _activeControllerInputs; + set + { + value?.AssertImmutable(); + _activeControllerInputs = value; + } + } + protected bool IsAttached() => Emulator != null; public IEmulator Emulator { get; private set; } @@ -48,6 +68,11 @@ public void CopyLog(IEnumerable log) public void AppendFrame(IController source) { + if (ActiveControllerInputs != null) + { + source = new MultitrackAdapter(source, new Bk2Controller(Session.MovieController.Definition, LogKey), ActiveControllerInputs); + } + Log.Add(Bk2LogEntryGenerator.GenerateLogEntry(source)); Changes = true; } @@ -62,15 +87,24 @@ public virtual void RecordFrame(int frame, IController source) } } - SetFrameAt(frame, Bk2LogEntryGenerator.GenerateLogEntry(source)); - - Changes = true; + PokeFrame(frame, source); } public virtual void Truncate(int frame) { if (frame < Log.Count) { + if (ActiveControllerInputs != null) + { + for (int i = frame; i < Log.Count; i++) + PokeFrame(i, DefaultValueController); + string defaultEntry = Bk2LogEntryGenerator.EmptyEntry(DefaultValueController); + int firstDefault = Log.Count; + while (firstDefault > frame && Log[firstDefault - 1] == defaultEntry) + firstDefault--; + frame = firstDefault; + } + Log.RemoveRange(frame, Log.Count - frame); Changes = true; } @@ -80,9 +114,9 @@ public IMovieController GetInputState(int frame) { if (frame < FrameCount && frame >= -1) { - _adapter ??= new Bk2Controller(Session.MovieController.Definition, LogKey); - _adapter.SetFromMnemonic(frame >= 0 ? Log[frame] : Bk2LogEntryGenerator.EmptyEntry(_adapter)); - return _adapter; + Bk2Controller controller = new(Session.MovieController.Definition, LogKey); + controller.SetFromMnemonic(frame >= 0 ? Log[frame] : Bk2LogEntryGenerator.EmptyEntry(controller)); + return controller; } return null; @@ -90,10 +124,18 @@ public IMovieController GetInputState(int frame) public virtual void PokeFrame(int frame, IController source) { + if (ActiveControllerInputs != null) + { + source = new MultitrackAdapter(source, GetInputState(frame) ?? DefaultValueController, ActiveControllerInputs); + } + SetFrameAt(frame, Bk2LogEntryGenerator.GenerateLogEntry(source)); Changes = true; } + /// + /// Does not use . + /// protected void SetFrameAt(int frameNum, string frame) { if (Log.Count > frameNum) diff --git a/src/BizHawk.Client.Common/movie/interfaces/IMovie.cs b/src/BizHawk.Client.Common/movie/interfaces/IMovie.cs index ac3d276eea5..3455f2e0ced 100644 --- a/src/BizHawk.Client.Common/movie/interfaces/IMovie.cs +++ b/src/BizHawk.Client.Common/movie/interfaces/IMovie.cs @@ -33,6 +33,13 @@ public enum MovieMode // TODO: consider other event handlers, switching modes? public interface IMovie : IBasicMovieInfo { + /// + /// When not null, methods that edit the movie will only affect buttons or axis values present in this definition, + /// with the exceoption of adding or removing default-value frames at the end of the movie. + /// The instance must be made immutable before assignment. + /// + ControllerDefinition/*?*/ ActiveControllerInputs { get; set; } + /// /// Gets the current movie mode /// @@ -161,6 +168,7 @@ public interface IMovie : IBasicMovieInfo /// /// Instructs the movie to remove all input from its input log starting with the input at frame. + /// If is not null, this might not actually change the length of the movie. /// /// The frame at which to truncate void Truncate(int frame); diff --git a/src/BizHawk.Client.Common/movie/interfaces/ITasMovie.cs b/src/BizHawk.Client.Common/movie/interfaces/ITasMovie.cs index 4012c9542d0..1daf4fcc4b3 100644 --- a/src/BizHawk.Client.Common/movie/interfaces/ITasMovie.cs +++ b/src/BizHawk.Client.Common/movie/interfaces/ITasMovie.cs @@ -55,11 +55,12 @@ public interface ITasMovie : IMovie, INotifyPropertyChanged, IDisposable /// /// Remove all frames between removeStart and removeUpTo (excluding removeUpTo). + /// If is not null, this will not actually change the length of the movie. /// /// The first frame to remove. /// The frame after the last frame to remove. void RemoveFrames(int removeStart, int removeUpTo); - void SetFrame(int frame, string source); + void PokeFrame(int frame, string source); void LoadBranch(TasBranch branch); diff --git a/src/BizHawk.Client.Common/movie/tasproj/TasMovie.Editing.cs b/src/BizHawk.Client.Common/movie/tasproj/TasMovie.Editing.cs index f836d705da3..943ee54f626 100644 --- a/src/BizHawk.Client.Common/movie/tasproj/TasMovie.Editing.cs +++ b/src/BizHawk.Client.Common/movie/tasproj/TasMovie.Editing.cs @@ -9,6 +9,18 @@ internal partial class TasMovie { public IMovieChangeLog ChangeLog { get; set; } + private Dictionary _inputMoveCache = new(); + + public override ControllerDefinition ActiveControllerInputs + { + get => base.ActiveControllerInputs; + set + { + base.ActiveControllerInputs = value; + _inputMoveCache.Clear(); + } + } + // In each editing method we do things in this order: // 1) Any special logic (such as short-circuit) // 2) Begin an undo batch, if needed. @@ -21,7 +33,7 @@ internal partial class TasMovie public override void RecordFrame(int frame, IController source) { ChangeLog.AddGeneralUndo(frame, frame, $"Record Frame: {frame}"); - SetFrameAt(frame, Bk2LogEntryGenerator.GenerateLogEntry(source)); + base.PokeFrame(frame, source); ChangeLog.SetGeneralRedo(); LagLog[frame] = _inputPollable.IsLagFrame; @@ -32,7 +44,7 @@ public override void RecordFrame(int frame, IController source) public override void Truncate(int frame) { - if (frame >= Log.Count - 1) return; + if (frame >= Log.Count) return; bool endBatch = ChangeLog.BeginNewBatch($"Truncate Movie: {frame}", true); @@ -56,22 +68,17 @@ public override void PokeFrame(int frame, IController source) InvalidateAfter(frame); } - public void SetFrame(int frame, string source) + public void PokeFrame(int frame, string source) { - ChangeLog.AddGeneralUndo(frame, frame, $"Set Frame At: {frame}"); - SetFrameAt(frame, source); - ChangeLog.SetGeneralRedo(); - - InvalidateAfter(frame); + Bk2Controller controller = new(Session.MovieController.Definition, LogKey); + controller.SetFromMnemonic(source); + PokeFrame(frame, controller); } public void ClearFrame(int frame) { - string empty = Bk2LogEntryGenerator.EmptyEntry(Session.MovieController); - if (GetInputLogEntry(frame) == empty) return; - ChangeLog.AddGeneralUndo(frame, frame, $"Clear Frame: {frame}"); - SetFrameAt(frame, empty); + base.PokeFrame(frame, DefaultValueController); ChangeLog.SetGeneralRedo(); InvalidateAfter(frame); @@ -116,8 +123,29 @@ public void RemoveFrames(ICollection frames) }); } + /// + /// This is used to optimize inserts and deletes when is not null. + /// + private void MoveFrame(int readFrom, int writeTo) + { + string log1 = Log[writeTo]; // always in range + string log2 = readFrom >= Log.Count ? "" : Log[readFrom]; // since we just use this string for a cache lookup, it can be blank + if (log1 == log2) return; + string combined = log1 + log2; + if (_inputMoveCache.TryGetValue(combined, out string result)) + { + Log[writeTo] = result; + } + else + { + base.PokeFrame(writeTo, GetInputState(readFrom) ?? DefaultValueController); + _inputMoveCache[combined] = Log[writeTo]; + } + } + public void RemoveFrames(int removeStart, int removeUpTo) { + // TODO: column limiting bool endBatch = ChangeLog.BeginNewBatch($"Remove frames {removeStart}-{removeUpTo - 1}", true); if (BindMarkersToInput) { @@ -133,12 +161,24 @@ public void RemoveFrames(int removeStart, int removeUpTo) // Log.GetRange() might be preferrable, but Log's type complicates that. string[] removedInputs = new string[removeUpTo - removeStart]; Log.CopyTo(removeStart, removedInputs, 0, removedInputs.Length); - Log.RemoveRange(removeStart, removeUpTo - removeStart); + + if (ActiveControllerInputs == null) + { + Log.RemoveRange(removeStart, removeUpTo - removeStart); + } + else + { + int count = removeUpTo - removeStart; + for (int i = removeStart; i < Log.Count; i++) + { + MoveFrame(readFrom: i + count, writeTo: i); + } + } ChangeLog.AddRemoveFrames( removeStart, - removeUpTo, removedInputs.ToList(), + ActiveControllerInputs, BindMarkersToInput ); if (endBatch) ChangeLog.EndBatch(); @@ -155,9 +195,32 @@ public void InsertInput(int frame, string inputState) public void InsertInput(int frame, IEnumerable inputLog) { var inputLogCopy = inputLog.ToList(); - Log.InsertRange(frame, inputLogCopy); + if (ActiveControllerInputs == null) + { + Log.InsertRange(frame, inputLogCopy); + } + else + { + int count = inputLogCopy.Count; + // add empty frames at the end + Log.AddRange(Enumerable.Repeat(Bk2LogEntryGenerator.EmptyEntry(Session.MovieController), count)); + // shift inputs to future frames + _inputMoveCache.Clear(); + for (int i = Log.Count - 1; i >= frame + count; i--) + { + MoveFrame(readFrom: i - count, writeTo: i); + } + // write the new inputs + Bk2Controller controller = new(Session.MovieController.Definition, LogKey); + for (int i = 0; i < count; i++) + { + controller.SetFromMnemonic(inputLogCopy[i]); + base.PokeFrame(frame + i, controller); + } + } + ShiftBindedMarkers(frame, inputLogCopy.Count); - ChangeLog.AddInsertInput(frame, inputLogCopy, BindMarkersToInput, $"Insert {inputLogCopy.Count} frame(s) at {frame}"); + ChangeLog.AddInsertInput(frame, inputLogCopy, ActiveControllerInputs, BindMarkersToInput, $"Insert {inputLogCopy.Count} frame(s) at {frame}"); InvalidateAfter(frame); } @@ -175,6 +238,7 @@ public void InsertInput(int frame, IEnumerable inputStates) public void CopyOverInput(int frame, IEnumerable inputStates) { + // TODO: column limiting var states = inputStates.ToList(); bool endBatch = ChangeLog.BeginNewBatch($"Copy Over Input: {frame}", true); @@ -187,7 +251,10 @@ public void CopyOverInput(int frame, IEnumerable inputStates) ChangeLog.AddGeneralUndo(frame, frame + states.Count - 1, $"Copy Over Input: {frame}"); for (int i = 0; i < states.Count; i++) { - Log[frame + i] = Bk2LogEntryGenerator.GenerateLogEntry(states[i]); + IController controller = states[i]; + if (ActiveControllerInputs != null) + controller = new MultitrackAdapter(controller, GetInputState(frame + i), ActiveControllerInputs); + Log[frame + i] = Bk2LogEntryGenerator.GenerateLogEntry(controller); } int firstChangedFrame = ChangeLog.SetGeneralRedo(); @@ -203,9 +270,27 @@ public void InsertEmptyFrame(int frame, int count = 1) { frame = Math.Min(frame, Log.Count); - Log.InsertRange(frame, Enumerable.Repeat(Bk2LogEntryGenerator.EmptyEntry(Session.MovieController), count)); + if (ActiveControllerInputs == null) + { + Log.InsertRange(frame, Enumerable.Repeat(Bk2LogEntryGenerator.EmptyEntry(Session.MovieController), count)); + } + else + { + // add empty frames at the end + Log.AddRange(Enumerable.Repeat(Bk2LogEntryGenerator.EmptyEntry(Session.MovieController), count)); + // shift inputs to future frames + _inputMoveCache.Clear(); + for (int i = Log.Count - 1; i >= frame + count; i--) + { + MoveFrame(readFrom: i - count, writeTo: i); + } + // clear frames + for (int i = frame; i < frame + count; i++) + base.PokeFrame(i, DefaultValueController); + } + ShiftBindedMarkers(frame, count); - ChangeLog.AddInsertFrames(frame, count, BindMarkersToInput, $"Insert {count} empty frame(s) at {frame}"); + ChangeLog.AddInsertFrames(frame, count, ActiveControllerInputs, BindMarkersToInput, $"Insert {count} empty frame(s) at {frame}"); InvalidateAfter(frame); } @@ -214,7 +299,6 @@ private void ExtendMovieForEdit(int numFrames) { int oldLength = InputLogLength; - // account for autohold TODO: What about auto-fire? string inputs = Bk2LogEntryGenerator.GenerateLogEntry(Session.StickySource); for (int i = 0; i < numFrames; i++) { diff --git a/src/BizHawk.Client.Common/movie/tasproj/TasMovie.History.cs b/src/BizHawk.Client.Common/movie/tasproj/TasMovie.History.cs index 887e2c1dd37..3b5aca8a2bc 100644 --- a/src/BizHawk.Client.Common/movie/tasproj/TasMovie.History.cs +++ b/src/BizHawk.Client.Common/movie/tasproj/TasMovie.History.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using BizHawk.Emulation.Common; namespace BizHawk.Client.Common { @@ -84,9 +85,9 @@ public interface IMovieChangeLog void AddAxisChange(int frame, string button, int oldState, int newState, string name = ""); void AddMarkerChange(TasMovieMarker newMarker, int oldPosition = -1, string oldMessage = "", string name = ""); void AddInputBind(int frame, bool isDelete, string name = ""); - void AddInsertFrames(int frame, int count, bool bindMarkers, string name = ""); - void AddInsertInput(int frame, List newInputs, bool bindMarkers, string name = ""); - void AddRemoveFrames(int removeStart, int removeUpTo, List oldInputs, bool bindMarkers, string name = ""); + void AddInsertFrames(int frame, int count, ControllerDefinition/*?*/ activeDefinition, bool bindMarkers, string name = ""); + void AddInsertInput(int frame, List newInputs, ControllerDefinition/*?*/ activeDefinition, bool bindMarkers, string name = ""); + void AddRemoveFrames(int removeStart, List oldInputs, ControllerDefinition/*?*/ activeDefinition, bool bindMarkers, string name = ""); void AddExtend(int originalLength, int count, string inputs); } @@ -425,30 +426,30 @@ public void AddInputBind(int frame, bool isDelete, string name = "") } } - public void AddInsertFrames(int frame, int count, bool bindMarkers, string name = "") + public void AddInsertFrames(int frame, int count, ControllerDefinition/*?*/ activeDefinition, bool bindMarkers, string name = "") { if (IsRecording) { AddMovieAction(name); - LatestBatch.Add(new MovieActionInsertFrames(frame, bindMarkers, count)); + LatestBatch.Add(new MovieActionInsertFrames(frame, bindMarkers, count, activeDefinition)); } } - public void AddInsertInput(int frame, List newInputs, bool bindMarkers, string name = "") + public void AddInsertInput(int frame, List newInputs, ControllerDefinition/*?*/ activeDefinition, bool bindMarkers, string name = "") { if (IsRecording) { AddMovieAction(name); - LatestBatch.Add(new MovieActionInsertFrames(frame, bindMarkers, newInputs)); + LatestBatch.Add(new MovieActionInsertFrames(frame, bindMarkers, newInputs, activeDefinition)); } } - public void AddRemoveFrames(int removeStart, int removeUpTo, List oldInputs, bool bindMarkers, string name = "") + public void AddRemoveFrames(int removeStart, List oldInputs, ControllerDefinition/*?*/ activeDefinition, bool bindMarkers, string name = "") { if (IsRecording) { AddMovieAction(name); - LatestBatch.Add(new MovieActionRemoveFrames(removeStart, removeUpTo, oldInputs, bindMarkers)); + LatestBatch.Add(new MovieActionRemoveFrames(removeStart, oldInputs, activeDefinition, bindMarkers)); } } @@ -521,6 +522,8 @@ public void Undo(ITasMovie movie) { bool wasBinding = movie.BindMarkersToInput; movie.BindMarkersToInput = _bindMarkers; + ControllerDefinition/*?*/ definition = movie.ActiveControllerInputs; + movie.ActiveControllerInputs = null; if (_redoLength != Length) { @@ -534,8 +537,10 @@ public void Undo(ITasMovie movie) for (int i = 0; i < _undoLength; i++) { - movie.SetFrame(FirstFrame + i, _oldLog[i]); + movie.PokeFrame(FirstFrame + i, _oldLog[i]); } + + movie.ActiveControllerInputs = definition; movie.BindMarkersToInput = wasBinding; } @@ -543,6 +548,8 @@ public void Redo(ITasMovie movie) { bool wasBinding = movie.BindMarkersToInput; movie.BindMarkersToInput = _bindMarkers; + ControllerDefinition/*?*/ definition = movie.ActiveControllerInputs; + movie.ActiveControllerInputs = null; if (_undoLength != Length) { @@ -556,8 +563,10 @@ public void Redo(ITasMovie movie) for (int i = 0; i < _redoLength; i++) { - movie.SetFrame(FirstFrame + i, _newLog[i]); + movie.PokeFrame(FirstFrame + i, _newLog[i]); } + + movie.ActiveControllerInputs = definition; movie.BindMarkersToInput = wasBinding; } } @@ -805,22 +814,25 @@ public class MovieActionInsertFrames : IMovieAction private readonly bool _bindMarkers; private readonly List _newInputs; + private readonly ControllerDefinition/*?*/ _activeDefinition; - public MovieActionInsertFrames(int frame, bool bindMarkers, int count) + public MovieActionInsertFrames(int frame, bool bindMarkers, int count, ControllerDefinition/*?*/ activeDefinition) { FirstFrame = frame; LastFrame = frame + count; _count = count; _onlyEmpty = true; + _activeDefinition = activeDefinition; _bindMarkers = bindMarkers; } - public MovieActionInsertFrames(int frame, bool bindMarkers, List newInputs) + public MovieActionInsertFrames(int frame, bool bindMarkers, List newInputs, ControllerDefinition/*?*/ activeDefinition) { FirstFrame = frame; LastFrame = frame + newInputs.Count; _onlyEmpty = false; _newInputs = newInputs; + _activeDefinition = activeDefinition; _bindMarkers = bindMarkers; } @@ -828,9 +840,19 @@ public void Undo(ITasMovie movie) { bool wasBinding = movie.BindMarkersToInput; movie.BindMarkersToInput = _bindMarkers; + ControllerDefinition/*?*/ definition = movie.ActiveControllerInputs; + movie.ActiveControllerInputs = _activeDefinition; movie.RemoveFrames(FirstFrame, LastFrame); + if (_activeDefinition != null) + { + // RemoveFrames will not have removed frames, but the insert operation we are undoing added frames + // so un-add them; we know they're empty + movie.ActiveControllerInputs = null; + movie.Truncate(movie.InputLogLength - (LastFrame - FirstFrame)); + } + movie.ActiveControllerInputs = definition; movie.BindMarkersToInput = wasBinding; } @@ -838,6 +860,8 @@ public void Redo(ITasMovie movie) { bool wasBinding = movie.BindMarkersToInput; movie.BindMarkersToInput = _bindMarkers; + ControllerDefinition/*?*/ definition = movie.ActiveControllerInputs; + movie.ActiveControllerInputs = _activeDefinition; if (_onlyEmpty) { @@ -848,6 +872,7 @@ public void Redo(ITasMovie movie) movie.InsertInput(FirstFrame, _newInputs); } + movie.ActiveControllerInputs = definition; movie.BindMarkersToInput = wasBinding; } } @@ -859,22 +884,34 @@ public class MovieActionRemoveFrames : IMovieAction private readonly List _oldInputs; private readonly bool _bindMarkers; + private readonly ControllerDefinition/*?*/ _activeDefinition; - public MovieActionRemoveFrames(int removeStart, int removeUpTo, List oldInputs, bool bindMarkers) + public MovieActionRemoveFrames(int removeStart, List oldInputs, ControllerDefinition/*?*/ activeDefinition, bool bindMarkers) { FirstFrame = removeStart; - LastFrame = removeUpTo; + LastFrame = removeStart + oldInputs.Count; _oldInputs = oldInputs; _bindMarkers = bindMarkers; + _activeDefinition = activeDefinition; } public void Undo(ITasMovie movie) { bool wasBinding = movie.BindMarkersToInput; movie.BindMarkersToInput = _bindMarkers; + ControllerDefinition/*?*/ definition = movie.ActiveControllerInputs; + movie.ActiveControllerInputs = _activeDefinition; movie.InsertInput(FirstFrame, _oldInputs); + if (_activeDefinition != null) + { + // insert will have added frames, but the delete operation we are undoing didn't delete any + // so un-add them; we know they're empty + movie.ActiveControllerInputs = null; + movie.Truncate(movie.InputLogLength - _oldInputs.Count); + } + movie.ActiveControllerInputs = definition; movie.BindMarkersToInput = wasBinding; } @@ -882,9 +919,12 @@ public void Redo(ITasMovie movie) { bool wasBinding = movie.BindMarkersToInput; movie.BindMarkersToInput = _bindMarkers; + ControllerDefinition/*?*/ definition = movie.ActiveControllerInputs; + movie.ActiveControllerInputs = _activeDefinition; movie.RemoveFrames(FirstFrame, LastFrame); + movie.ActiveControllerInputs = definition; movie.BindMarkersToInput = wasBinding; } } @@ -909,8 +949,12 @@ public void Undo(ITasMovie movie) { bool wasMarkerBound = movie.BindMarkersToInput; movie.BindMarkersToInput = false; + ControllerDefinition/*?*/ definition = movie.ActiveControllerInputs; + movie.ActiveControllerInputs = null; movie.RemoveFrames(FirstFrame, LastFrame + 1); + + movie.ActiveControllerInputs = definition; movie.BindMarkersToInput = wasMarkerBound; } @@ -918,8 +962,12 @@ public void Redo(ITasMovie movie) { bool wasMarkerBound = movie.BindMarkersToInput; movie.BindMarkersToInput = false; + ControllerDefinition/*?*/ definition = movie.ActiveControllerInputs; + movie.ActiveControllerInputs = null; movie.InsertInput(FirstFrame, Enumerable.Repeat(_inputs, _count)); + + movie.ActiveControllerInputs = definition; movie.BindMarkersToInput = wasMarkerBound; } } diff --git a/src/BizHawk.Client.EmuHawk/CustomControls/InputRoll/InputRoll.Drawing.cs b/src/BizHawk.Client.EmuHawk/CustomControls/InputRoll/InputRoll.Drawing.cs index 9c5cb2b67ef..fc8a64291b6 100644 --- a/src/BizHawk.Client.EmuHawk/CustomControls/InputRoll/InputRoll.Drawing.cs +++ b/src/BizHawk.Client.EmuHawk/CustomControls/InputRoll/InputRoll.Drawing.cs @@ -25,13 +25,13 @@ protected override void OnPaint(PaintEventArgs e) if (HorizontalOrientation) { visibleColumns = VisibleColumns - .Where(c => c.Right > _vBar.Value && c.Left - _vBar.Value < e.ClipRectangle.Height) + .Where(c => c.Right - _vBar.Value > e.ClipRectangle.Top && c.Left - _vBar.Value < e.ClipRectangle.Bottom) .ToList(); } else { visibleColumns = _columns.VisibleColumns - .Where(c => c.Right > _hBar.Value && c.Left - _hBar.Value < e.ClipRectangle.Width) + .Where(c => c.Right - _hBar.Value > e.ClipRectangle.Left && c.Left - _hBar.Value < e.ClipRectangle.Right) .ToList(); } @@ -116,10 +116,10 @@ private void DrawCellDrag() var text = ""; int offsetX = 0; int offsetY = 0; - QueryItemText?.Invoke(targetRow, targetCol, out text, ref offsetX, ref offsetY); + QueryItemText?.Invoke(this, targetRow, targetCol, out text, ref offsetX, ref offsetY); Color bgColor = _backColor; - QueryItemBkColor?.Invoke(targetRow, targetCol, ref bgColor); + QueryItemBkColor?.Invoke(this, targetRow, targetCol, ref bgColor); int columnHeight = CellHeight; if (HorizontalOrientation) @@ -208,7 +208,7 @@ private void DrawData(List visibleColumns, int firstVisibleRow, int int bitmapOffsetX = 0; int bitmapOffsetY = 0; - QueryItemIcon?.Invoke(f + startRow, col, ref image, ref bitmapOffsetX, ref bitmapOffsetY); + QueryItemIcon?.Invoke(this, f + startRow, col, ref image, ref bitmapOffsetX, ref bitmapOffsetY); if (image != null) { @@ -220,7 +220,7 @@ private void DrawData(List visibleColumns, int firstVisibleRow, int int strOffsetX = 0; int strOffsetY = 0; - QueryItemText(f + startRow, col, out var text, ref strOffsetX, ref strOffsetY); + QueryItemText(this, f + startRow, col, out var text, ref strOffsetX, ref strOffsetY); int textWidth = (int)_renderer.MeasureString(text, Font).Width; if (col.Rotatable) @@ -261,14 +261,14 @@ private void DrawData(List visibleColumns, int firstVisibleRow, int int bitmapOffsetX = 0; int bitmapOffsetY = 0; - QueryItemIcon?.Invoke(f + startRow, column, ref image, ref bitmapOffsetX, ref bitmapOffsetY); + QueryItemIcon?.Invoke(this, f + startRow, column, ref image, ref bitmapOffsetX, ref bitmapOffsetY); if (image != null) { _renderer.DrawBitmap(image, new Point(point.X + bitmapOffsetX, point.Y + bitmapOffsetY + CellHeightPadding)); } - QueryItemText(f + startRow, column, out var text, ref strOffsetX, ref strOffsetY); + QueryItemText(this, f + startRow, column, out var text, ref strOffsetX, ref strOffsetY); bool rePrep = false; currentCell.Column = column; @@ -297,7 +297,8 @@ private void DrawColumnBg(List visibleColumns, Rectangle rect) if (HorizontalOrientation) { - _renderer.FillRectangle(new Rectangle(0, 0, MaxColumnWidth + 1, rect.Height)); + int rightEdge = MaxColumnWidth; + _renderer.FillRectangle(new Rectangle(0, 0, rightEdge + 1, rect.Bottom)); for (int j = 0; j < visibleColumns.Count; j++) { @@ -307,20 +308,20 @@ private void DrawColumnBg(List visibleColumns, Rectangle rect) if (visibleColumns.Count is not 0) { - _renderer.Line(1, TotalColWidth, MaxColumnWidth, TotalColWidth); + _renderer.Line(1, TotalColWidth, rightEdge, TotalColWidth); } - _renderer.Line(0, 0, 0, rect.Height); - _renderer.Line(MaxColumnWidth, 0, MaxColumnWidth, rect.Height); + if (rect.Left <= 0) _renderer.Line(0, 0, 0, rect.Bottom); + _renderer.Line(rightEdge, 0, rightEdge, rect.Bottom); } else { int bottomEdge = RowsToPixels(0); // Gray column box and black line underneath - _renderer.FillRectangle(new Rectangle(0, 0, rect.Width, bottomEdge + 1)); - _renderer.Line(0, 0, rect.Width, 0); - _renderer.Line(0, bottomEdge, rect.Width, bottomEdge); + _renderer.FillRectangle(new Rectangle(0, 0, rect.Right, bottomEdge + 1)); + if (rect.Top <= 0) _renderer.Line(0, 0, rect.Right, 0); + _renderer.Line(0, bottomEdge, rect.Right, bottomEdge); // Vertical black separators foreach (var column in visibleColumns) @@ -421,18 +422,18 @@ private void DrawBg(List visibleColumns, Rectangle rect, int firstVi for (int i = 1; i < lastVisibleRow - firstVisibleRow + 1; i++) { int x = RowsToPixels(i); - _renderer.Line(x, 1, x, rect.Height); + _renderer.Line(x, 1, x, rect.Bottom); } // Rows - _renderer.Line(RowsToPixels(0) + 1, 0, rect.Width + MaxColumnWidth, 0); + int startX = RowsToPixels(0) + 1; + _renderer.Line(startX, 0, rect.Right, 0); for (int i = 0; i < visibleColumns.Count; i++) { // TODO: MaxColumnWidth shouldn't be necessary // This also makes too many assumptions, the parameters need to drive what is being drawn int y = visibleColumns[i].Right - _vBar.Value; - int x = RowsToPixels(0) + 1; - _renderer.Line(x, y, rect.Width + MaxColumnWidth, y); + _renderer.Line(startX, y, rect.Right, y); } } else @@ -442,19 +443,19 @@ private void DrawBg(List visibleColumns, Rectangle rect, int firstVi foreach (var column in visibleColumns) { int x = column.Left - _hBar.Value; - _renderer.Line(x, y, x, rect.Height - 1); + _renderer.Line(x, y, x, rect.Bottom - 1); } if (visibleColumns.Count is not 0) { int x = TotalColWidth - _hBar.Value; - _renderer.Line(x, y, x, rect.Height - 1); + _renderer.Line(x, y, x, rect.Bottom - 1); } // Rows for (int i = 1; i < VisibleRows + 1; i++) { - _renderer.Line(0, RowsToPixels(i), rect.Width + 1, RowsToPixels(i)); + _renderer.Line(0, RowsToPixels(i), rect.Right + 1, RowsToPixels(i)); } } } @@ -483,12 +484,12 @@ private void DoSelectionBG() if (QueryRowBkColor != null && lastRow != cell.RowIndex.Value) { - QueryRowBkColor(cell.RowIndex.Value, ref rowColor); + QueryRowBkColor(this, cell.RowIndex.Value, ref rowColor); lastRow = cell.RowIndex.Value; } Color cellColor = rowColor; - QueryItemBkColor?.Invoke(cell.RowIndex.Value, cell.Column, ref cellColor); + QueryItemBkColor?.Invoke(this, cell.RowIndex.Value, cell.Column, ref cellColor); // Alpha layering for cell before selection float alpha = (float)cellColor.A / 255; @@ -555,12 +556,12 @@ private void DoBackGroundCallback(List visibleColumns, int firstVisi { f += _lagFrames[i]; var rowColor = _backColor; - QueryRowBkColor?.Invoke(f + startIndex, ref rowColor); + QueryRowBkColor?.Invoke(this, f + startIndex, ref rowColor); foreach (var column in visibleColumns) { var itemColor = rowColor; - QueryItemBkColor?.Invoke(f + startIndex, column, ref itemColor); + QueryItemBkColor?.Invoke(this, f + startIndex, column, ref itemColor); if (itemColor.A is not (0 or 255)) { float alpha = (float)itemColor.A / 255; diff --git a/src/BizHawk.Client.EmuHawk/CustomControls/InputRoll/InputRoll.cs b/src/BizHawk.Client.EmuHawk/CustomControls/InputRoll/InputRoll.cs index 04e552c3bc5..b8cb80eca98 100644 --- a/src/BizHawk.Client.EmuHawk/CustomControls/InputRoll/InputRoll.cs +++ b/src/BizHawk.Client.EmuHawk/CustomControls/InputRoll/InputRoll.cs @@ -95,6 +95,8 @@ public override Color ForeColor [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public bool SuspendHotkeys { get; set; } + public event Action ColumnsChanged; + public InputRoll() { SetStyle(ControlStyles.AllPaintingInWmPaint, true); @@ -183,7 +185,7 @@ protected override void OnDoubleClick(EventArgs e) { string text = ""; int offSetX = 0, offSetY = 0; - QueryItemText?.Invoke(i, col, out text, ref offSetX, ref offSetY); + QueryItemText?.Invoke(this, i, col, out text, ref offSetX, ref offSetY); if (text.Length > maxLength) { maxLength = text.Length; @@ -489,32 +491,32 @@ public int HoverInterval /// /// Retrieve the text for a cell /// - public delegate void QueryItemTextHandler(int index, RollColumn column, out string text, ref int offsetX, ref int offsetY); + public delegate void QueryItemTextHandler(InputRoll sender, int index, RollColumn column, out string text, ref int offsetX, ref int offsetY); /// /// Retrieve the background color for a cell /// - public delegate void QueryItemBkColorHandler(int index, RollColumn column, ref Color color); - public delegate void QueryRowBkColorHandler(int index, ref Color color); + public delegate void QueryItemBkColorHandler(InputRoll sender, int index, RollColumn column, ref Color color); + public delegate void QueryRowBkColorHandler(InputRoll sender, int index, ref Color color); /// /// Retrieve the image for a given cell /// - public delegate void QueryItemIconHandler(int index, RollColumn column, ref Bitmap icon, ref int offsetX, ref int offsetY); + public delegate void QueryItemIconHandler(InputRoll sender, int index, RollColumn column, ref Bitmap icon, ref int offsetX, ref int offsetY); /// /// Check if a given frame is a lag frame /// - public delegate bool QueryFrameLagHandler(int index, bool hideWasLag); + public delegate bool QueryFrameLagHandler(InputRoll sender, int index, bool hideWasLag); /// /// Check if clicking the current cell should select it. /// - public delegate bool QueryShouldSelectCellHandler(MouseButtons button); + public delegate bool QueryShouldSelectCellHandler(InputRoll sender, MouseButtons button); - public delegate void CellChangeEventHandler(object sender, CellEventArgs e); + public delegate void CellChangeEventHandler(InputRoll sender, CellEventArgs e); - public delegate void HoverEventHandler(object sender, CellEventArgs e); + public delegate void HoverEventHandler(InputRoll sender, CellEventArgs e); public delegate void RightMouseScrollEventHandler(object sender, MouseEventArgs e); @@ -522,9 +524,9 @@ public int HoverInterval public delegate void ColumnReorderedEventHandler(object sender, ColumnReorderedEventArgs e); - public delegate void RowScrollEvent(object sender, EventArgs e); + public delegate void RowScrollEvent(InputRoll sender, EventArgs e); - public delegate void ColumnScrollEvent(object sender, EventArgs e); + public delegate void ColumnScrollEvent(InputRoll sender, EventArgs e); public delegate void CellDroppedEvent(object sender, CellEventArgs e); @@ -655,26 +657,13 @@ public int? SelectionEndIndex [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public bool RightButtonHeld { get; private set; } - public InputRollSettings GetUserSettings() => Settings; - - public void LoadSettings(InputRollSettings settings) + public void LoadColumns(RollColumns columns) { - _columns = settings.Columns; + _columns = columns; _columns.ChangedCallback = ColumnChangedCallback; _columns.ColumnsChanged(); - HorizontalOrientation = settings.HorizontalOrientation; - LagFramesToHide = settings.LagFramesToHide; - HideWasLagFrames = settings.HideWasLagFrames; } - private InputRollSettings Settings => new InputRollSettings - { - Columns = _columns, - HorizontalOrientation = HorizontalOrientation, - LagFramesToHide = LagFramesToHide, - HideWasLagFrames = HideWasLagFrames, - }; - public class InputRollSettings { public RollColumns Columns { get; set; } @@ -967,6 +956,12 @@ public bool AnyRowsSelected public int FirstSelectedRowIndex => SelectedRowsWithDuplicates.First(); + /// the of the last row in the selection list (throws if no rows are selected) + /// you probably want + [Browsable(false)] + public int LastSelectedRowIndex + => SelectedRowsWithDuplicates.Last(); + public bool IsRowSelected(int rowIndex) => _selectedItems.IncludesRow(rowIndex); @@ -1145,7 +1140,7 @@ protected override void OnMouseDown(MouseEventArgs e) OnMouseMove(e); } - if (IsHoveringOnDataCell && QueryShouldSelectCell?.Invoke(e.Button) != false) + if (IsHoveringOnDataCell && QueryShouldSelectCell?.Invoke(this, e.Button) != false) { if (e.Button == MouseButtons.Left) { @@ -1621,16 +1616,14 @@ private void VerticalBar_ValueChanged(object sender, EventArgs e) Refresh(); } -#pragma warning disable MA0091 // unorthodox, but I think this is sound --yoshi if (_horizontalOrientation) { - ColumnScroll?.Invoke(_hBar, e); + ColumnScroll?.Invoke(this, e); } else { - RowScroll?.Invoke(_vBar, e); + RowScroll?.Invoke(this, e); } -#pragma warning restore MA0091 } private void HorizontalBar_ValueChanged(object sender, EventArgs e) @@ -1640,16 +1633,14 @@ private void HorizontalBar_ValueChanged(object sender, EventArgs e) Refresh(); } -#pragma warning disable MA0091 // unorthodox, but I think this is sound --yoshi if (_horizontalOrientation) { - RowScroll?.Invoke(_hBar, e); + RowScroll?.Invoke(this, e); } else { - ColumnScroll?.Invoke(_vBar, e); + ColumnScroll?.Invoke(this, e); } -#pragma warning restore MA0091 } private void ColumnChangedCallback() @@ -1659,6 +1650,8 @@ private void ColumnChangedCallback() { MaxColumnWidth = _columns.VisibleColumns.Max(c => c.VerticalWidth); } + + ColumnsChanged?.Invoke(); } private void DoColumnReorder() @@ -1751,7 +1744,6 @@ private void RecalculateScrollBars() } } - _vBar.Location = new Point(Width - _vBar.Width, 0); _vBar.Height = Height; _vBar.Visible = true; } @@ -1773,7 +1765,6 @@ private void RecalculateScrollBars() _hBar.Maximum = TotalColWidth - _drawWidth + _hBar.LargeChange; } - _hBar.Location = new Point(0, Height - _hBar.Height); _hBar.Width = Width - (NeedsVScrollbar ? (_vBar.Width + 1) : 0); _hBar.Visible = true; } @@ -1782,6 +1773,17 @@ private void RecalculateScrollBars() _hBar.Visible = false; _hBar.Value = 0; } + + RepositionScrollbars(); + } + + public void RepositionScrollbars() + { + int edge = Parent != null ? Math.Min(Width, Parent.Width - Left) : Width; + _vBar.Location = new Point(Math.Max(edge - _vBar.Width, 0), 0); + + edge = Parent != null ? Math.Min(Height, Parent.Height - Top) : Height; + _hBar.Location = new Point(0, Math.Max(edge - _hBar.Height, 0)); } private void UpdateDrawSize() @@ -1885,7 +1887,11 @@ private Cell CalculatePointedCell(int x, int y) } } - if (!(IsPaintDown || RightButtonHeld) && newCell.RowIndex <= -1) // -2 if we're entering from the top + // We don't show the pointed cell as being a column header (RowIndex = null) if we are holding right mouse button. (This allows right-click dragging to rows above the top one.) + // hack: MouseMove events do not happen while a context menu is open, leading to a column right-click not working if a context menu was already open. + // we solve this by only considering right button if the prior cell has a row value + bool rightButton = RightButtonHeld && CurrentCell.RowIndex != null; + if (!(IsPaintDown || rightButton) && newCell.RowIndex <= -1) // -2 if we're entering from the top { newCell.RowIndex = null; } @@ -1900,7 +1906,7 @@ private Cell CalculatePointedCell(int x, int y) private bool NeedsHScrollbar { get; set; } // Gets the total width of all the columns by using the last column's Right property. - private int TotalColWidth => _columns.VisibleColumns.Any() + public int TotalColWidth => _columns.VisibleColumns.Any() ? _columns.VisibleColumns.Last().Right : 0; @@ -2032,7 +2038,7 @@ private void SetLagFramesArray() // First one needs to check BACKWARDS for lag frame count. SetLagFramesFirst(); int f = _lagFrames[0]; - if (QueryFrameLag(firstVisibleRow + f, HideWasLagFrames)) + if (QueryFrameLag(this, firstVisibleRow + f, HideWasLagFrames)) { showNext = true; } @@ -2044,7 +2050,7 @@ private void SetLagFramesArray() { for (; _lagFrames[i] < LagFramesToHide; _lagFrames[i]++) { - if (!QueryFrameLag(firstVisibleRow + i + f, HideWasLagFrames)) + if (!QueryFrameLag(this, firstVisibleRow + i + f, HideWasLagFrames)) { break; } @@ -2054,13 +2060,13 @@ private void SetLagFramesArray() } else { - if (!QueryFrameLag(firstVisibleRow + i + f, HideWasLagFrames)) + if (!QueryFrameLag(this, firstVisibleRow + i + f, HideWasLagFrames)) { showNext = false; } } - if (_lagFrames[i] == LagFramesToHide && QueryFrameLag(firstVisibleRow + i + f, HideWasLagFrames)) + if (_lagFrames[i] == LagFramesToHide && QueryFrameLag(this, firstVisibleRow + i + f, HideWasLagFrames)) { showNext = true; } @@ -2085,7 +2091,7 @@ private void SetLagFramesFirst() { count++; } - while (QueryFrameLag(firstVisibleRow - count, HideWasLagFrames) && count <= LagFramesToHide); + while (QueryFrameLag(this, firstVisibleRow - count, HideWasLagFrames) && count <= LagFramesToHide); count--; // Count forward @@ -2094,7 +2100,7 @@ private void SetLagFramesFirst() { fCount++; } - while (QueryFrameLag(firstVisibleRow + fCount, HideWasLagFrames) && count + fCount < LagFramesToHide); + while (QueryFrameLag(this, firstVisibleRow + fCount, HideWasLagFrames) && count + fCount < LagFramesToHide); _lagFrames[0] = (byte)fCount; } else diff --git a/src/BizHawk.Client.EmuHawk/CustomControls/InputRoll/RollColumn.cs b/src/BizHawk.Client.EmuHawk/CustomControls/InputRoll/RollColumn.cs index 7e54eb17b11..5fa78ea3e37 100644 --- a/src/BizHawk.Client.EmuHawk/CustomControls/InputRoll/RollColumn.cs +++ b/src/BizHawk.Client.EmuHawk/CustomControls/InputRoll/RollColumn.cs @@ -50,5 +50,7 @@ public RollColumn(string name, int verticalWidth, int horizontalHeight, string t HorizontalHeight = UIHelper.ScaleX(horizontalHeight); Width = VerticalWidth; } + + public RollColumn Clone() => (MemberwiseClone() as RollColumn)!; } } diff --git a/src/BizHawk.Client.EmuHawk/CustomControls/InputRoll/RollColumns.cs b/src/BizHawk.Client.EmuHawk/CustomControls/InputRoll/RollColumns.cs index fe4a2128c9d..49557255713 100644 --- a/src/BizHawk.Client.EmuHawk/CustomControls/InputRoll/RollColumns.cs +++ b/src/BizHawk.Client.EmuHawk/CustomControls/InputRoll/RollColumns.cs @@ -14,9 +14,13 @@ public RollColumn? this[string name] public Action? ChangedCallback { get; set; } = null; + private bool _suspendChanged = false; + // TODO: this shouldn't be exposed. But in order to not expose it, each RollColumn must have a change callback, and all property changes must call it, it is quicker and easier to just call this when needed public void ColumnsChanged() { + if (_suspendChanged) return; + int pos = 0; foreach (var col in VisibleColumns) @@ -40,13 +44,12 @@ public void ColumnsChanged() public new void AddRange(IEnumerable collection) { + _suspendChanged = true; foreach (var column in collection) { - if (this[column.Name] == null) - { - Add(column); - } + Add(column); } + _suspendChanged = false; ColumnsChanged(); } diff --git a/src/BizHawk.Client.EmuHawk/tools/CDL.cs b/src/BizHawk.Client.EmuHawk/tools/CDL.cs index 317a1041285..3061b6223f3 100644 --- a/src/BizHawk.Client.EmuHawk/tools/CDL.cs +++ b/src/BizHawk.Client.EmuHawk/tools/CDL.cs @@ -567,7 +567,7 @@ private void TsbLoggingActive_CheckedChanged(object sender, EventArgs e) CodeDataLogger.SetCDL(null); } - private void LvCDL_QueryItemText(int index, RollColumn column, out string text, ref int offsetX, ref int offsetY) + private void LvCDL_QueryItemText(InputRoll sender, int index, RollColumn column, out string text, ref int offsetX, ref int offsetY) { var subItem = lvCDL.AllColumns.IndexOf(column); text = _listContents[index][subItem]; diff --git a/src/BizHawk.Client.EmuHawk/tools/Cheats/Cheats.cs b/src/BizHawk.Client.EmuHawk/tools/Cheats/Cheats.cs index 8e27466e947..558165dc793 100644 --- a/src/BizHawk.Client.EmuHawk/tools/Cheats/Cheats.cs +++ b/src/BizHawk.Client.EmuHawk/tools/Cheats/Cheats.cs @@ -214,7 +214,7 @@ private void LoadConfigSettings() SetColumns(); } - private void CheatListView_QueryItemText(int index, RollColumn column, out string text, ref int offsetX, ref int offsetY) + private void CheatListView_QueryItemText(InputRoll sender, int index, RollColumn column, out string text, ref int offsetX, ref int offsetY) { text = ""; if (index >= MainForm.CheatList.Count || MainForm.CheatList[index].IsSeparator) @@ -270,7 +270,7 @@ private void CheatListView_QueryItemText(int index, RollColumn column, out strin } } - private void CheatListView_QueryItemBkColor(int index, RollColumn column, ref Color color) + private void CheatListView_QueryItemBkColor(InputRoll sender, int index, RollColumn column, ref Color color) { if (index < MainForm.CheatList.Count) { diff --git a/src/BizHawk.Client.EmuHawk/tools/Debugger/GenericDebugger.Disassembler.cs b/src/BizHawk.Client.EmuHawk/tools/Debugger/GenericDebugger.Disassembler.cs index b6cef4c4766..ffef837137b 100644 --- a/src/BizHawk.Client.EmuHawk/tools/Debugger/GenericDebugger.Disassembler.cs +++ b/src/BizHawk.Client.EmuHawk/tools/Debugger/GenericDebugger.Disassembler.cs @@ -69,7 +69,7 @@ private void Disassemble() DisassemblerView.Refresh(); } - private void DisassemblerView_QueryItemText(int index, RollColumn column, out string text, ref int offsetX, ref int offsetY) + private void DisassemblerView_QueryItemText(InputRoll sender, int index, RollColumn column, out string text, ref int offsetX, ref int offsetY) { text = ""; @@ -86,7 +86,7 @@ private void DisassemblerView_QueryItemText(int index, RollColumn column, out st } } - private void DisassemblerView_QueryItemBkColor(int index, RollColumn column, ref Color color) + private void DisassemblerView_QueryItemBkColor(InputRoll sender, int index, RollColumn column, ref Color color) { if (0 <= index && index < _disassemblyLines.Count) { diff --git a/src/BizHawk.Client.EmuHawk/tools/Lua/Libraries/TAStudioLuaLibrary.cs b/src/BizHawk.Client.EmuHawk/tools/Lua/Libraries/TAStudioLuaLibrary.cs index b5ffeecd87d..6c7406e0428 100644 --- a/src/BizHawk.Client.EmuHawk/tools/Lua/Libraries/TAStudioLuaLibrary.cs +++ b/src/BizHawk.Client.EmuHawk/tools/Lua/Libraries/TAStudioLuaLibrary.cs @@ -422,10 +422,11 @@ public void ClearInputChanges() name: "addcolumn", description: "Extends the piano roll with an extra column for data visualisation." + " The text parameter is used as the column header, while the name parameter is used to identify the column for {{onqueryitem*}} callbacks." - + " And width is obviously the width (in dp).")] - public void AddColumn(string name, string text, int width) + + " And width is obviously the width (in dp)." + + " If you have multiple input rolls, rollIndex can specify which one it should be visible in.")] + public void AddColumn(string name, string text, int width, int? rollIndex = null) { - if (Engaged()) Tastudio.AddColumn(name: name, widthUnscaled: width, text: text); + if (Engaged()) Tastudio.AddColumn(name: name, widthUnscaled: width, text: text, rollIndex: rollIndex ?? 0); } [LuaMethodExample("tastudio.setbranchtext( \"Some text\", 1 );")] diff --git a/src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.cs b/src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.cs index ff9ea53a77d..522da974d6e 100644 --- a/src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.cs +++ b/src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.cs @@ -428,7 +428,7 @@ private void SessionChangedCallback() Path.GetFileName(_openedFiles.Filename); } - private void LuaListView_QueryItemImage(int index, RollColumn column, ref Bitmap bitmap, ref int offsetX, ref int offsetY) + private void LuaListView_QueryItemImage(InputRoll sender, int index, RollColumn column, ref Bitmap bitmap, ref int offsetX, ref int offsetY) { if (column.Name != IconColumnName) { @@ -448,7 +448,7 @@ private void LuaListView_QueryItemImage(int index, RollColumn column, ref Bitmap }; } - private void LuaListView_QueryItemBkColor(int index, RollColumn column, ref Color color) + private void LuaListView_QueryItemBkColor(InputRoll sender, int index, RollColumn column, ref Color color) { var lf = _openedFiles[index]; if (lf.IsSeparator) color = BackColor; @@ -456,7 +456,7 @@ private void LuaListView_QueryItemBkColor(int index, RollColumn column, ref Colo else if (lf.Enabled) color = Color.LightCyan; } - private void LuaListView_QueryItemText(int index, RollColumn column, out string text, ref int offsetX, ref int offsetY) + private void LuaListView_QueryItemText(InputRoll sender, int index, RollColumn column, out string text, ref int offsetX, ref int offsetY) { text = ""; diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/BookmarksBranchesBox.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/BookmarksBranchesBox.cs index 0ebf61b7760..8d79644f118 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/BookmarksBranchesBox.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/BookmarksBranchesBox.cs @@ -86,7 +86,7 @@ private void SetupColumns() BranchView.AllColumns.Add(new(name: UserTextColumnName, widthUnscaled: 90, text: "UserText")); } - private void QueryItemText(int index, RollColumn column, out string text, ref int offsetX, ref int offsetY) + private void QueryItemText(InputRoll sender, int index, RollColumn column, out string text, ref int offsetX, ref int offsetY) { text = ""; @@ -105,7 +105,7 @@ private void QueryItemText(int index, RollColumn column, out string text, ref in }; } - private void QueryItemBkColor(int index, RollColumn column, ref Color color) + private void QueryItemBkColor(InputRoll sender, int index, RollColumn column, ref Color color) { // This could happen if the control is told to redraw while Tastudio is rebooting, as we would not have a TasMovie just yet if (Tastudio.CurrentTasMovie == null) diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/MarkerControl.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/MarkerControl.cs index bdcd2dedc4c..b4fc528d9fd 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/MarkerControl.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/MarkerControl.cs @@ -55,7 +55,7 @@ private void SetupColumns() public InputRoll MarkerInputRoll => MarkerView; - private void MarkerView_QueryItemBkColor(int index, RollColumn column, ref Color color) + private void MarkerView_QueryItemBkColor(InputRoll sender, int index, RollColumn column, ref Color color) { // This could happen if the control is told to redraw while Tastudio is rebooting, as we would not have a TasMovie just yet if (Tastudio.CurrentTasMovie is null) return; @@ -87,7 +87,7 @@ private void MarkerView_QueryItemBkColor(int index, RollColumn column, ref Color } } - private void MarkerView_QueryItemText(int index, RollColumn column, out string text, ref int offsetX, ref int offsetY) + private void MarkerView_QueryItemText(InputRoll sender, int index, RollColumn column, out string text, ref int offsetX, ref int offsetY) { text = ""; diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Designer.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Designer.cs index 9d563e4e63f..79a3f6325d7 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Designer.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Designer.cs @@ -84,7 +84,7 @@ private void InitializeComponent() this.CommentsMenuItem = new BizHawk.WinForms.Controls.ToolStripMenuItemEx(); this.SubtitlesMenuItem = new BizHawk.WinForms.Controls.ToolStripMenuItemEx(); this.SettingsSubMenu = new BizHawk.WinForms.Controls.ToolStripMenuItemEx(); - this.ColumnsSubMenu = new BizHawk.WinForms.Controls.ToolStripMenuItemEx(); + this.TAStudioSettingsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.toolStripSeparator19 = new BizHawk.WinForms.Controls.ToolStripSeparatorEx(); this.HelpSubMenu = new BizHawk.WinForms.Controls.ToolStripMenuItemEx(); this.TASEditorManualOnlineMenuItem = new BizHawk.WinForms.Controls.ToolStripMenuItemEx(); @@ -92,7 +92,6 @@ private void InitializeComponent() this.aboutToolStripMenuItem = new BizHawk.WinForms.Controls.ToolStripMenuItemEx(); this.toolStripSeparator10 = new BizHawk.WinForms.Controls.ToolStripSeparatorEx(); this.EnableTooltipsMenuItem = new BizHawk.WinForms.Controls.ToolStripMenuItemEx(); - this.TasView = new BizHawk.Client.EmuHawk.InputRoll(); this.TasStatusStrip = new BizHawk.WinForms.Controls.StatusStripEx(); this.MessageStatusLabel = new System.Windows.Forms.ToolStripStatusLabel(); this.ProgressBar = new System.Windows.Forms.ToolStripProgressBar(); @@ -132,7 +131,12 @@ private void InitializeComponent() this.BranchesMarkersSplit = new System.Windows.Forms.SplitContainer(); this.MainVertialSplit = new System.Windows.Forms.SplitContainer(); this.toolTip1 = new System.Windows.Forms.ToolTip(this.components); - this.TAStudioSettingsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.ColumnRightClickMenu = new System.Windows.Forms.ContextMenuStrip(this.components); + this.AutoHoldContextMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.HideColumnContextMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.ShowColumnsContextMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.NewInputRollContextMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.DeleteInputRollContextMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.TASMenu.SuspendLayout(); this.TasStatusStrip.SuspendLayout(); this.RightClickMenu.SuspendLayout(); @@ -141,9 +145,9 @@ private void InitializeComponent() this.BranchesMarkersSplit.Panel2.SuspendLayout(); this.BranchesMarkersSplit.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.MainVertialSplit)).BeginInit(); - this.MainVertialSplit.Panel1.SuspendLayout(); this.MainVertialSplit.Panel2.SuspendLayout(); this.MainVertialSplit.SuspendLayout(); + this.ColumnRightClickMenu.SuspendLayout(); this.SuspendLayout(); // // TASMenu @@ -153,7 +157,6 @@ private void InitializeComponent() this.EditSubMenu, this.MetaSubMenu, this.SettingsSubMenu, - this.ColumnsSubMenu, this.HelpSubMenu}); this.TASMenu.TabIndex = 0; // @@ -442,11 +445,12 @@ private void InitializeComponent() this.TAStudioSettingsToolStripMenuItem}); this.SettingsSubMenu.Text = "&Settings"; // - // ColumnsSubMenu + // TAStudioSettingsToolStripMenuItem // - this.ColumnsSubMenu.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.toolStripSeparator19}); - this.ColumnsSubMenu.Text = "&Columns"; + this.TAStudioSettingsToolStripMenuItem.Name = "TAStudioSettingsToolStripMenuItem"; + this.TAStudioSettingsToolStripMenuItem.Size = new System.Drawing.Size(156, 22); + this.TAStudioSettingsToolStripMenuItem.Text = "Open settings..."; + this.TAStudioSettingsToolStripMenuItem.Click += new System.EventHandler(this.TAStudioSettingsToolStripMenuItem_Click); // // HelpSubMenu // @@ -478,44 +482,6 @@ private void InitializeComponent() this.EnableTooltipsMenuItem.Enabled = false; this.EnableTooltipsMenuItem.Text = "&Enable Tooltips"; // - // TasView - // - this.TasView.AllowColumnReorder = false; - this.TasView.AllowColumnResize = false; - this.TasView.AllowMassNavigationShortcuts = false; - this.TasView.AllowRightClickSelection = false; - this.TasView.AlwaysScroll = false; - this.TasView.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.TasView.CellHeightPadding = 0; - this.TasView.ChangeSelectionWhenPaging = false; - this.TasView.Font = new System.Drawing.Font("Arial", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.TasView.FullRowSelect = true; - this.TasView.HorizontalOrientation = false; - this.TasView.InputPaintingMode = true; - this.TasView.LetKeysModifySelection = true; - this.TasView.Location = new System.Drawing.Point(3, 0); - this.TasView.Name = "TasView"; - this.TasView.Rotatable = true; - this.TasView.RowCount = 0; - this.TasView.ScrollSpeed = 1; - this.TasView.Size = new System.Drawing.Size(289, 528); - this.TasView.TabIndex = 1; - this.TasView.ColumnClick += new BizHawk.Client.EmuHawk.InputRoll.ColumnClickEventHandler(this.TasView_ColumnClick); - this.TasView.ColumnRightClick += new BizHawk.Client.EmuHawk.InputRoll.ColumnClickEventHandler(this.TasView_ColumnRightClick); - this.TasView.SelectedIndexChanged += new System.EventHandler(this.TasView_SelectedIndexChanged); - this.TasView.RightMouseScrolled += new BizHawk.Client.EmuHawk.InputRoll.RightMouseScrollEventHandler(this.TasView_MouseWheel); - this.TasView.ColumnReordered += new BizHawk.Client.EmuHawk.InputRoll.ColumnReorderedEventHandler(this.TasView_ColumnReordered); - this.TasView.CellDropped += new BizHawk.Client.EmuHawk.InputRoll.CellDroppedEvent(this.TasView_CellDropped); - this.TasView.RotationChanged += new System.EventHandler(this.HandleRotationChanged); - this.TasView.KeyDown += new System.Windows.Forms.KeyEventHandler(this.TasView_KeyDown); - this.TasView.MouseDoubleClick += new System.Windows.Forms.MouseEventHandler(this.TasView_MouseDoubleClick); - this.TasView.MouseDown += new System.Windows.Forms.MouseEventHandler(this.TasView_MouseDown); - this.TasView.MouseEnter += new System.EventHandler(this.TasView_MouseEnter); - this.TasView.MouseMove += new System.Windows.Forms.MouseEventHandler(this.TasView_MouseMove); - this.TasView.MouseUp += new System.Windows.Forms.MouseEventHandler(this.TasView_MouseUp); - // // TasStatusStrip // this.TasStatusStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { @@ -753,10 +719,6 @@ private void InitializeComponent() this.MainVertialSplit.Location = new System.Drawing.Point(2, 23); this.MainVertialSplit.Name = "MainVertialSplit"; // - // MainVertialSplit.Panel1 - // - this.MainVertialSplit.Panel1.Controls.Add(this.TasView); - // // MainVertialSplit.Panel2 // this.MainVertialSplit.Panel2.Controls.Add(this.BranchesMarkersSplit); @@ -765,12 +727,51 @@ private void InitializeComponent() this.MainVertialSplit.TabIndex = 10; this.MainVertialSplit.SplitterMoved += new System.Windows.Forms.SplitterEventHandler(this.MainVerticalSplit_SplitterMoved); // - // TAStudioSettingsToolStripMenuItem + // ColumnRightClickMenu // - this.TAStudioSettingsToolStripMenuItem.Name = "TAStudioSettingsToolStripMenuItem"; - this.TAStudioSettingsToolStripMenuItem.Size = new System.Drawing.Size(191, 22); - this.TAStudioSettingsToolStripMenuItem.Text = "Open settings..."; - this.TAStudioSettingsToolStripMenuItem.Click += new System.EventHandler(this.TAStudioSettingsToolStripMenuItem_Click); + this.ColumnRightClickMenu.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.AutoHoldContextMenuItem, + this.HideColumnContextMenuItem, + this.ShowColumnsContextMenuItem, + this.NewInputRollContextMenuItem, + this.DeleteInputRollContextMenuItem}); + this.ColumnRightClickMenu.Name = "ColumnRightClickMenu"; + this.ColumnRightClickMenu.Size = new System.Drawing.Size(181, 92); + this.ColumnRightClickMenu.Opened += new System.EventHandler(this.ColumnRightClickMenu_Opened); + // + // AutoHoldContextMenuItem + // + this.AutoHoldContextMenuItem.Name = "AutoHoldContextMenuItem"; + this.AutoHoldContextMenuItem.Size = new System.Drawing.Size(180, 22); + this.AutoHoldContextMenuItem.Text = "Auto-hold"; + this.AutoHoldContextMenuItem.Click += new System.EventHandler(this.AutoHoldContextMenuItem_Click); + // + // HideColumnContextMenuItem + // + this.HideColumnContextMenuItem.Name = "HideColumnContextMenuItem"; + this.HideColumnContextMenuItem.Size = new System.Drawing.Size(180, 22); + this.HideColumnContextMenuItem.Text = "Hide column"; + this.HideColumnContextMenuItem.Click += new System.EventHandler(this.HideColumnContextMenuItem_Click); + // + // ShowColumnsContextMenuItem + // + this.ShowColumnsContextMenuItem.Name = "ShowColumnsContextMenuItem"; + this.ShowColumnsContextMenuItem.Size = new System.Drawing.Size(180, 22); + this.ShowColumnsContextMenuItem.Text = "Show columns"; + // + // NewInputRollContextMenuItem + // + this.NewInputRollContextMenuItem.Name = "NewInputRollContextMenuItem"; + this.NewInputRollContextMenuItem.Size = new System.Drawing.Size(180, 22); + this.NewInputRollContextMenuItem.Text = "New input roll"; + this.NewInputRollContextMenuItem.Click += new System.EventHandler(this.NewInputRollContextMenuItem_Click); + // + // DeleteInputRollContextMenuItem + // + this.DeleteInputRollContextMenuItem.Name = "DeleteInputRollContextMenuItem"; + this.DeleteInputRollContextMenuItem.Size = new System.Drawing.Size(180, 22); + this.DeleteInputRollContextMenuItem.Text = "Delete input roll"; + this.DeleteInputRollContextMenuItem.Click += new System.EventHandler(this.DeleteInputRollContextMenuItem_Click); // // TAStudio // @@ -791,6 +792,7 @@ private void InitializeComponent() this.Load += new System.EventHandler(this.Tastudio_Load); this.DragDrop += new System.Windows.Forms.DragEventHandler(this.TAStudio_DragDrop); this.DragEnter += new System.Windows.Forms.DragEventHandler(this.DragEnterWrapper); + this.Resize += new System.EventHandler(this.TAStudio_Resize); this.TASMenu.ResumeLayout(false); this.TASMenu.PerformLayout(); this.TasStatusStrip.ResumeLayout(false); @@ -800,10 +802,10 @@ private void InitializeComponent() this.BranchesMarkersSplit.Panel2.ResumeLayout(false); ((System.ComponentModel.ISupportInitialize)(this.BranchesMarkersSplit)).EndInit(); this.BranchesMarkersSplit.ResumeLayout(false); - this.MainVertialSplit.Panel1.ResumeLayout(false); this.MainVertialSplit.Panel2.ResumeLayout(false); ((System.ComponentModel.ISupportInitialize)(this.MainVertialSplit)).EndInit(); this.MainVertialSplit.ResumeLayout(false); + this.ColumnRightClickMenu.ResumeLayout(false); this.ResumeLayout(false); this.PerformLayout(); @@ -819,7 +821,6 @@ private void InitializeComponent() private BizHawk.WinForms.Controls.ToolStripMenuItemEx SaveAsTASMenuItem; private BizHawk.WinForms.Controls.ToolStripSeparatorEx toolStripSeparator1; private BizHawk.WinForms.Controls.ToolStripMenuItemEx EditSubMenu; - private InputRoll TasView; private BizHawk.WinForms.Controls.ToolStripMenuItemEx RecentSubMenu; private BizHawk.WinForms.Controls.ToolStripSeparatorEx toolStripSeparator3; private BizHawk.WinForms.Controls.ToolStripMenuItemEx InsertFrameMenuItem; @@ -879,7 +880,6 @@ private void InitializeComponent() private BizHawk.WinForms.Controls.ToolStripMenuItemEx ClearGreenzoneMenuItem; private BizHawk.WinForms.Controls.ToolStripSeparatorEx GreenzoneICheckSeparator; private BizHawk.WinForms.Controls.ToolStripMenuItemEx StateHistoryIntegrityCheckMenuItem; - private BizHawk.WinForms.Controls.ToolStripMenuItemEx ColumnsSubMenu; private BizHawk.WinForms.Controls.ToolStripSeparatorEx toolStripSeparator19; private BizHawk.WinForms.Controls.ToolStripMenuItemEx CancelSeekContextMenuItem; private BizHawk.WinForms.Controls.ToolStripSeparatorEx StartFromNowSeparator; @@ -913,5 +913,11 @@ private void InitializeComponent() private BizHawk.WinForms.Controls.ToolStripMenuItemEx SaveBk2BackupMenuItem; private System.Windows.Forms.ToolTip toolTip1; private System.Windows.Forms.ToolStripMenuItem TAStudioSettingsToolStripMenuItem; + private System.Windows.Forms.ContextMenuStrip ColumnRightClickMenu; + private System.Windows.Forms.ToolStripMenuItem AutoHoldContextMenuItem; + private System.Windows.Forms.ToolStripMenuItem HideColumnContextMenuItem; + private System.Windows.Forms.ToolStripMenuItem ShowColumnsContextMenuItem; + private System.Windows.Forms.ToolStripMenuItem NewInputRollContextMenuItem; + private System.Windows.Forms.ToolStripMenuItem DeleteInputRollContextMenuItem; } } \ No newline at end of file diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IToolForm.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IToolForm.cs index fccc77fad62..8ea5d6cb7d6 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IToolForm.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IToolForm.cs @@ -81,9 +81,9 @@ protected override void UpdateAfter() return; } - bool refreshNeeded = TasView.IsPartiallyVisible(Emulator.Frame) || - TasView.IsPartiallyVisible(_lastRefresh) || - TasView.RowCount != CurrentTasMovie.InputLogLength + 1; + bool refreshNeeded = IsRowVisibleAnyRoll(Emulator.Frame) || + IsRowVisibleAnyRoll(_lastRefresh) || + _inputRolls[0].RowCount != CurrentTasMovie.InputLogLength + 1; if (Settings.AutoadjustInput) { //refreshNeeded = AutoAdjustInput(); diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs index 3090d2fe960..89f085f2a35 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs @@ -47,7 +47,7 @@ private string/*?*/ AxisEditColumn _axisTypedValue = ""; _didAxisType = false; _axisRestoreId = CurrentTasMovie.ChangeLog.MostRecentId; - TasView.SuspendHotkeys = true; + _inputRolls.ForEach(static r => r.SuspendHotkeys = true); } else { @@ -56,7 +56,7 @@ private string/*?*/ AxisEditColumn _didAxisType = false; CurrentTasMovie.ChangeLog.EndBatch(); } - TasView.SuspendHotkeys = false; + _inputRolls.ForEach(static r => r.SuspendHotkeys = false); } } } @@ -79,7 +79,7 @@ private void BeginAxisMouseEdit(int yPos) _didAxisType = false; _axisRestoreId = CurrentTasMovie.ChangeLog.MostRecentId; - CurrentTasMovie.ChangeLog.BeginNewBatch($"Axis mouse edit, frame {TasView.SelectedRows.First()}"); + CurrentTasMovie.ChangeLog.BeginNewBatch($"Axis mouse edit, frame {GetSelection().First()}"); } public bool AxisEditingMode => AxisEditColumn != null; @@ -102,8 +102,6 @@ private void BeginAxisMouseEdit(int yPos) private ControllerDefinition ControllerType => MovieSession.MovieController.Definition; public bool WasRecording { get; set; } - public AutoPatternBool[] BoolPatterns; - public AutoPatternAxis[] AxisPatterns; public void StopSeeking(bool skipRecModeCheck = false) { @@ -139,7 +137,217 @@ public void StopSeeking(bool skipRecModeCheck = false) private Bitmap icon_anchor_lag => Properties.Resources.icon_anchor_lag; private Bitmap icon_anchor => Properties.Resources.icon_anchor; - private void TasView_QueryItemIcon(int index, RollColumn column, ref Bitmap bitmap, ref int offsetX, ref int offsetY) + private Panel _tasViewPanel; + private HScrollBar _tasViewHBar = new(); + private VScrollBar _tasViewVBar = new(); + + private List _inputRolls = new(); + + /// + /// The selected input roll. Only the selected input roll's row selection will be considered. + /// + private InputRoll _activeInputRoll; + private Dictionary _rollDefinitions = new(); + + private bool AnyRowsSelected => _activeInputRoll.AnyRowsSelected; + private int FirstSelectedRowIndex => _activeInputRoll.FirstSelectedRowIndex; + private int LastSelectedRowIndex => _activeInputRoll.LastSelectedRowIndex; + private int? SelectionEndIndex => _activeInputRoll.SelectionEndIndex; + + private int FirstVisibleRowAllRolls => _inputRolls.Min(static r => r.FirstVisibleRow); + + private Cell/*?*/ CurrentCell => _inputRolls.Find(static r => r.CurrentCell != null)?.CurrentCell; + + private RollColumn/*?*/ _clickedColumn; + + private bool IsRowSelected(int frame) => _activeInputRoll.IsRowSelected(frame); + + private bool IsRowVisibleAnyRoll(int frame) => _inputRolls.Exists(r => r.IsPartiallyVisible(frame)); + + private InputRoll MakeInputRoll(int? insertAfter = null) + { + int index; + if (insertAfter == null) index = _inputRolls.Count; + else index = insertAfter.Value + 1; + + InputRoll roll = new() + { + // non user configurable + AllowColumnReorder = false, + AllowColumnResize = false, + AllowMassNavigationShortcuts = false, + AllowRightClickSelection = false, + CellHeightPadding = 0, + ChangeSelectionWhenPaging = false, + FullRowSelect = true, + InputPaintingMode = true, + LetKeysModifySelection = true, + Rotatable = true, + // user configurable + AlwaysScroll = Settings.FollowCursorAlwaysScroll, + Font = Settings.TasViewFont, + RowCount = (CurrentTasMovie?.InputLogLength ?? 0) + 1, + ScrollMethod = Settings.FollowCursorScrollMethod, + ScrollSpeed = Settings.ScrollSpeed, + // per-movie configurable + HideWasLagFrames = _movieSettings.HideWasLagFrames, + HorizontalOrientation = _movieSettings.HorizontalOrientation, + LagFramesToHide = _movieSettings.LagFramesToHide, + }; + + roll.KeyDown += TasView_KeyDown; + roll.MouseDoubleClick += TasView_MouseDoubleClick; + roll.MouseDown += TasView_MouseDown; + roll.MouseUp += TasView_MouseUp; + roll.MouseEnter += TasView_MouseEnter; + roll.MouseLeave += TAStudio_MouseLeave; + roll.MouseMove += TasView_MouseMove; + + roll.PointedCellChanged += TasView_PointedCellChanged; + roll.ColumnClick += TasView_ColumnClick; + roll.ColumnRightClick += TasView_ColumnRightClick; + roll.SelectedIndexChanged += TasView_SelectedIndexChanged; + roll.RightMouseScrolled += TasView_MouseWheel; + roll.ColumnReordered += TasView_ColumnReordered; + roll.CellDropped += TasView_CellDropped; + roll.RotationChanged += HandleRotationChanged; + roll.ColumnsChanged += RepositionRolls; + roll.RowScroll += TasView_RowScroll; + + roll.QueryItemText += TasView_QueryItemText; + roll.QueryItemBkColor += TasView_QueryItemBkColor; + roll.QueryRowBkColor += TasView_QueryRowBkColor; + roll.QueryItemIcon += TasView_QueryItemIcon; + roll.QueryFrameLag += TasView_QueryFrameLag; + roll.QueryShouldSelectCell += TasView_QueryShouldSelect; + + roll.CellHovered += (_, e) => + { + if (e.NewCell.RowIndex is null) + { + toolTip1.Show(e.NewCell.Column!.Name, roll, roll.PointToClient(Cursor.HotSpot)); + } + }; + + if (_inputRolls.Count != 0 && Settings.ScrollSync) + { + roll.FirstVisibleRow = _inputRolls[0].FirstVisibleRow; + } + + _inputRolls.Insert(index, roll); + _tasViewPanel.Controls.Add(roll); + + return roll; + } + + private void RepositionRolls() + { + if (_inputRolls[0].HorizontalOrientation) + { + int margin = _inputRolls[0].Margin.Top; + + int y = margin; + for (int i = 0; i < _inputRolls.Count; i++) + { + _inputRolls[i].SuspendDrawing(); + _inputRolls[i].Top = y - _tasViewVBar.Value; + _inputRolls[i].Height = _inputRolls[i].TotalColWidth + 1 + _tasViewHBar.Height; + + y += _inputRolls[i].Height + margin; + } + + _tasViewVBar.Visible = y >= _tasViewPanel.Height; + _tasViewHBar.Visible = false; + InputRoll lastRoll = _inputRolls[_inputRolls.Count - 1]; + if (!_tasViewVBar.Visible) lastRoll.Height += _tasViewPanel.Height - y; + + int desiredMaximum = _inputRolls[_inputRolls.Count - 1].Bottom + _tasViewVBar.Value - _tasViewPanel.Height + 1; + desiredMaximum = Math.Max(0, desiredMaximum); + _tasViewVBar.Maximum = desiredMaximum + _tasViewVBar.LargeChange - 1; // scroll bar dumbness + _tasViewVBar.Minimum = 0; // 0 is default, but somehow it gets set to a low negative value + + foreach (InputRoll roll in _inputRolls) + { + roll.Width = _tasViewPanel.Width - (_tasViewVBar.Visible ? _tasViewVBar.Width : 0); + roll.Anchor = AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + roll.RepositionScrollbars(); + roll.ResumeDrawing(); + roll.Refresh(); + } + + if (_tasViewVBar.Value > desiredMaximum) + { + _tasViewVBar.Value = desiredMaximum; + RepositionRolls(); + } + } + else + { + int margin = _inputRolls[0].Margin.Left; + + int x = margin; + for (int i = 0; i < _inputRolls.Count; i++) + { + _inputRolls[i].SuspendDrawing(); + _inputRolls[i].Left = x - _tasViewHBar.Value; + _inputRolls[i].Width = _inputRolls[i].TotalColWidth + 1 + _tasViewVBar.Width; + + x += _inputRolls[i].Width + margin; + } + + _tasViewHBar.Visible = x >= _tasViewPanel.Width; + _tasViewVBar.Visible = false; + InputRoll lastRoll = _inputRolls[_inputRolls.Count - 1]; + if (!_tasViewHBar.Visible) lastRoll.Width += _tasViewPanel.Width - x; + + int desiredMaximum = _inputRolls[_inputRolls.Count - 1].Right + _tasViewHBar.Value - _tasViewPanel.Width + 1; + desiredMaximum = Math.Max(0, desiredMaximum); + _tasViewHBar.Maximum = desiredMaximum + _tasViewHBar.LargeChange - 1; // scroll bar dumbness + _tasViewHBar.Minimum = 0; // 0 is default, but somehow it gets set to a low negative value + + foreach (InputRoll roll in _inputRolls) + { + roll.Height = _tasViewPanel.Height - (_tasViewHBar.Visible ? _tasViewHBar.Height : 0); + roll.Anchor = AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom; + roll.RepositionScrollbars(); + roll.ResumeDrawing(); + roll.Refresh(); + } + + if (_tasViewHBar.Value > desiredMaximum) + { + _tasViewHBar.Value = desiredMaximum; + RepositionRolls(); + } + } + + _tasViewPanel.Refresh(); // without this, parts of input rolls remain drawn in spaces where there are no input rolls anymore + } + + private void RemoveAllRolls() + { + foreach (InputRoll r in _inputRolls) + _tasViewPanel.Controls.Remove(r); + _inputRolls.Clear(); + _rollDefinitions.Clear(); + } + + private void MakeInputRollsFromSettings() + { + RemoveAllRolls(); + foreach (RollColumns cols in _movieSettings.Columns) + { + InputRoll roll = MakeInputRoll(); + roll.AllColumns.AddRange(cols); + } + + _movieSettings.Columns = _inputRolls.Select(static r => r.AllColumns).ToArray(); + + _activeInputRoll = _inputRolls[0]; + _inputRolls.ForEach(UpdateInputRollDefinition); // after setting active roll + } + + private void TasView_QueryItemIcon(InputRoll sender, int index, RollColumn column, ref Bitmap bitmap, ref int offsetX, ref int offsetY) { if (!_engaged || _initializing) { @@ -158,7 +366,7 @@ private void TasView_QueryItemIcon(int index, RollColumn column, ref Bitmap bitm if (columnName == CursorColumnName) { - if (TasView.HorizontalOrientation) + if (sender.HorizontalOrientation) { offsetX = -1; offsetY = 5; @@ -167,12 +375,12 @@ private void TasView_QueryItemIcon(int index, RollColumn column, ref Bitmap bitm if (index == Emulator.Frame) { bitmap = index == _seekingTo - ? TasView.HorizontalOrientation ? ts_v_arrow_green_blue : ts_h_arrow_green_blue - : TasView.HorizontalOrientation ? ts_v_arrow_blue : ts_h_arrow_blue; + ? sender.HorizontalOrientation ? ts_v_arrow_green_blue : ts_h_arrow_green_blue + : sender.HorizontalOrientation ? ts_v_arrow_blue : ts_h_arrow_blue; } else if (index == RestorePositionFrame) { - bitmap = TasView.HorizontalOrientation ? + bitmap = sender.HorizontalOrientation ? ts_v_arrow_green : ts_h_arrow_green; } @@ -194,7 +402,7 @@ private void TasView_QueryItemIcon(int index, RollColumn column, ref Bitmap bitm } } - private void TasView_QueryItemBkColor(int index, RollColumn column, ref Color color) + private void TasView_QueryItemBkColor(InputRoll sender, int index, RollColumn column, ref Color color) { if (!_engaged || _initializing) { @@ -227,7 +435,7 @@ private void TasView_QueryItemBkColor(int index, RollColumn column, ref Color co color = Color.FromArgb(0x60, 0xFF, 0xFF, 0xFF); } } - else if (columnName == AxisEditColumn && TasView.IsRowSelected(index)) + else if (columnName == AxisEditColumn && sender.IsRowSelected(index)) { color = Palette.AnalogEdit_Col; } @@ -242,7 +450,7 @@ private void TasView_QueryItemBkColor(int index, RollColumn column, ref Color co } } - private void TasView_QueryRowBkColor(int index, ref Color color) + private void TasView_QueryRowBkColor(InputRoll sender, int index, ref Color color) { if (!_engaged || _initializing) { @@ -286,7 +494,7 @@ private void TasView_QueryRowBkColor(int index, ref Color color) } } - private bool TasView_QueryShouldSelect(MouseButtons button) + private bool TasView_QueryShouldSelect(InputRoll sender, MouseButtons button) { if (AxisEditingMode) { @@ -295,7 +503,7 @@ private bool TasView_QueryShouldSelect(MouseButtons button) // This just makes it easier to select multiple rows with axis editing mode, by allowing multiple row selection when clicking columns that aren't the frame column. return true; } - else if (TasView.CurrentCell.Column.Name == AxisEditColumn && TasView.IsRowSelected(TasView.CurrentCell.RowIndex.Value)) + else if (sender.CurrentCell.Column.Name == AxisEditColumn && sender.IsRowSelected(sender.CurrentCell.RowIndex.Value)) { // We will start editing via mouse, so don't unselect if we have multiple selected rows. return false; @@ -308,7 +516,7 @@ private bool TasView_QueryShouldSelect(MouseButtons button) } } - if (TasView.CurrentCell.Column.Name == FrameColumnName) + if (sender.CurrentCell.Column.Name == FrameColumnName) { return true; } @@ -320,7 +528,7 @@ private bool TasView_QueryShouldSelect(MouseButtons button) { // This may not be necessary, but it is the behavior we've always had based on copying what TASeditor does. // Ctrl-click on a button selects the same as a regular click: the only row left selected is the one clicked - if (ModifierKeys == Keys.Control) TasView.DeselectAll(); + if (ModifierKeys == Keys.Control) sender.DeselectAll(); return true; } } @@ -333,7 +541,7 @@ private string FrameToStringPadded(int index) 4, NumberExtensions.Log10(Math.Max(CurrentTasMovie.InputLogLength, 1)))]); - private void TasView_QueryItemText(int index, RollColumn column, out string text, ref int offsetX, ref int offsetY) + private void TasView_QueryItemText(InputRoll sender, int index, RollColumn column, out string text, ref int offsetX, ref int offsetY) { if (!_engaged || _initializing) { @@ -363,13 +571,13 @@ private void TasView_QueryItemText(int index, RollColumn column, out string text } else if (columnName == FrameColumnName) { - offsetX = TasView.HorizontalOrientation ? 2 : 7; + offsetX = sender.HorizontalOrientation ? 2 : 7; text = FrameToStringPadded(index); } else { // Display typed float value (string "-" can't be parsed, so CurrentTasMovie.DisplayValue can't return it) - bool axisEditing = columnName == AxisEditColumn && TasView.IsRowSelected(index); + bool axisEditing = columnName == AxisEditColumn && sender.IsRowSelected(index); if (axisEditing && _didAxisType) { text = _axisTypedValue; @@ -391,7 +599,7 @@ private void TasView_QueryItemText(int index, RollColumn column, out string text } } - private bool TasView_QueryFrameLag(int index, bool hideWasLag) + private bool TasView_QueryFrameLag(InputRoll sender, int index, bool hideWasLag) { var lag = CurrentTasMovie[index]; return (lag.Lagged.HasValue && lag.Lagged.Value) || (hideWasLag && lag.WasLagged.HasValue && lag.WasLagged.Value); @@ -399,17 +607,18 @@ private bool TasView_QueryFrameLag(int index, bool hideWasLag) private void TasView_ColumnClick(object sender, InputRoll.ColumnClickEventArgs e) { - if (TasView.AnyRowsSelected) + if (AnyRowsSelected) { var columnName = e.Column!.Name; + InputRoll roll = (InputRoll)sender; if (columnName == FrameColumnName) { - CurrentTasMovie.Markers.Add(TasView.SelectionEndIndex!.Value, ""); + CurrentTasMovie.Markers.Add(LastSelectedRowIndex, ""); } else if (columnName != CursorColumnName) { - var buttonName = TasView.CurrentCell.Column!.Name; + var buttonName = roll.CurrentCell.Column!.Name; if (ControllerType.BoolButtons.Contains(buttonName)) { @@ -417,7 +626,7 @@ private void TasView_ColumnClick(object sender, InputRoll.ColumnClickEventArgs e { // nifty taseditor logic // your TAS Editor logic failed us (it didn't account for non-contiguous `SelectedRows`) --yoshi - var selection = TasView.SelectedRows.ToArray(); // sorted asc, length >= 1 + var selection = GetSelection().ToArray(); // sorted asc, length >= 1 var allPressed = selection[selection.Length - 1] != CurrentTasMovie.FrameCount // last movie frame can't have input, but can be selected && selection.All(index => CurrentTasMovie.BoolIsPressed(index, buttonName)); CurrentTasMovie.ChangeLog.BeginNewBatch($"{(allPressed ? "Unset" : "Set")} {selection.Length} frames of {buttonName} starting at {selection[0]}"); @@ -432,12 +641,12 @@ private void TasView_ColumnClick(object sender, InputRoll.ColumnClickEventArgs e } else { - BoolPatterns[ControllerType.BoolButtons.IndexOf(buttonName)].Reset(); + _movieSettings.BoolPatterns[ControllerType.BoolButtons.IndexOf(buttonName)].Reset(); CurrentTasMovie.SingleInvalidation(() => { - foreach (var index in TasView.SelectedRows) + foreach (var index in GetSelection()) { - CurrentTasMovie.SetBoolState(index, buttonName, BoolPatterns[ControllerType.BoolButtons.IndexOf(buttonName)].GetNextValue()); + CurrentTasMovie.SetBoolState(index, buttonName, _movieSettings.BoolPatterns[ControllerType.BoolButtons.IndexOf(buttonName)].GetNextValue()); } }); } @@ -453,26 +662,30 @@ private void TasView_ColumnClick(object sender, InputRoll.ColumnClickEventArgs e private void TasView_ColumnRightClick(object sender, InputRoll.ColumnClickEventArgs e) { - var col = e.Column!; + _clickedColumn = e.Column!; + ColumnRightClickMenu.Show(GetLocationForContextMenu(ColumnRightClickMenu)); + } + + private void ToggleAutoFire(RollColumn col) + { if (col.Name is FrameColumnName or CursorColumnName) return; col.Emphasis = !col.Emphasis; UpdateAutoFire(col.Name, col.Emphasis); - TasView.Refresh(); } private void UpdateAutoFire() { - for (int i = 2; i < TasView.AllColumns.Count; i++) + for (int i = 2; i < _inputRolls[0].AllColumns.Count; i++) { - UpdateAutoFire(TasView.AllColumns[i].Name, TasView.AllColumns[i].Emphasis); + UpdateAutoFire(_inputRolls[0].AllColumns[i].Name, _inputRolls[0].AllColumns[i].Emphasis); } } public void UpdateAutoFire(string button, bool? isOn) { // No value means don't change whether it's on or off. - isOn ??= TasView.AllColumns.Find(c => c.Name == button).Emphasis; + isOn ??= _inputRolls[0].AllColumns.Find(c => c.Name == button).Emphasis; // use custom pattern if set bool useCustom = Settings.PatternSelection == TAStudioSettings.PatternSelectionEnum.Custom; @@ -487,7 +700,7 @@ public void UpdateAutoFire(string button, bool? isOn) if (useCustom) { - InputManager.StickyAutofireController.SetButtonAutofire(button, true, BoolPatterns[ControllerType.BoolButtons.IndexOf(button)]); + InputManager.StickyAutofireController.SetButtonAutofire(button, true, _movieSettings.BoolPatterns[ControllerType.BoolButtons.IndexOf(button)]); } else if (autoHold) { @@ -507,7 +720,7 @@ public void UpdateAutoFire(string button, bool? isOn) int holdValue = ControllerType.Axes[button].Range.EndInclusive; // it's not clear what value to use for auto-hold, just use max i guess if (useCustom) { - InputManager.StickyAutofireController.SetAxisAutofire(button, holdValue, AxisPatterns[ControllerType.Axes.IndexOf(button)]); + InputManager.StickyAutofireController.SetAxisAutofire(button, holdValue, _movieSettings.AxisPatterns[ControllerType.Axes.IndexOf(button)]); } else if (autoHold) { @@ -529,7 +742,10 @@ private void TasView_MouseEnter(object sender, EventArgs e) { if (ContainsFocus) { - TasView.Select(); + // We want to ensure that one of the input rolls is selected, so it gets keyboard input (such as arrow keys to change the selected row) + // We could select the input roll that the mouse is over, but this could be confusing. + // It is better to select the most recent active one. + _activeInputRoll.Select(); } } @@ -541,9 +757,17 @@ private void TasView_MouseDown(object sender, MouseEventArgs e) return; } + InputRoll roll = (InputRoll)sender; + if (roll != _activeInputRoll) + { + _activeInputRoll.DeselectAll(); + _activeInputRoll = roll; + UpdateActiveMovieInputs(); + } + // only on mouse button down, check that the pointed to cell is the correct one (can be wrong due to scroll while playing) - TasView._programmaticallyChangingRow = true; - TasView.PointMouseToNewCell(); + roll._programmaticallyChangingRow = true; + roll.PointMouseToNewCell(); if (e.Button == MouseButtons.Middle) { @@ -568,7 +792,7 @@ private void TasView_MouseDown(object sender, MouseEventArgs e) return; } - if (TasView.CurrentCell is not { RowIndex: int frame, Column: RollColumn targetCol }) return; + if (roll.CurrentCell is not { RowIndex: int frame, Column: RollColumn targetCol }) return; var buttonName = targetCol.Name; WasRecording = CurrentTasMovie.IsRecording() || WasRecording; @@ -600,12 +824,12 @@ private void TasView_MouseDown(object sender, MouseEventArgs e) if (ModifierKeys == Keys.Alt && CurrentTasMovie.Markers.IsMarker(frame)) { // TODO - TasView.DragCurrentCell(); + roll.DragCurrentCell(); } else { _startSelectionDrag = true; - _selectionDragState = TasView.IsRowSelected(frame); + _selectionDragState = roll.IsRowSelected(frame); } } else @@ -625,16 +849,16 @@ private void TasView_MouseDown(object sender, MouseEventArgs e) || Settings.PatternPaintMode == TAStudioSettings.PatternPaintModeEnum.Always || (targetCol.Emphasis && Settings.PatternPaintMode == TAStudioSettings.PatternPaintModeEnum.AutoFireOnly)) { - BoolPatterns[ControllerType.BoolButtons.IndexOf(buttonName)].Reset(); + _movieSettings.BoolPatterns[ControllerType.BoolButtons.IndexOf(buttonName)].Reset(); _patternPaint = true; _startRow = frame; _boolPaintState = !CurrentTasMovie.BoolIsPressed(frame, buttonName); } else if (altOrShift4State is Keys.Shift) { - if (!TasView.AnyRowsSelected) return; + if (!AnyRowsSelected) return; - var iFirstSelectedRow = TasView.FirstSelectedRowIndex; + var iFirstSelectedRow = FirstSelectedRowIndex; var (firstSel, lastSel) = frame <= iFirstSelectedRow ? (frame, iFirstSelectedRow) : (iFirstSelectedRow, frame); @@ -678,8 +902,8 @@ private void TasView_MouseDown(object sender, MouseEventArgs e) if (Settings.PatternPaintMode == TAStudioSettings.PatternPaintModeEnum.Always || (targetCol.Emphasis && Settings.PatternPaintMode == TAStudioSettings.PatternPaintModeEnum.AutoFireOnly)) { - AxisPatterns[ControllerType.Axes.IndexOf(buttonName)].Reset(); - CurrentTasMovie.SetAxisState(frame, buttonName, AxisPatterns[ControllerType.Axes.IndexOf(buttonName)].GetNextValue()); + _movieSettings.AxisPatterns[ControllerType.Axes.IndexOf(buttonName)].Reset(); + CurrentTasMovie.SetAxisState(frame, buttonName, _movieSettings.AxisPatterns[ControllerType.Axes.IndexOf(buttonName)].GetNextValue()); _patternPaint = true; } else @@ -695,7 +919,7 @@ private void TasView_MouseDown(object sender, MouseEventArgs e) } else // Double-click enters axis editing mode { - if (AxisEditColumn != null && (AxisEditColumn != buttonName || !TasView.IsRowSelected(frame))) + if (AxisEditColumn != null && (AxisEditColumn != buttonName || !IsRowSelected(frame))) { AxisEditColumn = null; } @@ -717,10 +941,10 @@ private void TasView_MouseDown(object sender, MouseEventArgs e) _rightClickControl = (ModifierKeys | Keys.Control) == ModifierKeys; _rightClickShift = (ModifierKeys | Keys.Shift) == ModifierKeys; _rightClickAlt = (ModifierKeys | Keys.Alt) == ModifierKeys; - if (TasView.IsRowSelected(frame)) + if (roll.IsRowSelected(frame)) { - _rightClickInput = new string[TasView.SelectedRows.Count()]; - _rightClickFrame = TasView.SelectionStartIndex!.Value; + _rightClickInput = new string[GetSelection().Count()]; + _rightClickFrame = FirstSelectedRowIndex; try { CurrentTasMovie.GetLogEntries().CopyTo(_rightClickFrame, _rightClickInput, 0, _rightClickInput.Length); @@ -872,7 +1096,7 @@ public bool FrameEdited(int frame) if (needsRefresh) { - if (TasView.IsPartiallyVisible(frame) || frame < TasView.FirstVisibleRow) + if (IsRowVisibleAnyRoll(frame) || frame < FirstVisibleRowAllRolls) { // frame < FirstVisibleRow: Greenzone in visible rows has been invalidated RefreshDialog(); @@ -884,10 +1108,10 @@ public bool FrameEdited(int frame) { _undoForm.UpdateValues(); } - if (TasView.RowCount != CurrentTasMovie.InputLogLength + 1) + if (_inputRolls[0].RowCount != CurrentTasMovie.InputLogLength + 1) { // Row count must always be kept up to date even if last row is not directly visible. - TasView.RowCount = CurrentTasMovie.InputLogLength + 1; + _inputRolls.ForEach(r => r.RowCount = CurrentTasMovie.InputLogLength + 1); return true; } } @@ -903,7 +1127,6 @@ private void ClearLeftMouseStates() _startSelectionDrag = false; _startBoolDrawColumn = ""; _startAxisDrawColumn = ""; - TasView.ReleaseCurrentCell(); CurrentTasMovie.ChangeLog.EndBatch(); @@ -914,46 +1137,54 @@ private void ClearLeftMouseStates() RefreshDialog(); // Even if no edits happened, the undo form may need updating because we potentially ended a batch. } + private Point GetLocationForContextMenu(ContextMenuStrip menu) + { + var offset = new Point(0); + var topLeft = Cursor.Position; + var bottomRight = new Point( + topLeft.X + menu.Width, + topLeft.Y + menu.Height); + var screen = DrawingExtensions.BoundsOfDisplayContaining(topLeft) + ?? default; //TODO is zeroed the correct fallback value? --yoshi + // if we don't fully fit, move to the other side of the pointer + if (bottomRight.X > screen.Right) + { + offset.X -= menu.Width; + } + if (bottomRight.Y > screen.Bottom) + { + offset.Y -= menu.Height; + } + topLeft.Offset(offset); + // if the screen is insultingly tiny, best we can do is avoid negative pos + return new( + Math.Max(0, topLeft.X), + Math.Max(0, topLeft.Y)); + } + private void TasView_MouseUp(object sender, MouseEventArgs e) { - if (e.Button == MouseButtons.Right && !TasView.IsPointingAtColumnHeader - && !_suppressContextMenu && !_leftButtonHeld && TasView.AnyRowsSelected) + InputRoll roll = (InputRoll)sender; + + if (e.Button == MouseButtons.Right && !roll.IsPointingAtColumnHeader + && !_suppressContextMenu && !_leftButtonHeld && AnyRowsSelected) { - if (CurrentTasMovie.FrameCount < TasView.SelectionEndIndex) + if (CurrentTasMovie.FrameCount < SelectionEndIndex) { // trying to be smart here // if a loaded branch log is shorter than selection, keep selection until you attempt to call context menu // you might need it when you load again the branch where this frame exists - TasView.DeselectAll(); + roll.DeselectAll(); SetTasViewRowCount(); } else { - var offset = new Point(0); - var topLeft = Cursor.Position; - var bottomRight = new Point( - topLeft.X + RightClickMenu.Width, - topLeft.Y + RightClickMenu.Height); - var screen = DrawingExtensions.BoundsOfDisplayContaining(topLeft) - ?? default; //TODO is zeroed the correct fallback value? --yoshi - // if we don't fully fit, move to the other side of the pointer - if (bottomRight.X > screen.Right) - { - offset.X -= RightClickMenu.Width; - } - if (bottomRight.Y > screen.Bottom) - { - offset.Y -= RightClickMenu.Height; - } - topLeft.Offset(offset); - // if the screen is insultingly tiny, best we can do is avoid negative pos - RightClickMenu.Show( - Math.Max(0, topLeft.X), - Math.Max(0, topLeft.Y)); + RightClickMenu.Show(GetLocationForContextMenu(RightClickMenu)); } } else if (e.Button == MouseButtons.Left) { + roll.ReleaseCurrentCell(); ClearLeftMouseStates(); } @@ -995,7 +1226,8 @@ private void WheelSeek(int count) private void TasView_MouseWheel(object sender, MouseEventArgs e) { - if (TasView.RightButtonHeld && TasView?.CurrentCell.RowIndex.HasValue == true) + InputRoll roll = (InputRoll)sender; + if (roll.RightButtonHeld && roll.CurrentCell.RowIndex.HasValue) { _suppressContextMenu = true; int notch = e.Delta / 120; @@ -1030,20 +1262,22 @@ public void RemoveMarker() private void TasView_MouseDoubleClick(object sender, MouseEventArgs e) { - if (TasView.CurrentCell?.Column is not { Name: var columnName }) return; + InputRoll roll = (InputRoll)sender; + if (roll.CurrentCell?.Column is not { Name: var columnName }) return; if (e.Button == MouseButtons.Left) { if (!AxisEditingMode && columnName is FrameColumnName) { - SetMarker(TasView.CurrentCell.RowIndex.Value); + SetMarker(roll.CurrentCell.RowIndex.Value); } } } private void TasView_PointedCellChanged(object sender, InputRoll.CellEventArgs e) { - toolTip1.SetToolTip(TasView, null); + InputRoll roll = (InputRoll)sender; + toolTip1.SetToolTip(roll, null); if (e.NewCell.RowIndex is null) { @@ -1089,8 +1323,8 @@ private void TasView_PointedCellChanged(object sender, InputRoll.CellEventArgs e { for (var i = startVal; i <= endVal; i++) { - if (!TasView.IsRowSelected(i)) - TasView.SelectRow(i, _selectionDragState); + if (!roll.IsRowSelected(i)) + roll.SelectRow(i, _selectionDragState); } SetSplicer(); @@ -1101,11 +1335,11 @@ private void TasView_PointedCellChanged(object sender, InputRoll.CellEventArgs e FramePaint(frame, startVal, endVal); } // Left-click - else if (TasView.IsPaintDown && !string.IsNullOrEmpty(_startBoolDrawColumn)) + else if (roll.IsPaintDown && !string.IsNullOrEmpty(_startBoolDrawColumn)) { BoolPaint(frame, startVal, endVal); } - else if (TasView.IsPaintDown && !string.IsNullOrEmpty(_startAxisDrawColumn)) + else if (roll.IsPaintDown && !string.IsNullOrEmpty(_startAxisDrawColumn)) { AxisPaint(frame, startVal, endVal); } @@ -1114,11 +1348,24 @@ private void TasView_PointedCellChanged(object sender, InputRoll.CellEventArgs e if (MouseButtonHeld) { - TasView.MakeIndexVisible(TasView.CurrentCell.RowIndex.Value); // todo: limit scrolling speed + roll.MakeIndexVisible(roll.CurrentCell.RowIndex.Value); // todo: limit scrolling speed SetTasViewRowCount(); // refreshes } } + private void TasView_RowScroll(InputRoll sender, EventArgs e) + { + if (Settings.ScrollSync) + { + foreach (InputRoll roll in _inputRolls) + { + if (roll == sender) continue; + roll.FirstVisibleRow = sender.FirstVisibleRow; + roll.Refresh(); + } + } + } + private void FramePaint(int frame, int startVal, int endVal) { CurrentTasMovie.SingleInvalidation(() => { @@ -1162,7 +1409,7 @@ private void FramePaint(int frame, int startVal, int endVal) { for (int i = startVal; i <= endVal; i++) { - CurrentTasMovie.SetFrame(i, _rightClickInput[(i - _rightClickFrame).Mod(_rightClickInput.Length)]); + CurrentTasMovie.PokeFrame(i, _rightClickInput[(i - _rightClickFrame).Mod(_rightClickInput.Length)]); } } } @@ -1172,14 +1419,14 @@ private void FramePaint(int frame, int startVal, int endVal) { for (int i = 0; i < _rightClickInput.Length; i++) // Re-set initial range, just to verify it's still there. { - CurrentTasMovie.SetFrame(_rightClickFrame + i, _rightClickInput[i]); + CurrentTasMovie.PokeFrame(_rightClickFrame + i, _rightClickInput[i]); } if (_rightClickOverInput != null) // Restore overwritten input from previous movement { for (int i = 0; i < _rightClickOverInput.Length; i++) { - CurrentTasMovie.SetFrame(_rightClickLastFrame + i, _rightClickOverInput[i]); + CurrentTasMovie.PokeFrame(_rightClickLastFrame + i, _rightClickOverInput[i]); } } else @@ -1192,7 +1439,7 @@ private void FramePaint(int frame, int startVal, int endVal) for (int i = 0; i < _rightClickInput.Length; i++) // Place copied input { - CurrentTasMovie.SetFrame(frame + i, _rightClickInput[i]); + CurrentTasMovie.PokeFrame(frame + i, _rightClickInput[i]); } } else if (_rightClickAlt) @@ -1209,12 +1456,12 @@ private void FramePaint(int frame, int startVal, int endVal) int shiftTo = shiftFrom + (_rightClickInput.Length * Math.Sign(shiftBy)); for (int i = 0; i < shiftInput.Length; i++) { - CurrentTasMovie.SetFrame(shiftTo + i, shiftInput[i]); + CurrentTasMovie.PokeFrame(shiftTo + i, shiftInput[i]); } for (int i = 0; i < _rightClickInput.Length; i++) { - CurrentTasMovie.SetFrame(frame + i, _rightClickInput[i]); + CurrentTasMovie.PokeFrame(frame + i, _rightClickInput[i]); } _rightClickFrame = frame; @@ -1245,7 +1492,7 @@ private void BoolPaint(int frame, int startVal, int endVal) } else { - setVal = BoolPatterns[ControllerType.BoolButtons.IndexOf(_startBoolDrawColumn)].GetNextValue(); + setVal = _movieSettings.BoolPatterns[ControllerType.BoolButtons.IndexOf(_startBoolDrawColumn)].GetNextValue(); } } @@ -1271,7 +1518,7 @@ private void AxisPaint(int frame, int startVal, int endVal) } else { - setVal = AxisPatterns[ControllerType.Axes.IndexOf(_startAxisDrawColumn)].GetNextValue(); + setVal = _movieSettings.AxisPatterns[ControllerType.Axes.IndexOf(_startAxisDrawColumn)].GetNextValue(); } } @@ -1298,6 +1545,52 @@ private void TasView_SelectedIndexChanged(object sender, EventArgs e) SetSplicer(); } + private void UpdateActiveMovieInputs() + { + if (Settings.EditInvisibleColumns) + { + CurrentTasMovie.ActiveControllerInputs = null; + } + else + { + CurrentTasMovie.ActiveControllerInputs = _rollDefinitions[_activeInputRoll]; + } + } + + private void UpdateInputRollDefinition(InputRoll roll) + { + var controlTuples = ControllerType.ControlsOrdered.SelectMany(x => x); + Dictionary controlSpecs = new(); + foreach (var tuple in controlTuples) + controlSpecs.Add(tuple.Name, tuple.AxisSpec); + + if (!roll.AllColumns.Any(c => !c.Visible && controlSpecs.ContainsKey(c.Name))) + { + _rollDefinitions[roll] = null; + UpdateActiveMovieInputs(); + return; + } + + ControllerDefinition visibleDefinition = new("visible"); + foreach (RollColumn col in roll.VisibleColumns) + { + if (!controlSpecs.TryGetValue(col.Name, out AxisSpec? maybeSpec)) + continue; // frame, cursor, or Lua column + + if (maybeSpec == null) + visibleDefinition.BoolButtons.Add(col.Name); + else + { + AxisSpec spec = maybeSpec.Value; + visibleDefinition.AddAxis(col.Name, spec.Range, spec.Neutral, spec.IsReversed, spec.Constraint); + } + } + + _rollDefinitions[roll] = visibleDefinition.MakeImmutable(); + + UpdateActiveMovieInputs(); + } + public void AnalogIncrementByOne() { AnalogChangeBy(1); @@ -1329,10 +1622,10 @@ private void AnalogChangeBy(int change) CurrentTasMovie.ChangeLog.EndBatch(); } - bool batch = CurrentTasMovie.ChangeLog.BeginNewBatch($"Axis change by {change}, frame {TasView.SelectedRows.First()}", true); + bool batch = CurrentTasMovie.ChangeLog.BeginNewBatch($"Axis change by {change}, frame {GetSelection().First()}", true); CurrentTasMovie.SingleInvalidation(() => { - foreach (int frame in TasView.SelectedRows) + foreach (int frame in GetSelection()) { int value = CurrentTasMovie.GetAxisState(frame, AxisEditColumn) + change; value = value.ConstrainWithin(ControllerType.Axes[AxisEditColumn].Range); @@ -1350,7 +1643,7 @@ public void AnalogMax() if (!AxisEditingMode) return; int value = ControllerType.Axes[AxisEditColumn].Max; - foreach (int frame in TasView.SelectedRows) + foreach (int frame in GetSelection()) { CurrentTasMovie.SetAxisState(frame, AxisEditColumn, value); } @@ -1363,7 +1656,7 @@ public void AnalogMin() if (!AxisEditingMode) return; int value = ControllerType.Axes[AxisEditColumn].Min; - foreach (int frame in TasView.SelectedRows) + foreach (int frame in GetSelection()) { CurrentTasMovie.SetAxisState(frame, AxisEditColumn, value); } @@ -1412,7 +1705,7 @@ public void EditAnalogProgrammatically(KeyEventArgs e) { if (!_didAxisType) { - _axisTypedValue = CurrentTasMovie.GetAxisState(TasView.SelectedRows.First(), AxisEditColumn).ToString(); + _axisTypedValue = CurrentTasMovie.GetAxisState(GetSelection().First(), AxisEditColumn).ToString(); _didAxisType = true; } if (_axisTypedValue.Length != 0) @@ -1432,7 +1725,7 @@ public void EditAnalogProgrammatically(KeyEventArgs e) if (_axisTypedValue != prevTyped) { - CurrentTasMovie.ChangeLog.BeginNewBatch($"Axis edit: {TasView.SelectedRows.First()}", true); + CurrentTasMovie.ChangeLog.BeginNewBatch($"Axis edit: {GetSelection().First()}", true); _didAxisType = true; int value; @@ -1454,7 +1747,7 @@ public void EditAnalogProgrammatically(KeyEventArgs e) CurrentTasMovie.SingleInvalidation(() => { - foreach (int row in TasView.SelectedRows) + foreach (int row in GetSelection()) { CurrentTasMovie.SetAxisState(row, AxisEditColumn, value); } diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs index 58a8e48500c..453cb1abf55 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; @@ -25,7 +26,7 @@ private void FileSubMenu_DropDownOpened(object sender, EventArgs e) saveSelectionToMacroToolStripMenuItem.Enabled = placeMacroAtSelectionToolStripMenuItem.Enabled = recentMacrosToolStripMenuItem.Enabled = - TasView.AnyRowsSelected; + AnyRowsSelected; } private void NewFromSubMenu_DropDownOpened(object sender, EventArgs e) @@ -60,7 +61,7 @@ private void StartANewProjectFromSaveRamMenuItem_Click(object sender, EventArgs if (AskSaveChanges()) { var saveRam = SaveRamEmulator?.CloneSaveRam(clearDirty: false) ?? throw new Exception("No SaveRam"); - GoToFrame(TasView.AnyRowsSelected ? TasView.FirstSelectedRowIndex : 0); + GoToFrame(AnyRowsSelected ? FirstSelectedRowIndex : 0); var result = CurrentTasMovie.ConvertToSaveRamAnchoredMovie(saveRam); DisplayMessageIfFailed(() => result, "Failed to create movie."); @@ -152,14 +153,15 @@ private void SaveBk2BackupMenuItem_Click(object sender, EventArgs e) private void SaveSelectionToMacroMenuItem_Click(object sender, EventArgs e) { - if (!TasView.AnyRowsSelected) + if (!AnyRowsSelected) { return; } - if (TasView.SelectionEndIndex == CurrentTasMovie.InputLogLength) + int endIndex = LastSelectedRowIndex; + if (LastSelectedRowIndex == CurrentTasMovie.InputLogLength) { - TasView.SelectRow(CurrentTasMovie.InputLogLength, false); + endIndex--; } var file = SaveFileDialog( @@ -171,11 +173,11 @@ private void SaveSelectionToMacroMenuItem_Click(object sender, EventArgs e) if (file != null) { - var selectionStart = TasView.SelectionStartIndex!.Value; + var selectionStart = FirstSelectedRowIndex; MovieZone macro = new( CurrentTasMovie, start: selectionStart, - length: TasView.SelectionEndIndex!.Value - selectionStart + 1); + length: endIndex - selectionStart + 1); FileWriteResult saveResult = macro.Save(file.FullName); if (saveResult.IsError) @@ -194,7 +196,7 @@ private void SaveSelectionToMacroMenuItem_Click(object sender, EventArgs e) private void PlaceMacroAtSelectionMenuItem_Click(object sender, EventArgs e) { - if (!TasView.AnyRowsSelected) + if (!AnyRowsSelected) { return; } @@ -292,11 +294,11 @@ private void EditSubMenu_DropDownOpened(object sender, EventArgs e) TruncateMenuItem.Enabled = InsertFrameMenuItem.Enabled = InsertNumFramesMenuItem.Enabled = - TasView.AnyRowsSelected; + AnyRowsSelected; ReselectClipboardMenuItem.Enabled = PasteMenuItem.Enabled = - PasteInsertMenuItem.Enabled = TasView.AnyRowsSelected + PasteInsertMenuItem.Enabled = AnyRowsSelected && Clipboard.ContainsText(); ClearGreenzoneMenuItem.Enabled = @@ -335,57 +337,57 @@ private void ShowUndoHistoryMenuItem_Click(object sender, EventArgs e) private void DeselectMenuItem_Click(object sender, EventArgs e) { - TasView.DeselectAll(); - TasView.Refresh(); + _activeInputRoll.DeselectAll(); + _activeInputRoll.Refresh(); } /// TODO merge w/ Deselect? private void SelectAllMenuItem_Click(object sender, EventArgs e) { - TasView.SelectAll(); - TasView.Refresh(); + _activeInputRoll.SelectAll(); + _activeInputRoll.Refresh(); } private void SelectBetweenMarkersMenuItem_Click(object sender, EventArgs e) { - if (TasView.AnyRowsSelected) + if (AnyRowsSelected) { - var selectionEnd = TasView.SelectionEndIndex ?? 0; + var selectionEnd = LastSelectedRowIndex; var prevMarker = CurrentTasMovie.Markers.PreviousOrCurrent(selectionEnd); var nextMarker = CurrentTasMovie.Markers.Next(selectionEnd); int prev = prevMarker?.Frame ?? 0; int next = nextMarker?.Frame ?? CurrentTasMovie.InputLogLength; - TasView.DeselectAll(); + _activeInputRoll.DeselectAll(); for (int i = prev; i < next; i++) { - TasView.SelectRow(i, true); + _activeInputRoll.SelectRow(i, true); } + _activeInputRoll.Refresh(); SetSplicer(); - TasView.Refresh(); } } private void ReselectClipboardMenuItem_Click(object sender, EventArgs e) { - TasView.DeselectAll(); + _activeInputRoll.DeselectAll(); foreach (var item in _tasClipboard) { - TasView.SelectRow(item.Frame, true); + _activeInputRoll.SelectRow(item.Frame, true); } + _activeInputRoll.Refresh(); SetSplicer(); - TasView.Refresh(); } private void CopyMenuItem_Click(object sender, EventArgs e) { - if (TasView.AnyRowsSelected) + if (AnyRowsSelected) { _tasClipboard.Clear(); - var list = TasView.SelectedRows.ToArray(); + var list = GetSelection().ToArray(); var sb = new StringBuilder(); foreach (var index in list) @@ -418,7 +420,7 @@ private void PasteInsertMenuItem_Click(object sender, EventArgs e) private void MaybePasteFromClipboard(bool overwriteSelection) { - if (!TasView.AnyRowsSelected) return; + if (!AnyRowsSelected) return; var input = Clipboard.GetText(); if (string.IsNullOrWhiteSpace(input)) return; string[] lines = input.Split('\n'); @@ -435,10 +437,11 @@ private void MaybePasteFromClipboard(bool overwriteSelection) return; } + // remove inputs not in _visibleDefintion _tasClipboard.Add(new TasClipboardEntry(i, line)); } - var selectionStart = TasView.SelectionStartIndex ?? 0; + var selectionStart = FirstSelectedRowIndex; var inputStates = _tasClipboard.Select(static x => x.ControllerState); if (overwriteSelection) CurrentTasMovie.CopyOverInput(selectionStart, inputStates); else CurrentTasMovie.InsertInput(selectionStart, inputStates); @@ -446,10 +449,10 @@ private void MaybePasteFromClipboard(bool overwriteSelection) private void CutMenuItem_Click(object sender, EventArgs e) { - if (TasView.AnyRowsSelected) + if (AnyRowsSelected) { _tasClipboard.Clear(); - var list = TasView.SelectedRows.ToArray(); + var list = GetSelection().ToArray(); var sb = new StringBuilder(); foreach (var index in list) // copy of CopyMenuItem_Click() @@ -472,12 +475,12 @@ private void CutMenuItem_Click(object sender, EventArgs e) private void ClearFramesMenuItem_Click(object sender, EventArgs e) { - if (!TasView.AnyRowsSelected) return; + if (!AnyRowsSelected) return; CurrentTasMovie.SingleInvalidation(() => { - CurrentTasMovie.ChangeLog.BeginNewBatch($"Clear frames {TasView.SelectionStartIndex}-{TasView.SelectionEndIndex}"); - foreach (int frame in TasView.SelectedRows) + CurrentTasMovie.ChangeLog.BeginNewBatch($"Clear frames {FirstSelectedRowIndex}-{LastSelectedRowIndex}"); + foreach (int frame in GetSelection()) { CurrentTasMovie.ClearFrame(frame); } @@ -488,10 +491,9 @@ private void ClearFramesMenuItem_Click(object sender, EventArgs e) private void DeleteFramesMenuItem_Click(object sender, EventArgs e) { - if (TasView.AnyRowsSelected) + if (AnyRowsSelected) { - var selectionStart = TasView.SelectionStartIndex; - var rollBackFrame = selectionStart ?? 0; + var rollBackFrame = FirstSelectedRowIndex; if (rollBackFrame >= CurrentTasMovie.InputLogLength) { // Cannot delete non-existent frames @@ -499,7 +501,7 @@ private void DeleteFramesMenuItem_Click(object sender, EventArgs e) return; } - CurrentTasMovie.RemoveFrames(TasView.SelectedRows.ToArray()); + CurrentTasMovie.RemoveFrames(GetSelection().ToArray()); SetTasViewRowCount(); SetSplicer(); } @@ -521,18 +523,19 @@ private void CloneFramesXTimesMenuItem_Click(object sender, EventArgs e) private void CloneFramesXTimes(int timesToClone) { - if (!TasView.AnyRowsSelected) return; + if (!AnyRowsSelected) return; - var framesToInsert = TasView.SelectedRows; - var insertionFrame = Math.Min((TasView.SelectionEndIndex ?? 0) + 1, CurrentTasMovie.InputLogLength); + var framesToInsert = GetSelection(); + var insertionFrame = Math.Min(LastSelectedRowIndex + 1, CurrentTasMovie.InputLogLength); + // Get controller instead of string log var inputLog = framesToInsert .Select(CurrentTasMovie.GetInputLogEntry) .ToList(); CurrentTasMovie.SingleInvalidation(() => { - string batchName = $"Clone {inputLog.Count} frames starting at {TasView.FirstSelectedRowIndex}"; + string batchName = $"Clone {inputLog.Count} frames starting at {FirstSelectedRowIndex}"; if (timesToClone != 1) batchName += $" {timesToClone} times"; CurrentTasMovie.ChangeLog.BeginNewBatch(batchName); @@ -547,17 +550,17 @@ private void CloneFramesXTimes(int timesToClone) private void InsertFrameMenuItem_Click(object sender, EventArgs e) { - if (TasView.AnyRowsSelected) + if (AnyRowsSelected) { - CurrentTasMovie.InsertEmptyFrame(TasView.SelectionStartIndex ?? 0); + CurrentTasMovie.InsertEmptyFrame(FirstSelectedRowIndex); } } private void InsertNumFramesMenuItem_Click(object sender, EventArgs e) { - if (TasView.AnyRowsSelected) + if (AnyRowsSelected) { - var insertionFrame = TasView.SelectionStartIndex ?? 0; + var insertionFrame = FirstSelectedRowIndex; using var framesPrompt = new FramesPrompt(); if (framesPrompt.ShowDialogOnScreen().IsOk()) { @@ -568,16 +571,16 @@ private void InsertNumFramesMenuItem_Click(object sender, EventArgs e) private void TruncateMenuItem_Click(object sender, EventArgs e) { - if (TasView.AnyRowsSelected) + if (AnyRowsSelected) { - CurrentTasMovie.Truncate(TasView.SelectionEndIndex ?? 0); + CurrentTasMovie.Truncate(LastSelectedRowIndex); MarkerControl.MarkerInputRoll.TruncateSelection(CurrentTasMovie.Markers.Count - 1); } } private void SetMarkersMenuItem_Click(object sender, EventArgs e) { - var selectedRows = TasView.SelectedRows.ToList(); + var selectedRows = GetSelection().ToList(); if (selectedRows.Count > 50) { var result = DialogController.ShowMessageBox2("Are you sure you want to add more than 50 markers?", "Add markers", EMsgBoxIcon.Question, useOKCancel: true); @@ -595,12 +598,12 @@ private void SetMarkersMenuItem_Click(object sender, EventArgs e) private void SetMarkerWithTextMenuItem_Click(object sender, EventArgs e) { - MarkerControl.AddMarker(TasView.AnyRowsSelected ? TasView.FirstSelectedRowIndex : 0, true); + MarkerControl.AddMarker(AnyRowsSelected ? FirstSelectedRowIndex : 0, true); } private void RemoveMarkersMenuItem_Click(object sender, EventArgs e) { - CurrentTasMovie.Markers.RemoveAll(m => TasView.IsRowSelected(m.Frame)); + CurrentTasMovie.Markers.RemoveAll(m => IsRowSelected(m.Frame)); MarkerControl.UpdateMarkerCount(); } @@ -682,7 +685,7 @@ private void HeaderMenuItem_Click(object sender, EventArgs e) using MovieHeaderEditor form = new(CurrentTasMovie, Config) { Owner = this, - Location = this.ChildPointToScreen(TasView), + Location = this.ChildPointToScreen(_inputRolls[0]), }; form.ShowDialogOnScreen(); } @@ -693,7 +696,7 @@ private void CommentsMenuItem_Click(object sender, EventArgs e) { Owner = this, StartPosition = FormStartPosition.Manual, - Location = this.ChildPointToScreen(TasView), + Location = this.ChildPointToScreen(_inputRolls[0]), }; form.ShowDialogOnScreen(); } @@ -708,40 +711,57 @@ private void SubtitlesMenuItem_Click(object sender, EventArgs e) { Owner = this, StartPosition = FormStartPosition.Manual, - Location = this.ChildPointToScreen(TasView), + Location = this.ChildPointToScreen(_inputRolls[0]), }; form.ShowDialogOnScreen(); } private void SetUpToolStripColumns() { - ColumnsSubMenu.DropDownItems.Clear(); + ShowColumnsContextMenuItem.DropDownItems.Clear(); - var columns = TasView.AllColumns - .Where(static c => !string.IsNullOrWhiteSpace(c.Text) && c.Name is not "FrameColumn") + var columns = _inputRolls[0].AllColumns + .Where(static c => !string.IsNullOrWhiteSpace(c.Text) && c.Name is not FrameColumnName) .ToList(); int workingHeight = Screen.FromControl(this).WorkingArea.Height; - int rowHeight = ColumnsSubMenu.Height + 4; + int rowHeight = ShowColumnsContextMenuItem.Height + 4; int maxRows = workingHeight / rowHeight; int keyCount = columns.Count(c => c.Name.StartsWithOrdinal("Key ")); int keysMenusCount = (int)Math.Ceiling((double)keyCount / maxRows); + // Groupings for keys (we just have a lot of them) and for players var keysMenus = new ToolStripMenuItem[keysMenusCount]; - for (int i = 0; i < keysMenus.Length; i++) { keysMenus[i] = new ToolStripMenuItem(); + ShowColumnsContextMenuItem.DropDownItems.Add(keysMenus[i]); } var playerMenus = new ToolStripMenuItem[Emulator.ControllerDefinition.ControlsOrdered.Count]; - playerMenus[0] = ColumnsSubMenu; - + playerMenus[0] = ShowColumnsContextMenuItem; for (int i = 1; i < playerMenus.Length; i++) { playerMenus[i] = new ToolStripMenuItem($"Player {i}"); } + bool programmaticallyHidingColumns = false; + void AfterCheckChange(ToolStripMenuItem item) + { + if (!programmaticallyHidingColumns) + { + _activeInputRoll.AllColumns.ColumnsChanged(); + CurrentTasMovie.FlagChanges(); + _activeInputRoll.Refresh(); + + UpdateInputRollDefinition(_activeInputRoll); + + if (item.OwnerItem != ShowColumnsContextMenuItem) ShowColumnsContextMenuItem.ShowDropDown(); + ((ToolStripMenuItem)item.OwnerItem).ShowDropDown(); + } + } + + // Items for individual columns foreach (var column in columns) { var menuItem = new ToolStripMenuItem @@ -755,12 +775,8 @@ private void SetUpToolStripColumns() menuItem.CheckedChanged += (o, ev) => { ToolStripMenuItem sender = (ToolStripMenuItem)o; - TasView.AllColumns.Find(c => c.Name == (string)sender.Tag).Visible = sender.Checked; - TasView.AllColumns.ColumnsChanged(); - CurrentTasMovie.FlagChanges(); - TasView.Refresh(); - ColumnsSubMenu.ShowDropDown(); - ((ToolStripMenuItem)sender.OwnerItem).ShowDropDown(); + _activeInputRoll.AllColumns.Find(c => c.Name == (string)sender.Tag).Visible = sender.Checked; + AfterCheckChange(sender); }; if (column.Name.StartsWithOrdinal("Key ")) @@ -786,44 +802,37 @@ private void SetUpToolStripColumns() playerMenus[player].DropDownItems.Add(menuItem); } } - + // update text on key group dropdowns foreach (var menu in keysMenus) { string text = $"Keys ({menu.DropDownItems[0].Tag} - {menu.DropDownItems[menu.DropDownItems.Count - 1].Tag})"; menu.Text = text.Replace("Key ", ""); - ColumnsSubMenu.DropDownItems.Add(menu); } - - for (int i = 1; i < playerMenus.Length; i++) + // add player menus only if they actually contain items + foreach (ToolStripMenuItem menu in playerMenus.Skip(1).Where(static m => m.HasDropDown)) { - if (playerMenus[i].HasDropDownItems) - { - ColumnsSubMenu.DropDownItems.Add(playerMenus[i]); - } + ShowColumnsContextMenuItem.DropDownItems.Add(menu); } - ColumnsSubMenu.DropDownItems.Add(new ToolStripSeparator()); + ShowColumnsContextMenuItem.DropDownItems.Add(new ToolStripSeparator()); + // button for the group of all keys if (keysMenus.Length > 0) { ToolStripMenuItem item = new("Show Keys") { CheckOnClick = true }; - void UpdateAggregateCheckState() - => item.CheckState = keysMenus - .SelectMany(static submenu => submenu.DropDownItems.OfType()) - .Select(static mi => mi.Checked).Unanimity().ToCheckState(); - UpdateAggregateCheckState(); - var programmaticallyHidingColumns = false; - EventHandler columnHandler = (_, _) => + + // Handle updating the check state when individual keys are toggled + ShowColumnsContextMenuItem.DropDownOpening += (_, _) => { if (programmaticallyHidingColumns) return; programmaticallyHidingColumns = true; - UpdateAggregateCheckState(); + item.CheckState = keysMenus + .SelectMany(static submenu => submenu.DropDownItems.OfType()) + .Select(static mi => mi.Checked).Unanimity().ToCheckState(); programmaticallyHidingColumns = false; }; - foreach (var submenu in keysMenus) foreach (ToolStripMenuItem button in submenu.DropDownItems) - { - button.CheckedChanged += columnHandler; - } + + // Handle checking the button for all keys item.CheckedChanged += (o, ev) => { if (programmaticallyHidingColumns) return; @@ -834,37 +843,41 @@ void UpdateAggregateCheckState() { menuItem.Checked = item.Checked; } - - CurrentTasMovie.FlagChanges(); - TasView.AllColumns.ColumnsChanged(); - TasView.Refresh(); } programmaticallyHidingColumns = false; + + AfterCheckChange(item); }; - ColumnsSubMenu.DropDownItems.Add(item); + ShowColumnsContextMenuItem.DropDownItems.Add(item); } + // buttons for the groups of each players' columns + void UpdateIndividualItems(ToolStripMenuItem menu) + { + foreach (ToolStripMenuItem item in menu.DropDownItems.OfType()) + { + string/*?*/ tag = item.Tag as string; + if (!string.IsNullOrEmpty(tag)) + { + item.Checked = _activeInputRoll.AllColumns.Find(c => c.Name == tag).Visible; + } + } + } for (int i = 1; i < playerMenus.Length; i++) { ToolStripMenuItem dummyObject = playerMenus[i]; if (!dummyObject.HasDropDownItems) continue; ToolStripMenuItem item = new($"Show Player {i}") { CheckOnClick = true }; - void UpdateAggregateCheckState() - => item.CheckState = dummyObject.DropDownItems.OfType().Skip(1) - .Select(static mi => mi.Checked).Unanimity().ToCheckState(); - UpdateAggregateCheckState(); - var programmaticallyHidingColumns = false; - EventHandler columnHandler = (_, _) => + dummyObject.DropDownOpening += (_, _) => { - if (programmaticallyHidingColumns) return; programmaticallyHidingColumns = true; - UpdateAggregateCheckState(); + + UpdateIndividualItems(dummyObject); + item.CheckState = dummyObject.DropDownItems.OfType().Skip(1) + .Select(static mi => mi.Checked).Unanimity().ToCheckState(); + programmaticallyHidingColumns = false; }; - foreach (ToolStripMenuItem button in dummyObject.DropDownItems) - { - button.CheckedChanged += columnHandler; - } item.CheckedChanged += (o, ev) => { if (programmaticallyHidingColumns) return; @@ -876,16 +889,20 @@ void UpdateAggregateCheckState() } programmaticallyHidingColumns = false; - CurrentTasMovie.FlagChanges(); - TasView.AllColumns.ColumnsChanged(); - TasView.Refresh(); + AfterCheckChange(item); }; dummyObject.DropDownItems.Insert(0, item); dummyObject.DropDownItems.Insert(1, new ToolStripSeparator()); } + playerMenus[0].DropDownOpening += (_,_) => // "player menu 0" is for buttons not associated with a player, and doesn't have a button for the entire group + { + programmaticallyHidingColumns = true; + UpdateIndividualItems(playerMenus[0]); + programmaticallyHidingColumns = false; + }; - ColumnsSubMenu.DropDownItems.Add(new ToolStripMenuItem + ShowColumnsContextMenuItem.DropDownItems.Add(new ToolStripMenuItem { Enabled = false, Text = "Change Peripherals...", @@ -900,13 +917,81 @@ private void RestoreDefaults() { SetUpColumns(); SetUpToolStripColumns(); - TasView.Refresh(); + _inputRolls.ForEach(static r => r.Refresh()); CurrentTasMovie.FlagChanges(); MainVertialSplit.SplitterDistance = _defaultMainSplitDistance; BranchesMarkersSplit.SplitterDistance = _defaultBranchMarkerSplitDistance; } + private void ColumnRightClickMenu_Opened(object sender, EventArgs e) + { + if (_clickedColumn == null) return; + + bool cursorOrFrame = _clickedColumn.Name is (CursorColumnName or FrameColumnName); + ControllerDefinition cd = MovieSession.MovieController.Definition; + AutoHoldContextMenuItem.Visible = !cursorOrFrame + && (cd.BoolButtons.Contains(_clickedColumn.Name) || cd.Axes.ContainsKey(_clickedColumn.Name)); + HideColumnContextMenuItem.Visible = !cursorOrFrame; + + DeleteInputRollContextMenuItem.Visible = _inputRolls.Count > 1; + } + + private void AutoHoldContextMenuItem_Click(object sender, EventArgs e) + { + if (_clickedColumn == null) return; + ToggleAutoFire(_clickedColumn); + _activeInputRoll.Refresh(); + } + + private void HideColumnContextMenuItem_Click(object sender, EventArgs e) + { + if (_clickedColumn == null) return; + _clickedColumn.Visible = false; + _activeInputRoll.AllColumns.ColumnsChanged(); + CurrentTasMovie.FlagChanges(); + _activeInputRoll.Refresh(); + } + + private void NewInputRollContextMenuItem_Click(object sender, EventArgs e) + { + HashSet hiddenCols = _inputRolls + .SelectMany(static r => r.AllColumns.Where(static c => !c.Visible)) + .Select(static c => c.Name) + .Distinct() + .ToHashSet(); + + int index = _inputRolls.IndexOf(_activeInputRoll); + InputRoll roll = MakeInputRoll(index); + roll.AllColumns.AddRange(_activeInputRoll.AllColumns.Select(static c => c.Clone())); + + // If any columns aren't visible, default to showing those instead of showing all. + if (hiddenCols.Count != 0) + { + foreach (RollColumn col in roll.AllColumns) + { + col.Visible = hiddenCols.Contains(col.Name) || col.Name is (CursorColumnName or FrameColumnName); + } + } + roll.AllColumns.ColumnsChanged(); + UpdateInputRollDefinition(roll); + + RefreshDialog(); + } + + private void DeleteInputRollContextMenuItem_Click(object sender, EventArgs e) + { + if (_inputRolls.Count == 1) return; + + _tasViewPanel.Controls.Remove(_activeInputRoll); + _rollDefinitions.Remove(_activeInputRoll); + int index = _inputRolls.IndexOf(_activeInputRoll); + _inputRolls.RemoveAt(index); + _activeInputRoll = _inputRolls[0]; + + RepositionRolls(); + } + private void RightClickMenu_Opened(object sender, EventArgs e) { SetMarkersContextMenuItem.Enabled = @@ -920,16 +1005,16 @@ private void RightClickMenu_Opened(object sender, EventArgs e) InsertFrameContextMenuItem.Enabled = InsertNumFramesContextMenuItem.Enabled = TruncateContextMenuItem.Enabled = - TasView.AnyRowsSelected; + AnyRowsSelected; pasteToolStripMenuItem.Enabled = pasteInsertToolStripMenuItem.Enabled = - TasView.AnyRowsSelected && Clipboard.ContainsText(); + AnyRowsSelected && Clipboard.ContainsText(); - var selectionIsSingleRow = TasView.SelectedRows.CountIsExactly(1); + var selectionIsSingleRow = GetSelection().CountIsExactly(1); StartNewProjectFromNowMenuItem.Visible = selectionIsSingleRow - && TasView.IsRowSelected(Emulator.Frame) + && IsRowSelected(Emulator.Frame) && !CurrentTasMovie.StartsFromSaveRam; StartANewProjectFromSaveRamMenuItem.Visible = @@ -938,9 +1023,9 @@ private void RightClickMenu_Opened(object sender, EventArgs e) && !CurrentTasMovie.StartsFromSavestate; StartFromNowSeparator.Visible = StartNewProjectFromNowMenuItem.Visible || StartANewProjectFromSaveRamMenuItem.Visible; - RemoveMarkersContextMenuItem.Enabled = CurrentTasMovie.Markers.Any(m => TasView.IsRowSelected(m.Frame)); // Disable the option to remove markers if no markers are selected (FCEUX does this). + RemoveMarkersContextMenuItem.Enabled = CurrentTasMovie.Markers.Any(m => IsRowSelected(m.Frame)); // Disable the option to remove markers if no markers are selected (FCEUX does this). CancelSeekContextMenuItem.Enabled = _seekingTo != -1; - BranchContextMenuItem.Visible = TasView.CurrentCell?.RowIndex == Emulator.Frame; + BranchContextMenuItem.Visible = CurrentCell?.RowIndex == Emulator.Frame; } private void CancelSeekContextMenuItem_Click(object sender, EventArgs e) @@ -968,7 +1053,7 @@ private void TAStudioSettingsToolStripMenuItem_Click(object sender, EventArgs e) TAStudioSettingsForm settingsForm = new(new() { GeneralClientSettings = Settings, - MovieSettings = GetMovieSettings(), + MovieSettings = _movieSettings, CurrentStateManagerSettings = CurrentTasMovie.TasStateManager.Settings, DefaultStateManagerSettings = Config.Movies.DefaultTasStateManagerSettings, }, @@ -976,14 +1061,17 @@ private void TAStudioSettingsToolStripMenuItem_Click(object sender, EventArgs e) (s) => { // settings objects are mutated by the settings form, but some still need to be handled - TasView.LoadSettings(s.MovieSettings.InputRollSettings); - TasView.Font = Settings.TasViewFont; - TasView.ScrollSpeed = Settings.ScrollSpeed; - TasView.AlwaysScroll = Settings.FollowCursorAlwaysScroll; - TasView.ScrollMethod = Settings.FollowCursorScrollMethod; - - AxisPatterns = s.MovieSettings.AxisPatterns; - BoolPatterns = s.MovieSettings.BoolPatterns; + for (int i = 0; i < s.MovieSettings.Columns.Length; i++) + { + _inputRolls[i].AlwaysScroll = Settings.FollowCursorAlwaysScroll; + _inputRolls[i].Font = Settings.TasViewFont; + _inputRolls[i].HorizontalOrientation = s.MovieSettings.HorizontalOrientation; + _inputRolls[i].LagFramesToHide = s.MovieSettings.LagFramesToHide; + _inputRolls[i].HideWasLagFrames = s.MovieSettings.HideWasLagFrames; + _inputRolls[i].ScrollMethod = Settings.FollowCursorScrollMethod; + _inputRolls[i].ScrollSpeed = Settings.ScrollSpeed; + } + UpdateAutoFire(); if (CurrentTasMovie.TasStateManager.Settings != s.CurrentStateManagerSettings) @@ -995,6 +1083,8 @@ private void TAStudioSettingsToolStripMenuItem_Click(object sender, EventArgs e) UpdateChangeLogMaxSteps(Settings.MaxUndoSteps); CurrentTasMovie.BindMarkersToInput = Settings.BindMarkersToInput; + + UpdateActiveMovieInputs(); } ); settingsForm.ShowDialog(this); diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs index 0edbf2f02d9..baf498d2bdb 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs @@ -96,7 +96,15 @@ public void SetVisibleFrame(int? frame = null) return; } - TasView.ScrollToIndex(frame ?? Emulator.Frame); + int scrollTo = frame ?? Emulator.Frame; + if (Settings.ScrollSync) + { + _inputRolls.ForEach(r => r.ScrollToIndex(scrollTo)); + } + else + { + _activeInputRoll.ScrollToIndex(scrollTo); + } } private void MaybeFollowCursor() diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs index 00f70fa06ac..b3f72a49a57 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs @@ -89,6 +89,7 @@ public TAStudioSettings() AutosaveAsBk2 = false; AutosaveAsBackupFile = false; BackupPerFileSave = false; + EditInvisibleColumns = true; OldControlSchemeForBranches = false; LoadBranchOnDoubleClick = true; CopyIncludesFrameNo = false; @@ -120,6 +121,7 @@ public TAStudioSettings() public bool DenoteStatesWithBGColor { get; set; } public bool DenoteMarkersWithIcons { get; set; } public bool DenoteMarkersWithBGColor { get; set; } + public bool EditInvisibleColumns { get; set; } public int MainVerticalSplitDistance { get; set; } public int BranchMarkerSplitDistance { get; set; } public bool BindMarkersToInput { get; set; } @@ -129,6 +131,7 @@ public TAStudioSettings() public int MaxUndoSteps { get; set; } = 1000; public int RewindStep { get; set; } = 1; public int RewindStepFast { get; set; } = 4; + public bool ScrollSync { get; set; } = true; public PatternPaintModeEnum PatternPaintMode { get; set; } = TAStudioSettings.PatternPaintModeEnum.Never; public PatternSelectionEnum PatternSelection { get; set; } = TAStudioSettings.PatternSelectionEnum.Hold; public Font TasViewFont { get; set; } = new Font("Arial", 8.25F, FontStyle.Bold, GraphicsUnit.Point, 0); @@ -136,7 +139,10 @@ public TAStudioSettings() public class MovieClientSettings { - public InputRoll.InputRollSettings InputRollSettings { get; set; } + public bool HorizontalOrientation { get; set; } = false; + public bool HideWasLagFrames { get; set; } = false; + public int LagFramesToHide { get; set; } = 0; + public RollColumns[] Columns { get; set; } = [ ]; public AutoPatternBool[] BoolPatterns { get; set; } public AutoPatternAxis[] AxisPatterns { get; set; } @@ -153,19 +159,20 @@ public class AllSettings public IStateManagerSettings DefaultStateManagerSettings; } - private MovieClientSettings GetMovieSettings() - { - return new MovieClientSettings() - { - InputRollSettings = TasView.GetUserSettings(), - AxisPatterns = AxisPatterns, - BoolPatterns = BoolPatterns, - }; - } + private MovieClientSettings _movieSettings = new(); public TAStudio() { InitializeComponent(); + _tasViewPanel = MainVertialSplit.Panel1; + // The built-in scroll feature of .NET's scrollable controls is non-functional. So, custom scroll behavior! + _tasViewHBar.Dock = DockStyle.Bottom; + _tasViewVBar.Dock = DockStyle.Right; + _tasViewHBar.Scroll += (_, _) => RepositionRolls(); + _tasViewVBar.Scroll += (_, _) => RepositionRolls(); + _tasViewPanel.Controls.Add(_tasViewHBar); + _tasViewPanel.Controls.Add(_tasViewVBar); + ToolStripMenuItemEx goToFrameMenuItem = new() { ShortcutKeys = Keys.Control | Keys.G, @@ -201,53 +208,31 @@ public TAStudio() TasPlaybackBox.Tastudio = this; MarkerControl.Tastudio = this; BookMarkControl.Tastudio = this; - TasView.QueryItemText += TasView_QueryItemText; - TasView.QueryItemBkColor += TasView_QueryItemBkColor; - TasView.QueryRowBkColor += TasView_QueryRowBkColor; - TasView.QueryItemIcon += TasView_QueryItemIcon; - TasView.QueryFrameLag += TasView_QueryFrameLag; - TasView.QueryShouldSelectCell += TasView_QueryShouldSelect; - TasView.PointedCellChanged += TasView_PointedCellChanged; - - TasView.MouseLeave += TAStudio_MouseLeave; - TasView.CellHovered += (_, e) => - { - if (e.NewCell.RowIndex is null) - { - toolTip1.Show(e.NewCell.Column!.Name, TasView, PointToClient(Cursor.Position)); - } - }; } private void Tastudio_Load(object sender, EventArgs e) { + MainVertialSplit.SetDistanceOrDefault( + Settings.MainVerticalSplitDistance, + _defaultMainSplitDistance); + _activeInputRoll = MakeInputRoll(); // first because stuff in Engage assumes we have at least one + if (!Engage()) { Close(); return; } - RightClickMenu.Items.AddRange(TasView.GenerateContextMenuItems().ToArray()); - - TasView.ScrollSpeed = Settings.ScrollSpeed; - TasView.AlwaysScroll = Settings.FollowCursorAlwaysScroll; - TasView.ScrollMethod = Settings.FollowCursorScrollMethod; - _autosaveTimer = new Timer(components); _autosaveTimer.Tick += AutosaveTimerEventProcessor; ScheduleAutoSave(Settings.AutosaveInterval); - MainVertialSplit.SetDistanceOrDefault( - Settings.MainVerticalSplitDistance, - _defaultMainSplitDistance); - BranchesMarkersSplit.SetDistanceOrDefault( Settings.BranchMarkerSplitDistance, _defaultBranchMarkerSplitDistance); HandleHotkeyUpdate(); - TasView.Font = Settings.TasViewFont; RefreshDialog(); _initialized = true; } @@ -445,20 +430,33 @@ private void AutosaveTimerEventProcessor(object sender, EventArgs e) private void SetUpColumns() { - TasView.AllColumns.Clear(); - TasView.AllColumns.Add(new(name: CursorColumnName, widthUnscaled: 18, text: string.Empty)); - TasView.AllColumns.Add(new(name: FrameColumnName, widthUnscaled: 60, text: "Frame#") + _movieSettings.Columns = new RollColumns[_inputRolls.Count]; + for (int i = 0; i < _inputRolls.Count; i++) + { + InputRoll roll = _inputRolls[i]; + MakeDefaultColumns(roll); + _movieSettings.Columns[i] = roll.AllColumns; + UpdateInputRollDefinition(roll); + } + } + + private void MakeDefaultColumns(InputRoll roll) + { + roll.AllColumns.Clear(); + roll.AllColumns.Add(new(name: CursorColumnName, widthUnscaled: 18, text: string.Empty)); + roll.AllColumns.Add(new(name: FrameColumnName, widthUnscaled: 60, text: "Frame#") { Rotatable = true, }); + List columns = new(); // add to list first then AddRange to avoid 100 refreshes foreach ((string name, string mnemonic0, int maxLength) in MnemonicMap()) { var mnemonic = Emulator.SystemId is VSystemID.Raw.N64 && N64CButtonSuffixes.Any(name.EndsWithOrdinal) ? $"c{mnemonic0.ToUpperInvariant()}" // prepend 'c' to differentiate from L/R buttons -- this only affects the column headers : mnemonic0; - TasView.AllColumns.Add(new( + columns.Add(new( name: name, verticalWidth: (Math.Max(maxLength, mnemonic.Length) * 6) + 14, horizontalHeight: (maxLength * 6) + 14, @@ -467,8 +465,9 @@ private void SetUpColumns() Rotatable = ControllerType.Axes.ContainsKey(name), }); } + roll.AllColumns.AddRange(columns); - var columnsToHide = TasView.AllColumns + var columnsToHide = roll.AllColumns .Where(c => // todo: make a proper user editable list? c.Name == "Power" @@ -491,7 +490,7 @@ private void SetUpColumns() if (Emulator.SystemId is VSystemID.Raw.N64) { - var fakeAnalogControls = TasView.AllColumns + var fakeAnalogControls = roll.AllColumns .Where(c => c.Name.EndsWithOrdinal("A Up") || c.Name.EndsWithOrdinal("A Down") @@ -502,7 +501,7 @@ private void SetUpColumns() } else if (Emulator.SystemId is VSystemID.Raw.Doom) { - var columns = TasView.AllColumns + var doomColsToHide = roll.AllColumns .Where(c => c.Name.Contains("Forward") || c.Name.Contains("Backward") @@ -517,7 +516,7 @@ private void SetUpColumns() || c.Name.EndsWithOrdinal("Strafe") || c.Name.EndsWithOrdinal("Run")); - columnsToHide = columnsToHide.Concat(columns); + columnsToHide = columnsToHide.Concat(doomColsToHide); } foreach (var column in columnsToHide) @@ -525,7 +524,7 @@ private void SetUpColumns() column.Visible = false; } - foreach (var column in TasView.VisibleColumns) + foreach (var column in roll.VisibleColumns) { if (InputManager.StickyHoldController.IsSticky(column.Name) || InputManager.StickyAutofireController.IsSticky(column.Name)) { @@ -533,32 +532,45 @@ private void SetUpColumns() } } - TasView.AllColumns.ColumnsChanged(); + roll.AllColumns.ColumnsChanged(); } private void SetupCustomPatterns() { // custom autofire patterns to allow configuring a unique pattern for each button or axis - BoolPatterns = new AutoPatternBool[ControllerType.BoolButtons.Count]; - AxisPatterns = new AutoPatternAxis[ControllerType.Axes.Count]; + _movieSettings.BoolPatterns = new AutoPatternBool[ControllerType.BoolButtons.Count]; + _movieSettings.AxisPatterns = new AutoPatternAxis[ControllerType.Axes.Count]; - for (int i = 0; i < BoolPatterns.Length; i++) + for (int i = 0; i < _movieSettings.BoolPatterns.Length; i++) { // standard 1 on 1 off autofire pattern - BoolPatterns[i] = new AutoPatternBool(1, 1); + _movieSettings.BoolPatterns[i] = new AutoPatternBool(1, 1); } - for (int i = 0; i < AxisPatterns.Length; i++) + for (int i = 0; i < _movieSettings.AxisPatterns.Length; i++) { // autohold pattern with the maximum axis range as hold value (bit arbitrary) var axisSpec = ControllerType.Axes[ControllerType.Axes[i]]; - AxisPatterns[i] = new AutoPatternAxis([ axisSpec.Range.EndInclusive ]); + _movieSettings.AxisPatterns[i] = new AutoPatternAxis([ axisSpec.Range.EndInclusive ]); } } /// for Lua - public void AddColumn(string name, string text, int widthUnscaled) - => TasView.AllColumns.Add(new(name: name, widthUnscaled: widthUnscaled, text: text)); + public void AddColumn(string name, string text, int widthUnscaled, int rollIndex) + { + if (_inputRolls[0].AllColumns.Exists(c => c.Name == name)) return; + + for (int i = 0; i < _inputRolls.Count; i++) + { + RollColumn col = new(name: name, widthUnscaled: widthUnscaled, text: text); + col.Visible = i == rollIndex; + _inputRolls[rollIndex].AllColumns.Add(col); + _inputRolls[rollIndex].AllColumns.ColumnsChanged(); + _inputRolls[rollIndex].Refresh(); + } + + SetUpToolStripColumns(); + } public void LoadBranchByIndex(int index) => BookMarkControl.LoadBranchExternal(index); @@ -597,8 +609,8 @@ public void ReselectClipboardExternal() public void SelectCurrentFrame() { - TasView.DeselectAll(); - TasView.SelectRow(Emulator.Frame, true); + _activeInputRoll.DeselectAll(); + _activeInputRoll.SelectRow(Emulator.Frame, true); RefreshDialog(); } @@ -618,23 +630,6 @@ public IMovieController GetBranchInput(string branchId, int frame) return controller; } - private int? FirstNonEmptySelectedFrame - { - get - { - var empty = Bk2LogEntryGenerator.EmptyEntry(MovieSession.MovieController); - foreach (var row in TasView.SelectedRows) - { - if (CurrentTasMovie[row].LogEntry != empty) - { - return row; - } - } - - return null; - } - } - private bool LoadMovie(ITasMovie tasMovie, bool startsFromSavestate = false, int gotoFrame = 0) { _engaged = false; @@ -661,7 +656,7 @@ private bool LoadMovie(ITasMovie tasMovie, bool startsFromSavestate = false, int } // clear all selections - TasView.DeselectAll(); + _activeInputRoll.DeselectAll(); BookMarkControl.Restart(); MarkerControl.Restart(); @@ -687,7 +682,7 @@ private bool StartNewTasMovie() if (success) { // clear all selections - TasView.DeselectAll(); + _activeInputRoll.DeselectAll(); BookMarkControl.Restart(); MarkerControl.Restart(); RefreshDialog(); @@ -701,7 +696,7 @@ private bool StartNewMovieWrapper(ITasMovie movie, bool isNew) _initializing = true; movie.ClientSettingsForSave = () => - ConfigService.SaveWithType(GetMovieSettings()); + ConfigService.SaveWithType(_movieSettings); movie.BindMarkersToInput = Settings.BindMarkersToInput; movie.GreenzoneInvalidated = (f) => _ = FrameEdited(f); movie.ChangeLog.MaxSteps = Settings.MaxUndoSteps; @@ -740,14 +735,25 @@ private bool StartNewMovieWrapper(ITasMovie movie, bool isNew) if (settings is InputRoll.InputRollSettings inputRollSettings) { - // Old movie. - TasView.LoadSettings(inputRollSettings); + // Old movie, 2.11 or earlier + RemoveAllRolls(); + InputRoll roll = _activeInputRoll = MakeInputRoll(); + roll.LoadColumns(inputRollSettings.Columns); + roll.HorizontalOrientation = _movieSettings.HorizontalOrientation = inputRollSettings.HorizontalOrientation; + roll.LagFramesToHide = _movieSettings.LagFramesToHide = inputRollSettings.LagFramesToHide; + roll.HideWasLagFrames = _movieSettings.HideWasLagFrames = inputRollSettings.HideWasLagFrames; + _movieSettings.Columns = [ roll.AllColumns ]; + UpdateInputRollDefinition(roll); } else if (settings is MovieClientSettings clientSettings) { - TasView.LoadSettings(clientSettings.InputRollSettings); - AxisPatterns = clientSettings.AxisPatterns; - BoolPatterns = clientSettings.BoolPatterns; + _movieSettings = clientSettings; + if (_movieSettings.Columns.Length == 0) + { + // This will happen if the movie was a build between 2.11 and 2.11.1 + _movieSettings.Columns = _inputRolls.Select(static t => t.AllColumns).ToArray(); + } + MakeInputRollsFromSettings(); } else { @@ -757,6 +763,8 @@ private bool StartNewMovieWrapper(ITasMovie movie, bool isNew) } if (!hasClientSettings) { + RemoveAllRolls(); + _activeInputRoll = MakeInputRoll(); SetUpColumns(); SetupCustomPatterns(); } @@ -803,13 +811,13 @@ private bool LoadFileWithFallback(string path) private void DummyLoadMacro(string path) { - if (!TasView.AnyRowsSelected) + if (!AnyRowsSelected) { return; } var loadZone = MovieZone.Load(path, MainForm, CurrentTasMovie); - loadZone?.PlaceZone(CurrentTasMovie, TasView.FirstSelectedRowIndex); + loadZone?.PlaceZone(CurrentTasMovie, FirstSelectedRowIndex); } private void TastudioToggleReadOnly() @@ -981,7 +989,7 @@ protected override string WindowTitle protected override string WindowTitleStatic => "TAStudio"; - public IEnumerable GetSelection() => TasView.SelectedRows; + public IEnumerable GetSelection() => _activeInputRoll.SelectedRows; public void RefreshDialog(bool refreshTasView = true, bool refreshBranches = true) { @@ -1010,7 +1018,7 @@ public void RefreshDialog(bool refreshTasView = true, bool refreshBranches = tru private void SetTasViewRowCount() { - TasView.RowCount = CurrentTasMovie.InputLogLength + 1; + _inputRolls.ForEach(r => r.RowCount = CurrentTasMovie.InputLogLength + 1); _lastRefresh = Emulator.Frame; } @@ -1067,7 +1075,7 @@ public override void OnPauseToggle(bool newPauseState) private void SetSplicer() { // TODO: columns selected? - var selectedRowCount = TasView.SelectedRows.Count(); + var selectedRowCount = GetSelection().Count(); var temp = $"Selected: {selectedRowCount} {(selectedRowCount == 1 ? "frame" : "frames")}, States: {CurrentTasMovie.TasStateManager.Count}"; var clipboardCount = _tasClipboard.Count; if (clipboardCount is not 0) temp += $", Clipboard: {clipboardCount} {(clipboardCount is 1 ? "frame" : "frames")}"; @@ -1117,22 +1125,28 @@ private void TAStudio_DragDrop(object sender, DragEventArgs e) private void TAStudio_MouseLeave(object sender, EventArgs e) { - toolTip1.SetToolTip(TasView, null); + toolTip1.SetToolTip(_inputRolls[0], null); } private void TAStudio_Deactivate(object sender, EventArgs e) { if (_leftButtonHeld) { - TasView_MouseUp(this, new(MouseButtons.Left, 0, 0, 0, 0)); + TasView_MouseUp(_activeInputRoll, new(MouseButtons.Left, 0, 0, 0, 0)); } if (_rightClickFrame != -1) { _suppressContextMenu = true; - TasView_MouseUp(this, new(MouseButtons.Right, 0, 0, 0, 0)); + TasView_MouseUp(_activeInputRoll, new(MouseButtons.Right, 0, 0, 0, 0)); } } + private void TAStudio_Resize(object sender, EventArgs e) + { + if (_inputRolls.Count == 0) return; + RepositionRolls(); + } + protected override bool ProcessCmdKey(ref Message msg, Keys keyData) { if (keyData is Keys.Tab or (Keys.Shift | Keys.Tab) or Keys.Space) return true; @@ -1195,6 +1209,7 @@ private bool AutoAdjustInput() private void MainVerticalSplit_SplitterMoved(object sender, SplitterEventArgs e) { Settings.MainVerticalSplitDistance = MainVertialSplit.SplitterDistance; + if (_inputRolls.Count != 0) RepositionRolls(); // this event gets called while loading } private void BranchesMarkersSplit_SplitterMoved(object sender, SplitterEventArgs e) @@ -1217,20 +1232,6 @@ protected void DragEnterWrapper(object sender, DragEventArgs e) GenericDragEnter(sender, e); } - private void SetFontMenuItem_Click(object sender, EventArgs e) - { - using var fontDialog = new FontDialog - { - ShowColor = false, - Font = TasView.Font, - }; - if (fontDialog.ShowDialog() != DialogResult.Cancel) - { - TasView.Font = Settings.TasViewFont = fontDialog.Font; - TasView.Refresh(); - } - } - private IMovieController ControllerFromMnemonicStr(string inputLogEntry) { try @@ -1271,12 +1272,13 @@ private IMovieController ControllerFromMnemonicStr(string inputLogEntry) private void HandleRotationChanged(object sender, EventArgs e) { + InputRoll roll = (InputRoll)sender; CurrentTasMovie.FlagChanges(); - if (TasView.HorizontalOrientation) + if (roll.HorizontalOrientation) { BranchesMarkersSplit.Orientation = Orientation.Vertical; BranchesMarkersSplit.SplitterDistance = 200; - foreach (var rollColumn in TasView.AllColumns) + foreach (var rollColumn in roll.AllColumns) { rollColumn.Width = rollColumn.HorizontalHeight; } @@ -1285,13 +1287,13 @@ private void HandleRotationChanged(object sender, EventArgs e) { BranchesMarkersSplit.Orientation = Orientation.Horizontal; BranchesMarkersSplit.SplitterDistance = _defaultBranchMarkerSplitDistance; - foreach (var rollColumn in TasView.AllColumns) + foreach (var rollColumn in roll.AllColumns) { rollColumn.Width = rollColumn.VerticalWidth; } } - TasView.AllColumns.ColumnsChanged(); + roll.AllColumns.ColumnsChanged(); } private bool _suspendEditLogic; diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.resx b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.resx index 2de40e63762..72a5c51cd65 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.resx +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.resx @@ -129,4 +129,7 @@ 388, 17 + + 486, 17 + \ No newline at end of file diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudioSettingsForm.Designer.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudioSettingsForm.Designer.cs index 2fc26286ffb..0a4ce55a9b5 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudioSettingsForm.Designer.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudioSettingsForm.Designer.cs @@ -86,23 +86,26 @@ private void InitializeComponent() this.label8 = new System.Windows.Forms.Label(); this.RewindNum = new System.Windows.Forms.NumericUpDown(); this.label7 = new System.Windows.Forms.Label(); + this.BindMarkersCheckbox = new System.Windows.Forms.CheckBox(); + this.IncludeFrameNumberCheckbox = new System.Windows.Forms.CheckBox(); + this.UndoCountNum = new System.Windows.Forms.NumericUpDown(); + this.label1 = new System.Windows.Forms.Label(); + this.AutopauseCheckbox = new System.Windows.Forms.CheckBox(); + this.SettingsCancelButton = new System.Windows.Forms.Button(); + this.ApplyButton = new System.Windows.Forms.Button(); + this.tabPage6 = new System.Windows.Forms.TabPage(); this.groupBox4 = new System.Windows.Forms.GroupBox(); this.ScrollToCenterRadio = new System.Windows.Forms.RadioButton(); this.ScrollToBottomRadio = new System.Windows.Forms.RadioButton(); this.ScrollToTopRadio = new System.Windows.Forms.RadioButton(); this.ScrollToViewRadio = new System.Windows.Forms.RadioButton(); this.AlwaysScrollCheckbox = new System.Windows.Forms.CheckBox(); - this.BindMarkersCheckbox = new System.Windows.Forms.CheckBox(); - this.IncludeFrameNumberCheckbox = new System.Windows.Forms.CheckBox(); - this.UndoCountNum = new System.Windows.Forms.NumericUpDown(); - this.label1 = new System.Windows.Forms.Label(); this.label3 = new System.Windows.Forms.Label(); this.ScrollSpeedNum = new System.Windows.Forms.NumericUpDown(); this.label2 = new System.Windows.Forms.Label(); - this.AutopauseCheckbox = new System.Windows.Forms.CheckBox(); - this.SettingsCancelButton = new System.Windows.Forms.Button(); - this.ApplyButton = new System.Windows.Forms.Button(); this.toolTip1 = new System.Windows.Forms.ToolTip(this.components); + this.EditInvisibleColumnsCheckbox = new System.Windows.Forms.CheckBox(); + this.ScrollSyncCheckbox = new System.Windows.Forms.CheckBox(); this.tabControl1.SuspendLayout(); this.tabPage2.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.HideLagNum)).BeginInit(); @@ -118,8 +121,9 @@ private void InitializeComponent() this.tabPage3.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.FastRewindNum)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.RewindNum)).BeginInit(); - this.groupBox4.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.UndoCountNum)).BeginInit(); + this.tabPage6.SuspendLayout(); + this.groupBox4.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.ScrollSpeedNum)).BeginInit(); this.SuspendLayout(); // @@ -132,11 +136,12 @@ private void InitializeComponent() this.tabControl1.Controls.Add(this.tabPage5); this.tabControl1.Controls.Add(this.tabPage4); this.tabControl1.Controls.Add(this.tabPage1); + this.tabControl1.Controls.Add(this.tabPage6); this.tabControl1.Controls.Add(this.tabPage3); this.tabControl1.Location = new System.Drawing.Point(12, 12); this.tabControl1.Name = "tabControl1"; this.tabControl1.SelectedIndex = 0; - this.tabControl1.Size = new System.Drawing.Size(369, 433); + this.tabControl1.Size = new System.Drawing.Size(374, 433); this.tabControl1.TabIndex = 0; // // tabPage2 @@ -155,7 +160,7 @@ private void InitializeComponent() this.tabPage2.Location = new System.Drawing.Point(4, 22); this.tabPage2.Name = "tabPage2"; this.tabPage2.Padding = new System.Windows.Forms.Padding(3); - this.tabPage2.Size = new System.Drawing.Size(361, 407); + this.tabPage2.Size = new System.Drawing.Size(366, 407); this.tabPage2.TabIndex = 1; this.tabPage2.Text = "Appearance"; this.tabPage2.UseVisualStyleBackColor = true; @@ -165,7 +170,7 @@ private void InitializeComponent() this.label10.AutoSize = true; this.label10.Location = new System.Drawing.Point(3, 188); this.label10.Name = "label10"; - this.label10.Size = new System.Drawing.Size(100, 13); + this.label10.Size = new System.Drawing.Size(97, 13); this.label10.TabIndex = 104; this.label10.Text = "Lag frames to hide:"; // @@ -199,7 +204,7 @@ private void InitializeComponent() 0, 0}); this.HideLagNum.Name = "HideLagNum"; - this.HideLagNum.Size = new System.Drawing.Size(50, 21); + this.HideLagNum.Size = new System.Drawing.Size(50, 20); this.HideLagNum.TabIndex = 102; this.toolTip1.SetToolTip(this.HideLagNum, "Hide up to this many lag frames per group of consecutive lag frames. Intended for" + " use in 30FPS games running on 60FPS consoles."); @@ -209,7 +214,7 @@ private void InitializeComponent() this.MarkerColorCheckbox.AutoSize = true; this.MarkerColorCheckbox.Location = new System.Drawing.Point(6, 104); this.MarkerColorCheckbox.Name = "MarkerColorCheckbox"; - this.MarkerColorCheckbox.Size = new System.Drawing.Size(233, 17); + this.MarkerColorCheckbox.Size = new System.Drawing.Size(230, 17); this.MarkerColorCheckbox.TabIndex = 6; this.MarkerColorCheckbox.Text = "Denote markers with color on frame column"; this.toolTip1.SetToolTip(this.MarkerColorCheckbox, "If enabled, the frame column will have a different background color on each frame" + @@ -221,7 +226,7 @@ private void InitializeComponent() this.MarkerIconsCheckbox.AutoSize = true; this.MarkerIconsCheckbox.Location = new System.Drawing.Point(6, 81); this.MarkerIconsCheckbox.Name = "MarkerIconsCheckbox"; - this.MarkerIconsCheckbox.Size = new System.Drawing.Size(152, 17); + this.MarkerIconsCheckbox.Size = new System.Drawing.Size(151, 17); this.MarkerIconsCheckbox.TabIndex = 5; this.MarkerIconsCheckbox.Text = "Denote markers with icons"; this.toolTip1.SetToolTip(this.MarkerIconsCheckbox, "If enabled, the frame column will contain an icon on each frame that TAStudio has" + @@ -233,7 +238,7 @@ private void InitializeComponent() this.StateColorCheckbox.AutoSize = true; this.StateColorCheckbox.Location = new System.Drawing.Point(6, 58); this.StateColorCheckbox.Name = "StateColorCheckbox"; - this.StateColorCheckbox.Size = new System.Drawing.Size(202, 17); + this.StateColorCheckbox.Size = new System.Drawing.Size(200, 17); this.StateColorCheckbox.TabIndex = 4; this.StateColorCheckbox.Text = "Denote states with background color"; this.toolTip1.SetToolTip(this.StateColorCheckbox, "If enabled, the the background color of a row will be slightly different when TAS" + @@ -245,7 +250,7 @@ private void InitializeComponent() this.StateIconsCheckbox.AutoSize = true; this.StateIconsCheckbox.Location = new System.Drawing.Point(6, 35); this.StateIconsCheckbox.Name = "StateIconsCheckbox"; - this.StateIconsCheckbox.Size = new System.Drawing.Size(144, 17); + this.StateIconsCheckbox.Size = new System.Drawing.Size(142, 17); this.StateIconsCheckbox.TabIndex = 3; this.StateIconsCheckbox.Text = "Denote states with icons"; this.toolTip1.SetToolTip(this.StateIconsCheckbox, "If enabled, the frame column will contain an icon on each frame that TAStudio has" + @@ -257,7 +262,7 @@ private void InitializeComponent() this.HideWasLagCheckbox.AutoSize = true; this.HideWasLagCheckbox.Location = new System.Drawing.Point(6, 210); this.HideWasLagCheckbox.Name = "HideWasLagCheckbox"; - this.HideWasLagCheckbox.Size = new System.Drawing.Size(130, 17); + this.HideWasLagCheckbox.Size = new System.Drawing.Size(131, 17); this.HideWasLagCheckbox.TabIndex = 103; this.HideWasLagCheckbox.Text = "Hide \"was lag\" frames"; this.toolTip1.SetToolTip(this.HideWasLagCheckbox, resources.GetString("HideWasLagCheckbox.ToolTip")); @@ -268,7 +273,7 @@ private void InitializeComponent() this.RotateCheckbox.AutoSize = true; this.RotateCheckbox.Location = new System.Drawing.Point(6, 164); this.RotateCheckbox.Name = "RotateCheckbox"; - this.RotateCheckbox.Size = new System.Drawing.Size(103, 17); + this.RotateCheckbox.Size = new System.Drawing.Size(100, 17); this.RotateCheckbox.TabIndex = 100; this.RotateCheckbox.Text = "Rotate input roll"; this.RotateCheckbox.UseVisualStyleBackColor = true; @@ -295,7 +300,7 @@ private void InitializeComponent() this.tabPage5.Location = new System.Drawing.Point(4, 22); this.tabPage5.Name = "tabPage5"; this.tabPage5.Padding = new System.Windows.Forms.Padding(3); - this.tabPage5.Size = new System.Drawing.Size(361, 407); + this.tabPage5.Size = new System.Drawing.Size(366, 407); this.tabPage5.TabIndex = 4; this.tabPage5.Text = "Autosave"; this.tabPage5.UseVisualStyleBackColor = true; @@ -305,7 +310,7 @@ private void InitializeComponent() this.label11.AutoSize = true; this.label11.Location = new System.Drawing.Point(6, 29); this.label11.Name = "label11"; - this.label11.Size = new System.Drawing.Size(142, 13); + this.label11.Size = new System.Drawing.Size(139, 13); this.label11.TabIndex = 205; this.label11.Text = "Set to 0 to disable autosave"; // @@ -314,7 +319,7 @@ private void InitializeComponent() this.BackupOnSaveCheckbox.AutoSize = true; this.BackupOnSaveCheckbox.Location = new System.Drawing.Point(6, 102); this.BackupOnSaveCheckbox.Name = "BackupOnSaveCheckbox"; - this.BackupOnSaveCheckbox.Size = new System.Drawing.Size(222, 17); + this.BackupOnSaveCheckbox.Size = new System.Drawing.Size(226, 17); this.BackupOnSaveCheckbox.TabIndex = 204; this.BackupOnSaveCheckbox.Text = "When manually saving, also save .backup"; this.toolTip1.SetToolTip(this.BackupOnSaveCheckbox, "Save a backup .tasproj after each manual file save."); @@ -325,7 +330,7 @@ private void InitializeComponent() this.AutosaveBackupCheckbox.AutoSize = true; this.AutosaveBackupCheckbox.Location = new System.Drawing.Point(6, 79); this.AutosaveBackupCheckbox.Name = "AutosaveBackupCheckbox"; - this.AutosaveBackupCheckbox.Size = new System.Drawing.Size(182, 17); + this.AutosaveBackupCheckbox.Size = new System.Drawing.Size(181, 17); this.AutosaveBackupCheckbox.TabIndex = 203; this.AutosaveBackupCheckbox.Text = "Save as .backup (no greenzone)"; this.toolTip1.SetToolTip(this.AutosaveBackupCheckbox, resources.GetString("AutosaveBackupCheckbox.ToolTip")); @@ -336,7 +341,7 @@ private void InitializeComponent() this.AutosaveBk2Checkbox.AutoSize = true; this.AutosaveBk2Checkbox.Location = new System.Drawing.Point(6, 56); this.AutosaveBk2Checkbox.Name = "AutosaveBk2Checkbox"; - this.AutosaveBk2Checkbox.Size = new System.Drawing.Size(88, 17); + this.AutosaveBk2Checkbox.Size = new System.Drawing.Size(89, 17); this.AutosaveBk2Checkbox.TabIndex = 202; this.AutosaveBk2Checkbox.Text = "Save as .bk2"; this.toolTip1.SetToolTip(this.AutosaveBk2Checkbox, "Autosave will export movie to a .bk2 file instead of saving a .tasproj file."); @@ -347,7 +352,7 @@ private void InitializeComponent() this.label5.AutoSize = true; this.label5.Location = new System.Drawing.Point(208, 8); this.label5.Name = "label5"; - this.label5.Size = new System.Drawing.Size(46, 13); + this.label5.Size = new System.Drawing.Size(47, 13); this.label5.TabIndex = 6; this.label5.Text = "seconds"; // @@ -356,7 +361,7 @@ private void InitializeComponent() this.label4.AutoSize = true; this.label4.Location = new System.Drawing.Point(6, 8); this.label4.Name = "label4"; - this.label4.Size = new System.Drawing.Size(109, 13); + this.label4.Size = new System.Drawing.Size(105, 13); this.label4.TabIndex = 5; this.label4.Text = "Autosave frequency:"; // @@ -369,7 +374,7 @@ private void InitializeComponent() 0, 0}); this.AutosaveIntervalNum.Name = "AutosaveIntervalNum"; - this.AutosaveIntervalNum.Size = new System.Drawing.Size(85, 21); + this.AutosaveIntervalNum.Size = new System.Drawing.Size(85, 20); this.AutosaveIntervalNum.TabIndex = 201; this.toolTip1.SetToolTip(this.AutosaveIntervalNum, resources.GetString("AutosaveIntervalNum.ToolTip")); // @@ -385,7 +390,7 @@ private void InitializeComponent() this.tabPage4.Location = new System.Drawing.Point(4, 22); this.tabPage4.Name = "tabPage4"; this.tabPage4.Padding = new System.Windows.Forms.Padding(3); - this.tabPage4.Size = new System.Drawing.Size(361, 407); + this.tabPage4.Size = new System.Drawing.Size(366, 407); this.tabPage4.TabIndex = 3; this.tabPage4.Text = "Savestates"; this.tabPage4.UseVisualStyleBackColor = true; @@ -395,7 +400,7 @@ private void InitializeComponent() this.label12.AutoSize = true; this.label12.Location = new System.Drawing.Point(9, 362); this.label12.Name = "label12"; - this.label12.Size = new System.Drawing.Size(233, 13); + this.label12.Size = new System.Drawing.Size(230, 13); this.label12.TabIndex = 306; this.label12.Text = "Each movie contains its own savestate settings"; // @@ -439,7 +444,7 @@ private void InitializeComponent() this.ManagerSettingsPropertyGrid.Location = new System.Drawing.Point(9, 33); this.ManagerSettingsPropertyGrid.Name = "ManagerSettingsPropertyGrid"; this.ManagerSettingsPropertyGrid.PropertySort = System.Windows.Forms.PropertySort.NoSort; - this.ManagerSettingsPropertyGrid.Size = new System.Drawing.Size(342, 326); + this.ManagerSettingsPropertyGrid.Size = new System.Drawing.Size(347, 326); this.ManagerSettingsPropertyGrid.TabIndex = 301; this.ManagerSettingsPropertyGrid.ToolbarVisible = false; // @@ -460,7 +465,7 @@ private void InitializeComponent() this.label6.AutoSize = true; this.label6.Location = new System.Drawing.Point(6, 9); this.label6.Name = "label6"; - this.label6.Size = new System.Drawing.Size(117, 13); + this.label6.Size = new System.Drawing.Size(112, 13); this.label6.TabIndex = 0; this.label6.Text = "Management strategy:"; // @@ -472,7 +477,7 @@ private void InitializeComponent() this.tabPage1.Location = new System.Drawing.Point(4, 22); this.tabPage1.Name = "tabPage1"; this.tabPage1.Padding = new System.Windows.Forms.Padding(3); - this.tabPage1.Size = new System.Drawing.Size(361, 407); + this.tabPage1.Size = new System.Drawing.Size(366, 407); this.tabPage1.TabIndex = 5; this.tabPage1.Text = "Paint Patterns"; this.tabPage1.UseVisualStyleBackColor = true; @@ -518,7 +523,7 @@ private void InitializeComponent() 0, 0}); this.CountNum.Name = "CountNum"; - this.CountNum.Size = new System.Drawing.Size(38, 21); + this.CountNum.Size = new System.Drawing.Size(38, 20); this.CountNum.TabIndex = 423; this.CountNum.Value = new decimal(new int[] { 1, @@ -542,7 +547,7 @@ private void InitializeComponent() 0, -2147483648}); this.ValueNum.Name = "ValueNum"; - this.ValueNum.Size = new System.Drawing.Size(51, 21); + this.ValueNum.Size = new System.Drawing.Size(51, 20); this.ValueNum.TabIndex = 422; this.ValueNum.Visible = false; this.ValueNum.ValueChanged += new System.EventHandler(this.ValueNum_ValueChanged); @@ -569,7 +574,7 @@ private void InitializeComponent() this.LagBox.CheckState = System.Windows.Forms.CheckState.Checked; this.LagBox.Location = new System.Drawing.Point(6, 243); this.LagBox.Name = "LagBox"; - this.LagBox.Size = new System.Drawing.Size(135, 17); + this.LagBox.Size = new System.Drawing.Size(132, 17); this.LagBox.TabIndex = 426; this.LagBox.Text = "Account for lag frames"; this.LagBox.UseVisualStyleBackColor = true; @@ -642,7 +647,7 @@ private void InitializeComponent() this.PatternCustomRadioButton.AutoSize = true; this.PatternCustomRadioButton.Location = new System.Drawing.Point(6, 68); this.PatternCustomRadioButton.Name = "PatternCustomRadioButton"; - this.PatternCustomRadioButton.Size = new System.Drawing.Size(61, 17); + this.PatternCustomRadioButton.Size = new System.Drawing.Size(60, 17); this.PatternCustomRadioButton.TabIndex = 412; this.PatternCustomRadioButton.TabStop = true; this.PatternCustomRadioButton.Text = "Custom"; @@ -656,7 +661,7 @@ private void InitializeComponent() this.PatternHoldRadioButton.AutoSize = true; this.PatternHoldRadioButton.Location = new System.Drawing.Point(6, 22); this.PatternHoldRadioButton.Name = "PatternHoldRadioButton"; - this.PatternHoldRadioButton.Size = new System.Drawing.Size(46, 17); + this.PatternHoldRadioButton.Size = new System.Drawing.Size(47, 17); this.PatternHoldRadioButton.TabIndex = 410; this.PatternHoldRadioButton.TabStop = true; this.PatternHoldRadioButton.Text = "Hold"; @@ -668,7 +673,7 @@ private void InitializeComponent() this.PatternAutoFireRadioButton.AutoSize = true; this.PatternAutoFireRadioButton.Location = new System.Drawing.Point(6, 45); this.PatternAutoFireRadioButton.Name = "PatternAutoFireRadioButton"; - this.PatternAutoFireRadioButton.Size = new System.Drawing.Size(70, 17); + this.PatternAutoFireRadioButton.Size = new System.Drawing.Size(67, 17); this.PatternAutoFireRadioButton.TabIndex = 411; this.PatternAutoFireRadioButton.TabStop = true; this.PatternAutoFireRadioButton.Text = "Auto-Fire"; @@ -692,7 +697,7 @@ private void InitializeComponent() this.PatternPaintAlwaysRadioButton.AutoSize = true; this.PatternPaintAlwaysRadioButton.Location = new System.Drawing.Point(6, 65); this.PatternPaintAlwaysRadioButton.Name = "PatternPaintAlwaysRadioButton"; - this.PatternPaintAlwaysRadioButton.Size = new System.Drawing.Size(59, 17); + this.PatternPaintAlwaysRadioButton.Size = new System.Drawing.Size(58, 17); this.PatternPaintAlwaysRadioButton.TabIndex = 402; this.PatternPaintAlwaysRadioButton.TabStop = true; this.PatternPaintAlwaysRadioButton.Text = "Always"; @@ -705,7 +710,7 @@ private void InitializeComponent() this.PatternPaintAutoColumnsOnlyRadioButton.AutoSize = true; this.PatternPaintAutoColumnsOnlyRadioButton.Location = new System.Drawing.Point(6, 42); this.PatternPaintAutoColumnsOnlyRadioButton.Name = "PatternPaintAutoColumnsOnlyRadioButton"; - this.PatternPaintAutoColumnsOnlyRadioButton.Size = new System.Drawing.Size(134, 17); + this.PatternPaintAutoColumnsOnlyRadioButton.Size = new System.Drawing.Size(131, 17); this.PatternPaintAutoColumnsOnlyRadioButton.TabIndex = 401; this.PatternPaintAutoColumnsOnlyRadioButton.TabStop = true; this.PatternPaintAutoColumnsOnlyRadioButton.Text = "Auto-Fire columns only"; @@ -733,19 +738,14 @@ private void InitializeComponent() this.tabPage3.Controls.Add(this.label8); this.tabPage3.Controls.Add(this.RewindNum); this.tabPage3.Controls.Add(this.label7); - this.tabPage3.Controls.Add(this.groupBox4); - this.tabPage3.Controls.Add(this.AlwaysScrollCheckbox); this.tabPage3.Controls.Add(this.BindMarkersCheckbox); this.tabPage3.Controls.Add(this.IncludeFrameNumberCheckbox); this.tabPage3.Controls.Add(this.UndoCountNum); this.tabPage3.Controls.Add(this.label1); - this.tabPage3.Controls.Add(this.label3); - this.tabPage3.Controls.Add(this.ScrollSpeedNum); - this.tabPage3.Controls.Add(this.label2); this.tabPage3.Controls.Add(this.AutopauseCheckbox); this.tabPage3.Location = new System.Drawing.Point(4, 22); this.tabPage3.Name = "tabPage3"; - this.tabPage3.Size = new System.Drawing.Size(361, 407); + this.tabPage3.Size = new System.Drawing.Size(366, 407); this.tabPage3.TabIndex = 2; this.tabPage3.Text = "Misc"; this.tabPage3.UseVisualStyleBackColor = true; @@ -753,9 +753,9 @@ private void InitializeComponent() // OldBranchesCheckbox // this.OldBranchesCheckbox.AutoSize = true; - this.OldBranchesCheckbox.Location = new System.Drawing.Point(12, 346); + this.OldBranchesCheckbox.Location = new System.Drawing.Point(12, 177); this.OldBranchesCheckbox.Name = "OldBranchesCheckbox"; - this.OldBranchesCheckbox.Size = new System.Drawing.Size(181, 17); + this.OldBranchesCheckbox.Size = new System.Drawing.Size(179, 17); this.OldBranchesCheckbox.TabIndex = 518; this.OldBranchesCheckbox.Text = "Old control scheme for branches"; this.toolTip1.SetToolTip(this.OldBranchesCheckbox, resources.GetString("OldBranchesCheckbox.ToolTip")); @@ -764,23 +764,23 @@ private void InitializeComponent() // BranchDoubleClickCheckbox // this.BranchDoubleClickCheckbox.AutoSize = true; - this.BranchDoubleClickCheckbox.Location = new System.Drawing.Point(12, 323); + this.BranchDoubleClickCheckbox.Location = new System.Drawing.Point(12, 154); this.BranchDoubleClickCheckbox.Name = "BranchDoubleClickCheckbox"; - this.BranchDoubleClickCheckbox.Size = new System.Drawing.Size(171, 17); + this.BranchDoubleClickCheckbox.Size = new System.Drawing.Size(174, 17); this.BranchDoubleClickCheckbox.TabIndex = 517; this.BranchDoubleClickCheckbox.Text = "Load branch by double clicking"; this.BranchDoubleClickCheckbox.UseVisualStyleBackColor = true; // // FastRewindNum // - this.FastRewindNum.Location = new System.Drawing.Point(116, 200); + this.FastRewindNum.Location = new System.Drawing.Point(116, 31); this.FastRewindNum.Minimum = new decimal(new int[] { 1, 0, 0, 0}); this.FastRewindNum.Name = "FastRewindNum"; - this.FastRewindNum.Size = new System.Drawing.Size(67, 21); + this.FastRewindNum.Size = new System.Drawing.Size(67, 20); this.FastRewindNum.TabIndex = 512; this.toolTip1.SetToolTip(this.FastRewindNum, "how many frames backward to go when you press the rewind button or hotkey,\r\nwhile" + " also holding the turbo hotkey or fast forward hotkey"); @@ -793,22 +793,22 @@ private void InitializeComponent() // label8 // this.label8.AutoSize = true; - this.label8.Location = new System.Drawing.Point(9, 204); + this.label8.Location = new System.Drawing.Point(9, 35); this.label8.Name = "label8"; - this.label8.Size = new System.Drawing.Size(91, 13); + this.label8.Size = new System.Drawing.Size(87, 13); this.label8.TabIndex = 12; this.label8.Text = "Fast rewind step:"; // // RewindNum // - this.RewindNum.Location = new System.Drawing.Point(116, 175); + this.RewindNum.Location = new System.Drawing.Point(116, 6); this.RewindNum.Minimum = new decimal(new int[] { 1, 0, 0, 0}); this.RewindNum.Name = "RewindNum"; - this.RewindNum.Size = new System.Drawing.Size(67, 21); + this.RewindNum.Size = new System.Drawing.Size(67, 20); this.RewindNum.TabIndex = 511; this.toolTip1.SetToolTip(this.RewindNum, "how many frames backward to go when you press the rewind button or hotkey"); this.RewindNum.Value = new decimal(new int[] { @@ -820,22 +820,126 @@ private void InitializeComponent() // label7 // this.label7.AutoSize = true; - this.label7.Location = new System.Drawing.Point(9, 178); + this.label7.Location = new System.Drawing.Point(9, 9); this.label7.Name = "label7"; - this.label7.Size = new System.Drawing.Size(70, 13); + this.label7.Size = new System.Drawing.Size(69, 13); this.label7.TabIndex = 12; this.label7.Text = "Rewind step:"; // + // BindMarkersCheckbox + // + this.BindMarkersCheckbox.AutoSize = true; + this.BindMarkersCheckbox.Location = new System.Drawing.Point(12, 85); + this.BindMarkersCheckbox.Name = "BindMarkersCheckbox"; + this.BindMarkersCheckbox.Size = new System.Drawing.Size(125, 17); + this.BindMarkersCheckbox.TabIndex = 514; + this.BindMarkersCheckbox.Text = "Bind markers to input"; + this.toolTip1.SetToolTip(this.BindMarkersCheckbox, "When enabled, inserting or deleting frames will move any markers that are on futu" + + "re frames by the same amount.\r\nDeleting a frame that has a marker will delete th" + + "e marker."); + this.BindMarkersCheckbox.UseVisualStyleBackColor = true; + // + // IncludeFrameNumberCheckbox + // + this.IncludeFrameNumberCheckbox.AutoSize = true; + this.IncludeFrameNumberCheckbox.Location = new System.Drawing.Point(12, 108); + this.IncludeFrameNumberCheckbox.Name = "IncludeFrameNumberCheckbox"; + this.IncludeFrameNumberCheckbox.Size = new System.Drawing.Size(228, 17); + this.IncludeFrameNumberCheckbox.TabIndex = 515; + this.IncludeFrameNumberCheckbox.Text = "Include frame numbers when copying input"; + this.IncludeFrameNumberCheckbox.UseVisualStyleBackColor = true; + // + // UndoCountNum + // + this.UndoCountNum.Location = new System.Drawing.Point(116, 58); + this.UndoCountNum.Maximum = new decimal(new int[] { + 10000, + 0, + 0, + 0}); + this.UndoCountNum.Minimum = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.UndoCountNum.Name = "UndoCountNum"; + this.UndoCountNum.Size = new System.Drawing.Size(67, 20); + this.UndoCountNum.TabIndex = 513; + this.toolTip1.SetToolTip(this.UndoCountNum, "the maximum number of edit actions TAStudio should keep in the undo history"); + this.UndoCountNum.Value = new decimal(new int[] { + 10, + 0, + 0, + 0}); + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(9, 61); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(90, 13); + this.label1.TabIndex = 7; + this.label1.Text = "Max Undo Count:"; + // + // AutopauseCheckbox + // + this.AutopauseCheckbox.AutoSize = true; + this.AutopauseCheckbox.Location = new System.Drawing.Point(12, 131); + this.AutopauseCheckbox.Name = "AutopauseCheckbox"; + this.AutopauseCheckbox.Size = new System.Drawing.Size(196, 17); + this.AutopauseCheckbox.TabIndex = 516; + this.AutopauseCheckbox.Text = "Automatically pause at end of movie"; + this.AutopauseCheckbox.UseVisualStyleBackColor = true; + // + // SettingsCancelButton + // + this.SettingsCancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.SettingsCancelButton.Location = new System.Drawing.Point(307, 449); + this.SettingsCancelButton.Name = "SettingsCancelButton"; + this.SettingsCancelButton.Size = new System.Drawing.Size(75, 23); + this.SettingsCancelButton.TabIndex = 3; + this.SettingsCancelButton.Text = "Cancel"; + this.SettingsCancelButton.UseVisualStyleBackColor = true; + this.SettingsCancelButton.Click += new System.EventHandler(this.SettingsCancelButton_Click); + // + // ApplyButton + // + this.ApplyButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.ApplyButton.Location = new System.Drawing.Point(226, 449); + this.ApplyButton.Name = "ApplyButton"; + this.ApplyButton.Size = new System.Drawing.Size(75, 23); + this.ApplyButton.TabIndex = 3; + this.ApplyButton.Text = "Apply"; + this.ApplyButton.UseVisualStyleBackColor = true; + this.ApplyButton.Click += new System.EventHandler(this.ApplyButton_Click); + // + // tabPage6 + // + this.tabPage6.Controls.Add(this.ScrollSyncCheckbox); + this.tabPage6.Controls.Add(this.EditInvisibleColumnsCheckbox); + this.tabPage6.Controls.Add(this.groupBox4); + this.tabPage6.Controls.Add(this.AlwaysScrollCheckbox); + this.tabPage6.Controls.Add(this.label3); + this.tabPage6.Controls.Add(this.ScrollSpeedNum); + this.tabPage6.Controls.Add(this.label2); + this.tabPage6.Location = new System.Drawing.Point(4, 22); + this.tabPage6.Name = "tabPage6"; + this.tabPage6.Padding = new System.Windows.Forms.Padding(3); + this.tabPage6.Size = new System.Drawing.Size(366, 407); + this.tabPage6.TabIndex = 6; + this.tabPage6.Text = "Input Roll"; + this.tabPage6.UseVisualStyleBackColor = true; + // // groupBox4 // this.groupBox4.Controls.Add(this.ScrollToCenterRadio); this.groupBox4.Controls.Add(this.ScrollToBottomRadio); this.groupBox4.Controls.Add(this.ScrollToTopRadio); this.groupBox4.Controls.Add(this.ScrollToViewRadio); - this.groupBox4.Location = new System.Drawing.Point(9, 28); + this.groupBox4.Location = new System.Drawing.Point(12, 31); this.groupBox4.Name = "groupBox4"; this.groupBox4.Size = new System.Drawing.Size(168, 116); - this.groupBox4.TabIndex = 11; + this.groupBox4.TabIndex = 513; this.groupBox4.TabStop = false; this.groupBox4.Text = "Scroll to cursor method"; // @@ -844,7 +948,7 @@ private void InitializeComponent() this.ScrollToCenterRadio.AutoSize = true; this.ScrollToCenterRadio.Location = new System.Drawing.Point(6, 88); this.ScrollToCenterRadio.Name = "ScrollToCenterRadio"; - this.ScrollToCenterRadio.Size = new System.Drawing.Size(96, 17); + this.ScrollToCenterRadio.Size = new System.Drawing.Size(94, 17); this.ScrollToCenterRadio.TabIndex = 504; this.ScrollToCenterRadio.TabStop = true; this.ScrollToCenterRadio.Text = "scroll to center"; @@ -856,7 +960,7 @@ private void InitializeComponent() this.ScrollToBottomRadio.AutoSize = true; this.ScrollToBottomRadio.Location = new System.Drawing.Point(6, 65); this.ScrollToBottomRadio.Name = "ScrollToBottomRadio"; - this.ScrollToBottomRadio.Size = new System.Drawing.Size(99, 17); + this.ScrollToBottomRadio.Size = new System.Drawing.Size(96, 17); this.ScrollToBottomRadio.TabIndex = 503; this.ScrollToBottomRadio.TabStop = true; this.ScrollToBottomRadio.Text = "scroll to bottom"; @@ -868,7 +972,7 @@ private void InitializeComponent() this.ScrollToTopRadio.AutoSize = true; this.ScrollToTopRadio.Location = new System.Drawing.Point(6, 42); this.ScrollToTopRadio.Name = "ScrollToTopRadio"; - this.ScrollToTopRadio.Size = new System.Drawing.Size(81, 17); + this.ScrollToTopRadio.Size = new System.Drawing.Size(79, 17); this.ScrollToTopRadio.TabIndex = 502; this.ScrollToTopRadio.TabStop = true; this.ScrollToTopRadio.Text = "scroll to top"; @@ -880,7 +984,7 @@ private void InitializeComponent() this.ScrollToViewRadio.AutoSize = true; this.ScrollToViewRadio.Location = new System.Drawing.Point(6, 19); this.ScrollToViewRadio.Name = "ScrollToViewRadio"; - this.ScrollToViewRadio.Size = new System.Drawing.Size(87, 17); + this.ScrollToViewRadio.Size = new System.Drawing.Size(86, 17); this.ScrollToViewRadio.TabIndex = 501; this.ScrollToViewRadio.TabStop = true; this.ScrollToViewRadio.Text = "scroll to view"; @@ -890,133 +994,68 @@ private void InitializeComponent() // AlwaysScrollCheckbox // this.AlwaysScrollCheckbox.AutoSize = true; - this.AlwaysScrollCheckbox.Location = new System.Drawing.Point(9, 5); + this.AlwaysScrollCheckbox.Location = new System.Drawing.Point(12, 8); this.AlwaysScrollCheckbox.Name = "AlwaysScrollCheckbox"; - this.AlwaysScrollCheckbox.Size = new System.Drawing.Size(238, 17); - this.AlwaysScrollCheckbox.TabIndex = 500; + this.AlwaysScrollCheckbox.Size = new System.Drawing.Size(231, 17); + this.AlwaysScrollCheckbox.TabIndex = 514; this.AlwaysScrollCheckbox.Text = "When following cursor, scroll at every frame"; this.toolTip1.SetToolTip(this.AlwaysScrollCheckbox, "When disabled, \"follow cursor\" will not scroll when the cursor is already visible" + "."); this.AlwaysScrollCheckbox.UseVisualStyleBackColor = true; // - // BindMarkersCheckbox - // - this.BindMarkersCheckbox.AutoSize = true; - this.BindMarkersCheckbox.Location = new System.Drawing.Point(12, 254); - this.BindMarkersCheckbox.Name = "BindMarkersCheckbox"; - this.BindMarkersCheckbox.Size = new System.Drawing.Size(127, 17); - this.BindMarkersCheckbox.TabIndex = 514; - this.BindMarkersCheckbox.Text = "Bind markers to input"; - this.toolTip1.SetToolTip(this.BindMarkersCheckbox, "When enabled, inserting or deleting frames will move any markers that are on futu" + - "re frames by the same amount.\r\nDeleting a frame that has a marker will delete th" + - "e marker."); - this.BindMarkersCheckbox.UseVisualStyleBackColor = true; - // - // IncludeFrameNumberCheckbox - // - this.IncludeFrameNumberCheckbox.AutoSize = true; - this.IncludeFrameNumberCheckbox.Location = new System.Drawing.Point(12, 277); - this.IncludeFrameNumberCheckbox.Name = "IncludeFrameNumberCheckbox"; - this.IncludeFrameNumberCheckbox.Size = new System.Drawing.Size(232, 17); - this.IncludeFrameNumberCheckbox.TabIndex = 515; - this.IncludeFrameNumberCheckbox.Text = "Include frame numbers when copying input"; - this.IncludeFrameNumberCheckbox.UseVisualStyleBackColor = true; - // - // UndoCountNum - // - this.UndoCountNum.Location = new System.Drawing.Point(116, 227); - this.UndoCountNum.Maximum = new decimal(new int[] { - 10000, - 0, - 0, - 0}); - this.UndoCountNum.Minimum = new decimal(new int[] { - 1, - 0, - 0, - 0}); - this.UndoCountNum.Name = "UndoCountNum"; - this.UndoCountNum.Size = new System.Drawing.Size(67, 21); - this.UndoCountNum.TabIndex = 513; - this.toolTip1.SetToolTip(this.UndoCountNum, "the maximum number of edit actions TAStudio should keep in the undo history"); - this.UndoCountNum.Value = new decimal(new int[] { - 10, - 0, - 0, - 0}); - // - // label1 - // - this.label1.AutoSize = true; - this.label1.Location = new System.Drawing.Point(9, 230); - this.label1.Name = "label1"; - this.label1.Size = new System.Drawing.Size(91, 13); - this.label1.TabIndex = 7; - this.label1.Text = "Max Undo Count:"; - // // label3 // this.label3.AutoSize = true; - this.label3.Location = new System.Drawing.Point(189, 152); + this.label3.Location = new System.Drawing.Point(192, 155); this.label3.Name = "label3"; - this.label3.Size = new System.Drawing.Size(78, 13); - this.label3.TabIndex = 6; + this.label3.Size = new System.Drawing.Size(76, 13); + this.label3.TabIndex = 512; this.label3.Text = "frames per tick"; // // ScrollSpeedNum // - this.ScrollSpeedNum.Location = new System.Drawing.Point(116, 150); + this.ScrollSpeedNum.Location = new System.Drawing.Point(119, 153); this.ScrollSpeedNum.Name = "ScrollSpeedNum"; - this.ScrollSpeedNum.Size = new System.Drawing.Size(67, 21); - this.ScrollSpeedNum.TabIndex = 510; + this.ScrollSpeedNum.Size = new System.Drawing.Size(67, 20); + this.ScrollSpeedNum.TabIndex = 515; this.toolTip1.SetToolTip(this.ScrollSpeedNum, "how many frames to scroll per tick of the mouse wheel"); // // label2 // this.label2.AutoSize = true; - this.label2.Location = new System.Drawing.Point(9, 152); + this.label2.Location = new System.Drawing.Point(12, 155); this.label2.Name = "label2"; this.label2.Size = new System.Drawing.Size(100, 13); - this.label2.TabIndex = 4; + this.label2.TabIndex = 511; this.label2.Text = "Wheel scroll speed:"; // - // AutopauseCheckbox + // EditInvisibleColumnsCheckbox // - this.AutopauseCheckbox.AutoSize = true; - this.AutopauseCheckbox.Location = new System.Drawing.Point(12, 300); - this.AutopauseCheckbox.Name = "AutopauseCheckbox"; - this.AutopauseCheckbox.Size = new System.Drawing.Size(200, 17); - this.AutopauseCheckbox.TabIndex = 516; - this.AutopauseCheckbox.Text = "Automatically pause at end of movie"; - this.AutopauseCheckbox.UseVisualStyleBackColor = true; + this.EditInvisibleColumnsCheckbox.AutoSize = true; + this.EditInvisibleColumnsCheckbox.Location = new System.Drawing.Point(12, 202); + this.EditInvisibleColumnsCheckbox.Name = "EditInvisibleColumnsCheckbox"; + this.EditInvisibleColumnsCheckbox.Size = new System.Drawing.Size(308, 17); + this.EditInvisibleColumnsCheckbox.TabIndex = 519; + this.EditInvisibleColumnsCheckbox.Text = "Allow editing hidden columns (with clear, record, paste, etc.)"; + this.toolTip1.SetToolTip(this.EditInvisibleColumnsCheckbox, resources.GetString("EditInvisibleColumnsCheckbox.ToolTip")); + this.EditInvisibleColumnsCheckbox.UseVisualStyleBackColor = true; // - // SettingsCancelButton + // ScrollSyncCheckbox // - this.SettingsCancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.SettingsCancelButton.Location = new System.Drawing.Point(302, 449); - this.SettingsCancelButton.Name = "SettingsCancelButton"; - this.SettingsCancelButton.Size = new System.Drawing.Size(75, 23); - this.SettingsCancelButton.TabIndex = 3; - this.SettingsCancelButton.Text = "Cancel"; - this.SettingsCancelButton.UseVisualStyleBackColor = true; - this.SettingsCancelButton.Click += new System.EventHandler(this.SettingsCancelButton_Click); - // - // ApplyButton - // - this.ApplyButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.ApplyButton.Location = new System.Drawing.Point(221, 449); - this.ApplyButton.Name = "ApplyButton"; - this.ApplyButton.Size = new System.Drawing.Size(75, 23); - this.ApplyButton.TabIndex = 3; - this.ApplyButton.Text = "Apply"; - this.ApplyButton.UseVisualStyleBackColor = true; - this.ApplyButton.Click += new System.EventHandler(this.ApplyButton_Click); + this.ScrollSyncCheckbox.AutoSize = true; + this.ScrollSyncCheckbox.Location = new System.Drawing.Point(12, 179); + this.ScrollSyncCheckbox.Name = "ScrollSyncCheckbox"; + this.ScrollSyncCheckbox.Size = new System.Drawing.Size(171, 17); + this.ScrollSyncCheckbox.TabIndex = 520; + this.ScrollSyncCheckbox.Text = "Sync scroll across all input rolls"; + this.ScrollSyncCheckbox.UseVisualStyleBackColor = true; + this.toolTip1.SetToolTip(this.ScrollSyncCheckbox, "When enabled, scrolling one input roll will scroll all input rolls."); // // TAStudioSettingsForm // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(389, 484); + this.ClientSize = new System.Drawing.Size(394, 484); this.Controls.Add(this.ApplyButton); this.Controls.Add(this.SettingsCancelButton); this.Controls.Add(this.tabControl1); @@ -1045,9 +1084,11 @@ private void InitializeComponent() this.tabPage3.PerformLayout(); ((System.ComponentModel.ISupportInitialize)(this.FastRewindNum)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.RewindNum)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.UndoCountNum)).EndInit(); + this.tabPage6.ResumeLayout(false); + this.tabPage6.PerformLayout(); this.groupBox4.ResumeLayout(false); this.groupBox4.PerformLayout(); - ((System.ComponentModel.ISupportInitialize)(this.UndoCountNum)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.ScrollSpeedNum)).EndInit(); this.ResumeLayout(false); @@ -1067,9 +1108,6 @@ private void InitializeComponent() private System.Windows.Forms.CheckBox MarkerIconsCheckbox; private System.Windows.Forms.CheckBox StateColorCheckbox; private System.Windows.Forms.CheckBox StateIconsCheckbox; - private System.Windows.Forms.Label label3; - private System.Windows.Forms.NumericUpDown ScrollSpeedNum; - private System.Windows.Forms.Label label2; private System.Windows.Forms.CheckBox IncludeFrameNumberCheckbox; private System.Windows.Forms.NumericUpDown UndoCountNum; private System.Windows.Forms.Label label1; @@ -1105,12 +1143,6 @@ private void InitializeComponent() private System.Windows.Forms.ListBox PatternList; private System.Windows.Forms.ComboBox ButtonBox; private System.Windows.Forms.CheckBox BindMarkersCheckbox; - private System.Windows.Forms.GroupBox groupBox4; - private System.Windows.Forms.RadioButton ScrollToCenterRadio; - private System.Windows.Forms.RadioButton ScrollToBottomRadio; - private System.Windows.Forms.RadioButton ScrollToTopRadio; - private System.Windows.Forms.RadioButton ScrollToViewRadio; - private System.Windows.Forms.CheckBox AlwaysScrollCheckbox; private System.Windows.Forms.NumericUpDown FastRewindNum; private System.Windows.Forms.Label label8; private System.Windows.Forms.NumericUpDown RewindNum; @@ -1127,5 +1159,17 @@ private void InitializeComponent() private System.Windows.Forms.Label label12; private System.Windows.Forms.CheckBox OldBranchesCheckbox; private System.Windows.Forms.ToolTip toolTip1; + private System.Windows.Forms.TabPage tabPage6; + private System.Windows.Forms.CheckBox ScrollSyncCheckbox; + private System.Windows.Forms.CheckBox EditInvisibleColumnsCheckbox; + private System.Windows.Forms.GroupBox groupBox4; + private System.Windows.Forms.RadioButton ScrollToCenterRadio; + private System.Windows.Forms.RadioButton ScrollToBottomRadio; + private System.Windows.Forms.RadioButton ScrollToTopRadio; + private System.Windows.Forms.RadioButton ScrollToViewRadio; + private System.Windows.Forms.CheckBox AlwaysScrollCheckbox; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.NumericUpDown ScrollSpeedNum; + private System.Windows.Forms.Label label2; } } \ No newline at end of file diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudioSettingsForm.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudioSettingsForm.cs index 9957645792a..a7e34900e67 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudioSettingsForm.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudioSettingsForm.cs @@ -67,9 +67,9 @@ private void TAStudioSettingsForm_Load(object sender, EventArgs e) MarkerIconsCheckbox.Checked = _settings.GeneralClientSettings.DenoteMarkersWithIcons; StateColorCheckbox.Checked = _settings.GeneralClientSettings.DenoteStatesWithBGColor; StateIconsCheckbox.Checked = _settings.GeneralClientSettings.DenoteStatesWithIcons; - HideLagNum.Value = _settings.MovieSettings.InputRollSettings.LagFramesToHide; - HideWasLagCheckbox.Checked = _settings.MovieSettings.InputRollSettings.HideWasLagFrames; - RotateCheckbox.Checked = _settings.MovieSettings.InputRollSettings.HorizontalOrientation; + HideLagNum.Value = _settings.MovieSettings.LagFramesToHide; + HideWasLagCheckbox.Checked = _settings.MovieSettings.HideWasLagFrames; + RotateCheckbox.Checked = _settings.MovieSettings.HorizontalOrientation; // autosave AutosaveBackupCheckbox.Checked = _settings.GeneralClientSettings.AutosaveAsBackupFile; @@ -77,17 +77,10 @@ private void TAStudioSettingsForm_Load(object sender, EventArgs e) AutosaveIntervalNum.Value = _settings.GeneralClientSettings.AutosaveInterval / 1000; BackupOnSaveCheckbox.Checked = _settings.GeneralClientSettings.BackupPerFileSave; - // misc - BindMarkersCheckbox.Checked = _settings.GeneralClientSettings.BindMarkersToInput; - AutopauseCheckbox.Checked = _settings.GeneralClientSettings.AutoPause; - BranchDoubleClickCheckbox.Checked = _settings.GeneralClientSettings.LoadBranchOnDoubleClick; - OldBranchesCheckbox.Checked = _settings.GeneralClientSettings.OldControlSchemeForBranches; - IncludeFrameNumberCheckbox.Checked = _settings.GeneralClientSettings.CopyIncludesFrameNo; + // input roll AlwaysScrollCheckbox.Checked = _settings.GeneralClientSettings.FollowCursorAlwaysScroll; - UndoCountNum.Value = _settings.GeneralClientSettings.MaxUndoSteps; - RewindNum.Value = _settings.GeneralClientSettings.RewindStep; - FastRewindNum.Value = _settings.GeneralClientSettings.RewindStepFast; - ScrollSpeedNum.Value = _settings.GeneralClientSettings.ScrollSpeed; + EditInvisibleColumnsCheckbox.Checked = _settings.GeneralClientSettings.EditInvisibleColumns; + ScrollSyncCheckbox.Checked = _settings.GeneralClientSettings.ScrollSync; RadioButton scrollMethodRadio = _settings.GeneralClientSettings.FollowCursorScrollMethod switch { "near" => ScrollToViewRadio, @@ -98,6 +91,17 @@ private void TAStudioSettingsForm_Load(object sender, EventArgs e) }; scrollMethodRadio.Checked = true; + // misc + BindMarkersCheckbox.Checked = _settings.GeneralClientSettings.BindMarkersToInput; + AutopauseCheckbox.Checked = _settings.GeneralClientSettings.AutoPause; + BranchDoubleClickCheckbox.Checked = _settings.GeneralClientSettings.LoadBranchOnDoubleClick; + OldBranchesCheckbox.Checked = _settings.GeneralClientSettings.OldControlSchemeForBranches; + IncludeFrameNumberCheckbox.Checked = _settings.GeneralClientSettings.CopyIncludesFrameNo; + UndoCountNum.Value = _settings.GeneralClientSettings.MaxUndoSteps; + RewindNum.Value = _settings.GeneralClientSettings.RewindStep; + FastRewindNum.Value = _settings.GeneralClientSettings.RewindStepFast; + ScrollSpeedNum.Value = _settings.GeneralClientSettings.ScrollSpeed; + // patterns foreach (var button in _controllerDef.BoolButtons) { @@ -448,6 +452,21 @@ private void SetDefaultStateSettingsButton_Click(object sender, EventArgs e) private void ApplyButton_Click(object sender, EventArgs e) { + // warn the user of potentailly harmful settings + bool wasAlreadyBad = _settings.GeneralClientSettings.BindMarkersToInput && !_settings.GeneralClientSettings.EditInvisibleColumns; + bool isBad = BindMarkersCheckbox.Checked && !EditInvisibleColumnsCheckbox.Checked; + if (isBad && !wasAlreadyBad) + { + if (MessageBox.Show( + text: "You have enabled 'bind markers to input' and disabled 'allow editing hidden columns'. This combination is not recommended, and may result in your markers being moved in unexpected ways. Continue?", + caption: "Warning", + buttons: MessageBoxButtons.OKCancel, + icon: MessageBoxIcon.Warning) == DialogResult.Cancel) + { + return; + } + } + // stuff we keep copies of _settings.GeneralClientSettings.Palette = _palette; _settings.GeneralClientSettings.TasViewFont = _font; @@ -461,25 +480,28 @@ private void ApplyButton_Click(object sender, EventArgs e) _settings.GeneralClientSettings.DenoteMarkersWithIcons = MarkerIconsCheckbox.Checked; _settings.GeneralClientSettings.DenoteStatesWithBGColor = StateColorCheckbox.Checked; _settings.GeneralClientSettings.DenoteStatesWithIcons = StateIconsCheckbox.Checked; - _settings.MovieSettings.InputRollSettings.LagFramesToHide = (int)HideLagNum.Value; - _settings.MovieSettings.InputRollSettings.HideWasLagFrames = HideWasLagCheckbox.Checked; - _settings.MovieSettings.InputRollSettings.HorizontalOrientation = RotateCheckbox.Checked; + _settings.MovieSettings.LagFramesToHide = (int)HideLagNum.Value; + _settings.MovieSettings.HideWasLagFrames = HideWasLagCheckbox.Checked; + _settings.MovieSettings.HorizontalOrientation = RotateCheckbox.Checked; _settings.GeneralClientSettings.AutosaveAsBackupFile = AutosaveBackupCheckbox.Checked; _settings.GeneralClientSettings.AutosaveAsBk2 = AutosaveBk2Checkbox.Checked; _settings.GeneralClientSettings.AutosaveInterval = (uint)AutosaveIntervalNum.Value * 1000; _settings.GeneralClientSettings.BackupPerFileSave = BackupOnSaveCheckbox.Checked; + _settings.GeneralClientSettings.FollowCursorAlwaysScroll = AlwaysScrollCheckbox.Checked; + _settings.GeneralClientSettings.ScrollSpeed = (int)ScrollSpeedNum.Value; + _settings.GeneralClientSettings.ScrollSync = ScrollSyncCheckbox.Checked; + _settings.GeneralClientSettings.EditInvisibleColumns = EditInvisibleColumnsCheckbox.Checked; + _settings.GeneralClientSettings.BindMarkersToInput = BindMarkersCheckbox.Checked; _settings.GeneralClientSettings.AutoPause = AutopauseCheckbox.Checked; _settings.GeneralClientSettings.LoadBranchOnDoubleClick = BranchDoubleClickCheckbox.Checked; _settings.GeneralClientSettings.OldControlSchemeForBranches = OldBranchesCheckbox.Checked; _settings.GeneralClientSettings.CopyIncludesFrameNo = IncludeFrameNumberCheckbox.Checked; - _settings.GeneralClientSettings.FollowCursorAlwaysScroll = AlwaysScrollCheckbox.Checked; _settings.GeneralClientSettings.MaxUndoSteps = (int)UndoCountNum.Value; _settings.GeneralClientSettings.RewindStep = (int)RewindNum.Value; _settings.GeneralClientSettings.RewindStepFast = (int)FastRewindNum.Value; - _settings.GeneralClientSettings.ScrollSpeed = (int)ScrollSpeedNum.Value; if (ScrollToViewRadio.Checked) _settings.GeneralClientSettings.FollowCursorScrollMethod = "near"; else if (ScrollToTopRadio.Checked) _settings.GeneralClientSettings.FollowCursorScrollMethod = "top"; diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudioSettingsForm.resx b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudioSettingsForm.resx index c746d946c0a..ce3dd509779 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudioSettingsForm.resx +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudioSettingsForm.resx @@ -117,6 +117,22 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + When enabled, branches cannot be loaded when recording mode is off; instead TAStudio will seek to the branch frame. +And when recording mode is on, the movie will be truncated at the branch frame after loading a branch. + + + 17, 17 + + + When disabled, inputs that are not visible on the active input roll cannot be edited. +This allows you to, for example, insert and delete frames on a roll with player 1's inputs +without affecting player 2's inputs, by hiding all columns for player 2. +You can hide columns and create/delete input rolls by right-clicking on a column header. +Because markers are not associated with any input column, it is recommended that you turn +off "Bind markers to input" if you uncheck this box. Otherwise markers may move unexpectedly. +This feature is experimental but should be ready for use. + 17, 17 @@ -133,8 +149,4 @@ Backup movies will not contain any "greenzone" savestates, which makes them smal If neither "Save as ..." boxes are checked, autosave will overwrite whatever file is open. In no file is open, autosave will never do anything. - - When enabled, branches cannot be loaded when recording mode is off; instead TAStudio will seek to the branch frame. -And when recording mode is on, the movie will be truncated at the branch frame after loading a branch. - \ No newline at end of file diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/UndoHistoryForm.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/UndoHistoryForm.cs index e4f33b5f461..aed157cfb12 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/UndoHistoryForm.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/UndoHistoryForm.cs @@ -28,14 +28,14 @@ public UndoHistoryForm(TAStudio owner) MaxStepsNum.Value = Log.MaxSteps; } - private void HistoryView_QueryItemText(int index, RollColumn column, out string text, ref int offsetX, ref int offsetY) + private void HistoryView_QueryItemText(InputRoll sender, int index, RollColumn column, out string text, ref int offsetX, ref int offsetY) { text = column.Name == UndoColumnName ? Log.GetActionName(index) : index.ToString(); } - private void HistoryView_QueryItemBkColor(int index, RollColumn column, ref Color color) + private void HistoryView_QueryItemBkColor(InputRoll sender, int index, RollColumn column, ref Color color) { if (index == Log.UndoIndex) { diff --git a/src/BizHawk.Client.EmuHawk/tools/TraceLogger.cs b/src/BizHawk.Client.EmuHawk/tools/TraceLogger.cs index 621b8e5a054..d086b4a0a21 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TraceLogger.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TraceLogger.cs @@ -102,7 +102,7 @@ private void SaveConfigSettings() //Tracer.Enabled = LoggingEnabled.Checked; } - private void TraceView_QueryItemText(int index, RollColumn column, out string text, ref int offsetX, ref int offsetY) + private void TraceView_QueryItemText(InputRoll sender, int index, RollColumn column, out string text, ref int offsetX, ref int offsetY) { text = ""; if (index < _instructions.Count) diff --git a/src/BizHawk.Client.EmuHawk/tools/Watch/RamSearch.cs b/src/BizHawk.Client.EmuHawk/tools/Watch/RamSearch.cs index 05316bd4599..d1cef842326 100644 --- a/src/BizHawk.Client.EmuHawk/tools/Watch/RamSearch.cs +++ b/src/BizHawk.Client.EmuHawk/tools/Watch/RamSearch.cs @@ -186,7 +186,7 @@ private void OutOfRangeCheck() ErrorIconButton.Visible = _searches.OutOfRangeAddress.Any(); } - private void ListView_QueryItemBkColor(int index, RollColumn column, ref Color color) + private void ListView_QueryItemBkColor(InputRoll sender, int index, RollColumn column, ref Color color) { if ((_searches.Count > 0) && (index < _searches.Count)) { @@ -215,7 +215,7 @@ private void ListView_QueryItemBkColor(int index, RollColumn column, ref Color c } } - private void ListView_QueryItemText(int index, RollColumn column, out string text, ref int offsetX, ref int offsetY) + private void ListView_QueryItemText(InputRoll sender, int index, RollColumn column, out string text, ref int offsetX, ref int offsetY) { text = ""; diff --git a/src/BizHawk.Client.EmuHawk/tools/Watch/RamWatch.cs b/src/BizHawk.Client.EmuHawk/tools/Watch/RamWatch.cs index 7282cd3c5ea..86288fda74a 100644 --- a/src/BizHawk.Client.EmuHawk/tools/Watch/RamWatch.cs +++ b/src/BizHawk.Client.EmuHawk/tools/Watch/RamWatch.cs @@ -658,7 +658,7 @@ private void UpdateWatchCount() WatchCountLabel.Text = _watches.WatchCount + (_watches.WatchCount == 1 ? " watch" : " watches"); } - private void WatchListView_QueryItemBkColor(int index, RollColumn column, ref Color color) + private void WatchListView_QueryItemBkColor(InputRoll sender, int index, RollColumn column, ref Color color) { if (index >= _watches.Count) { @@ -679,7 +679,7 @@ private void WatchListView_QueryItemBkColor(int index, RollColumn column, ref Co } } - private void WatchListView_QueryItemText(int index, RollColumn column, out string text, ref int offsetX, ref int offsetY) + private void WatchListView_QueryItemText(InputRoll sender, int index, RollColumn column, out string text, ref int offsetX, ref int offsetY) { text = ""; if (index >= _watches.Count) diff --git a/src/BizHawk.Emulation.Common/Base Implementations/ControllerDefinition.cs b/src/BizHawk.Emulation.Common/Base Implementations/ControllerDefinition.cs index 0bb7931e0a4..1eac146c58d 100644 --- a/src/BizHawk.Emulation.Common/Base Implementations/ControllerDefinition.cs +++ b/src/BizHawk.Emulation.Common/Base Implementations/ControllerDefinition.cs @@ -117,6 +117,12 @@ private void AssertMutable() if (!_mutable) throw new InvalidOperationException(ERR_MSG); } + public void AssertImmutable() + { + const string ERR_MSG = "this " + nameof(ControllerDefinition) + " has not been marked immutable"; + if (_mutable) throw new InvalidOperationException(ERR_MSG); + } + /// Implementors should include empty lists for empty players, including "player 0" (console buttons), to match this base implementation protected virtual IReadOnlyList> GenOrderedControls() { @@ -176,5 +182,20 @@ public int NumControllerGroups public bool Any() => BoolButtons.Count is not 0 || Axes.Count is not 0; + + public static ControllerDefinition Merge(ControllerDefinition def1, ControllerDefinition def2) + { + ControllerDefinition merged = new("merged"); + merged.BoolButtons = def1.BoolButtons.Union(def2.BoolButtons).ToList(); + foreach (var axis in def1.Axes.Concat(def2.Axes)) + { + if (!merged.Axes.ContainsKey(axis.Key)) + { + merged.AddAxis(axis.Key, axis.Value.Range, axis.Value.Neutral, axis.Value.IsReversed, axis.Value.Constraint); + } + } + + return merged.MakeImmutable(); + } } } diff --git a/src/BizHawk.Tests.Client.Common/Movie/Bk2MovieTests.cs b/src/BizHawk.Tests.Client.Common/Movie/Bk2MovieTests.cs new file mode 100644 index 00000000000..3046e23948e --- /dev/null +++ b/src/BizHawk.Tests.Client.Common/Movie/Bk2MovieTests.cs @@ -0,0 +1,138 @@ +using BizHawk.Client.Common; +using BizHawk.Emulation.Common; + +namespace BizHawk.Tests.Client.Common.Movie +{ + [TestClass] + public class Bk2MovieTests + { + internal static Bk2Movie MakeMovie(int numberOfFrames) + { + FakeEmulator emu = new FakeEmulator(); + FakeMovieSession session = new(emu); + Bk2Movie movie = new(session, "/fake/path"); + session.Movie = movie; + + movie.Attach(emu); + for (int i = 0; i < numberOfFrames; i++) + movie.RecordFrame(i, session.MovieController); + + return movie; + } + + public static void SetSingleActiveInput(IMovie movie, string button) + { + ControllerDefinition activeInputs = new("single"); + activeInputs.BoolButtons.Add(button); + movie.ActiveControllerInputs = activeInputs.MakeImmutable(); + } + + [TestMethod] + public void GetInputReturnsDifferentInstances() + { + // arrange + Bk2Movie movie = MakeMovie(2); + IMovieController controller = movie.Session.MovieController; + controller.SetBool("A", true); + movie.PokeFrame(0, controller); + + // act + IController frame0 = movie.GetInputState(0); + IController frame1 = movie.GetInputState(1); + + // assert + Assert.AreNotEqual(frame0, frame1); + Assert.IsTrue(frame0.IsPressed("A")); + Assert.IsFalse(frame1.IsPressed("A")); + } + + [TestMethod] + public void MultitrackRecordFrame() + { + // arrange + Bk2Movie movie = MakeMovie(2); + SetSingleActiveInput(movie, "A"); + IMovieController controller = movie.Session.MovieController; + controller.SetBool("A", true); + controller.SetBool("B", true); + + // act + movie.RecordFrame(0, controller); + + // assert + Assert.IsTrue(movie.GetInputState(0).IsPressed("A")); + Assert.IsFalse(movie.GetInputState(0).IsPressed("B")); + } + + [TestMethod] + public void MultitrackPokeFrame() + { + // arrange + Bk2Movie movie = MakeMovie(2); + SetSingleActiveInput(movie, "A"); + IMovieController controller = movie.Session.MovieController; + controller.SetBool("A", true); + controller.SetBool("B", true); + + // act + movie.PokeFrame(0, controller); + + // assert + Assert.IsTrue(movie.GetInputState(0).IsPressed("A")); + Assert.IsFalse(movie.GetInputState(0).IsPressed("B")); + } + + [TestMethod] + public void MultitrackAppendFrame() + { + // arrange + Bk2Movie movie = MakeMovie(2); + SetSingleActiveInput(movie, "A"); + IMovieController controller = movie.Session.MovieController; + controller.SetBool("A", true); + controller.SetBool("B", true); + + // act + movie.AppendFrame(controller); + + // assert + Assert.IsTrue(movie.GetInputState(2).IsPressed("A")); + Assert.IsFalse(movie.GetInputState(2).IsPressed("B")); + } + + [TestMethod] + public void MultitrackTruncateFrameWithoutLengthChange() + { + // arrange + Bk2Movie movie = MakeMovie(2); + IMovieController controller = movie.Session.MovieController; + controller.SetBool("A", true); + controller.SetBool("B", true); + movie.PokeFrame(1, controller); + + SetSingleActiveInput(movie, "A"); + + // act + movie.Truncate(1); + + // assert + Assert.IsFalse(movie.GetInputState(1).IsPressed("A")); + Assert.IsTrue(movie.GetInputState(1).IsPressed("B")); + Assert.AreEqual(2, movie.FrameCount); + } + + [TestMethod] + public void MultitrackTruncateFrameWithLengthChange() + { + // arrange + Bk2Movie movie = MakeMovie(2); + SetSingleActiveInput(movie, "A"); + + // act + movie.Truncate(1); + + // assert + Assert.AreEqual(1, movie.FrameCount); + } + } +} diff --git a/src/BizHawk.Tests.Client.Common/Movie/MovieUndoTests.cs b/src/BizHawk.Tests.Client.Common/Movie/MovieUndoTests.cs index bd142829c13..0e3a242035d 100644 --- a/src/BizHawk.Tests.Client.Common/Movie/MovieUndoTests.cs +++ b/src/BizHawk.Tests.Client.Common/Movie/MovieUndoTests.cs @@ -97,11 +97,14 @@ public void SetAxis() } [TestMethod] - public void InsertFrame() + [DataRow(true)] + [DataRow(false)] + public void InsertFrame(bool useMultitrack) { ITasMovie movie = TasMovieTests.MakeMovie(5); movie.SetBoolState(2, "A", true); movie.SetBoolState(3, "B", true); + if (useMultitrack) Bk2MovieTests.SetSingleActiveInput(movie, "B"); ValidateActionCanUndoAndRedo(movie, () => { @@ -110,11 +113,14 @@ public void InsertFrame() } [TestMethod] - public void DeleteFrame() + [DataRow(true)] + [DataRow(false)] + public void DeleteFrame(bool useMultitrack) { ITasMovie movie = TasMovieTests.MakeMovie(5); movie.SetBoolState(2, "A", true); movie.SetBoolState(4, "B", true); + if (useMultitrack) Bk2MovieTests.SetSingleActiveInput(movie, "B"); ValidateActionCanUndoAndRedo(movie, () => { @@ -123,11 +129,14 @@ public void DeleteFrame() } [TestMethod] - public void DeleteFrames() + [DataRow(true)] + [DataRow(false)] + public void DeleteFrames(bool useMultitrack) { ITasMovie movie = TasMovieTests.MakeMovie(10); movie.SetBoolState(2, "A", true); movie.SetBoolState(4, "B", true); + if (useMultitrack) Bk2MovieTests.SetSingleActiveInput(movie, "B"); // both overloads ValidateActionCanUndoAndRedo(movie, () => @@ -143,11 +152,14 @@ public void DeleteFrames() } [TestMethod] - public void CloneFrame() + [DataRow(true)] + [DataRow(false)] + public void CloneFrame(bool useMultitrack) { ITasMovie movie = TasMovieTests.MakeMovie(5); movie.SetBoolState(2, "A", true); movie.SetBoolState(3, "B", true); + if (useMultitrack) Bk2MovieTests.SetSingleActiveInput(movie, "B"); ValidateActionCanUndoAndRedo(movie, () => { @@ -182,9 +194,12 @@ public void BatchedEdit() } [TestMethod] - public void RecordFrameAtEnd() + [DataRow(true)] + [DataRow(false)] + public void RecordFrameAtEnd(bool useMultitrack) { ITasMovie movie = TasMovieTests.MakeMovie(5); + if (useMultitrack) Bk2MovieTests.SetSingleActiveInput(movie, "A"); ValidateActionCanUndoAndRedo(movie, () => { @@ -195,9 +210,12 @@ public void RecordFrameAtEnd() } [TestMethod] - public void RecordFrameInMiddle() + [DataRow(true)] + [DataRow(false)] + public void RecordFrameInMiddle(bool useMultitrack) { ITasMovie movie = TasMovieTests.MakeMovie(5); + if (useMultitrack) Bk2MovieTests.SetSingleActiveInput(movie, "A"); ValidateActionCanUndoAndRedo(movie, () => { @@ -296,7 +314,7 @@ public void AllOperationsRespectBatching() }, () => { - movie.SetFrame(0, entryA); + movie.PokeFrame(0, entryA); movie.ChangeLog.EndBatch(); #pragma warning disable BHI1600 //TODO disambiguate assert calls diff --git a/src/BizHawk.Tests.Client.Common/Movie/TasMovieTests.cs b/src/BizHawk.Tests.Client.Common/Movie/TasMovieTests.cs index 94c95e9d62e..52ea6d96e12 100644 --- a/src/BizHawk.Tests.Client.Common/Movie/TasMovieTests.cs +++ b/src/BizHawk.Tests.Client.Common/Movie/TasMovieTests.cs @@ -1,4 +1,5 @@ -using BizHawk.Client.Common; +using System.Linq; +using BizHawk.Client.Common; namespace BizHawk.Tests.Client.Common.Movie { @@ -40,7 +41,7 @@ void TestForSingleOperation(Action a) TestForSingleOperation(() => movie.RecordFrame(1, controllerA)); TestForSingleOperation(() => movie.Truncate(3)); TestForSingleOperation(() => movie.PokeFrame(1, controllerA)); - TestForSingleOperation(() => movie.SetFrame(1, entryA)); + TestForSingleOperation(() => movie.PokeFrame(1, entryA)); TestForSingleOperation(() => movie.ClearFrame(3)); TestForSingleOperation(() => movie.InsertInput(2, entryA)); @@ -95,5 +96,172 @@ public void AllOperationsProduceSingleInvalidation() ); } #pragma warning restore BHI1600 + + [TestMethod] + public void CanTruncateLastFrame() + { + // arrange + ITasMovie movie = MakeMovie(2); + + // act + movie.Truncate(1); + + // assert + Assert.AreEqual(1, movie.InputLogLength); + } + + [TestMethod] + public void MultitrackClearFrame() + { + // arrange + ITasMovie movie = MakeMovie(2); + IMovieController controller = movie.Session.MovieController; + controller.SetBool("A", true); + controller.SetBool("B", true); + movie.PokeFrame(0, controller); + Bk2MovieTests.SetSingleActiveInput(movie, "A"); + + // act + movie.ClearFrame(0); + + // assert + Assert.IsFalse(movie.GetInputState(0).IsPressed("A")); + Assert.IsTrue(movie.GetInputState(0).IsPressed("B")); + } + + [TestMethod] + public void MultitrackRemoveFrame() + { + // arrange + ITasMovie movie = MakeMovie(2); + IMovieController controller = movie.Session.MovieController; + controller.SetBool("A", true); + controller.SetBool("B", true); + movie.PokeFrame(0, controller); + Bk2MovieTests.SetSingleActiveInput(movie, "A"); + + // act + movie.RemoveFrame(0); + + // assert + Assert.IsFalse(movie.GetInputState(0).IsPressed("A")); + Assert.IsTrue(movie.GetInputState(0).IsPressed("B")); + Assert.AreEqual(2, movie.InputLogLength); // with multi-track editing, the movie length is not expected to change + } + + [TestMethod] + public void MultitrackRemoveFrames() + { + // arrange + ITasMovie movie = MakeMovie(2); + IMovieController controller = movie.Session.MovieController; + controller.SetBool("A", true); + controller.SetBool("B", true); + movie.PokeFrame(0, controller); + Bk2MovieTests.SetSingleActiveInput(movie, "A"); + + // act + movie.RemoveFrames(0, 1); + + // assert + Assert.IsFalse(movie.GetInputState(0).IsPressed("A")); + Assert.IsTrue(movie.GetInputState(0).IsPressed("B")); + Assert.AreEqual(2, movie.InputLogLength); // with multi-track editing, the movie length is not expected to change + } + + [TestMethod] + public void MultitrackInsertInput() + { + // arrange + ITasMovie movie = MakeMovie(2); + Bk2MovieTests.SetSingleActiveInput(movie, "A"); + IMovieController controller = movie.Session.MovieController; + controller.SetBool("A", true); + controller.SetBool("B", true); + + // act + movie.InsertInput(0, Enumerable.Repeat(controller, 1)); + + // assert + Assert.IsTrue(movie.GetInputState(0).IsPressed("A")); + Assert.IsFalse(movie.GetInputState(0).IsPressed("B")); + Assert.AreEqual(3, movie.InputLogLength); + } + + [TestMethod] + public void MultitrackInsertInputsByString() + { + // arrange + ITasMovie movie = MakeMovie(2); + Bk2MovieTests.SetSingleActiveInput(movie, "A"); + IMovieController controller = movie.Session.MovieController; + controller.SetBool("A", true); + controller.SetBool("B", true); + + // act + movie.InsertInput(0, Enumerable.Repeat(Bk2LogEntryGenerator.GenerateLogEntry(controller), 1)); + + // assert + Assert.IsTrue(movie.GetInputState(0).IsPressed("A")); + Assert.IsFalse(movie.GetInputState(0).IsPressed("B")); + Assert.AreEqual(3, movie.InputLogLength); + } + + [TestMethod] + public void MultitrackInsertInputByString() + { + // arrange + ITasMovie movie = MakeMovie(2); + Bk2MovieTests.SetSingleActiveInput(movie, "A"); + IMovieController controller = movie.Session.MovieController; + controller.SetBool("A", true); + controller.SetBool("B", true); + + // act + movie.InsertInput(0, Bk2LogEntryGenerator.GenerateLogEntry(controller)); + + // assert + Assert.IsTrue(movie.GetInputState(0).IsPressed("A")); + Assert.IsFalse(movie.GetInputState(0).IsPressed("B")); + Assert.AreEqual(3, movie.InputLogLength); + } + + [TestMethod] + public void MultitrackInsertFrame() + { + // arrange + ITasMovie movie = MakeMovie(2); + IMovieController controller = movie.Session.MovieController; + controller.SetBool("A", true); + controller.SetBool("B", true); + movie.PokeFrame(0, controller); + Bk2MovieTests.SetSingleActiveInput(movie, "A"); + + // act + movie.InsertEmptyFrame(0); + + // assert + Assert.IsFalse(movie.GetInputState(0).IsPressed("A")); + Assert.IsTrue(movie.GetInputState(0).IsPressed("B")); + Assert.AreEqual(3, movie.InputLogLength); + } + + [TestMethod] + public void MultitrackCopyOverInput() + { + // arrange + ITasMovie movie = MakeMovie(2); + Bk2MovieTests.SetSingleActiveInput(movie, "A"); + IMovieController controller = movie.Session.MovieController; + controller.SetBool("A", true); + controller.SetBool("B", true); + + // act + movie.CopyOverInput(0, Enumerable.Repeat(controller, 1)); + + // assert + Assert.IsTrue(movie.GetInputState(0).IsPressed("A")); + Assert.IsFalse(movie.GetInputState(0).IsPressed("B")); + } } }