diff --git a/src/BizHawk.Client.Common/config/Binding.cs b/src/BizHawk.Client.Common/config/Binding.cs index 7989f8fcd2..a3c378d17d 100644 --- a/src/BizHawk.Client.Common/config/Binding.cs +++ b/src/BizHawk.Client.Common/config/Binding.cs @@ -133,6 +133,7 @@ namespace BizHawk.Client.Common Bind("TAStudio", "Show Cursor"); Bind("TAStudio", "Toggle Follow Cursor", "Shift+F"); Bind("TAStudio", "Toggle Auto-Restore", "Shift+R"); + Bind("TAStudio", "Seek To Green Arrow", "R"); Bind("TAStudio", "Toggle Turbo Seek", "Shift+S"); Bind("TAStudio", "Undo", "Ctrl+Z"); // TODO: these are getting not unique enough Bind("TAStudio", "Redo", "Ctrl+Y"); diff --git a/src/BizHawk.Client.Common/lua/CommonLibs/ClientLuaLibrary.cs b/src/BizHawk.Client.Common/lua/CommonLibs/ClientLuaLibrary.cs index c525292d55..88fe584b88 100644 --- a/src/BizHawk.Client.Common/lua/CommonLibs/ClientLuaLibrary.cs +++ b/src/BizHawk.Client.Common/lua/CommonLibs/ClientLuaLibrary.cs @@ -102,37 +102,11 @@ namespace BizHawk.Client.Common public void InvisibleEmulation(bool invisible) => APIs.EmuClient.InvisibleEmulation(invisible); - [LuaMethodExample("client.seekframe( 100 );")] - [LuaMethod("seekframe", "Makes the emulator seek to the frame specified")] + [LuaDeprecatedMethod] + [LuaMethod("seekframe", "Does nothing. Use the pause/unpause functions instead and a loop that waits for the desired frame.")] public void SeekFrame(int frame) { - if (_luaLibsImpl.IsInInputOrMemoryCallback) - { - throw new InvalidOperationException("client.seekframe() is not allowed during input/memory callbacks"); - } - - if (frame < Emulator.Frame) - { - Log("client.seekframe: cannot seek backwards"); - return; - } - if (frame == Emulator.Frame) return; - - bool wasPaused = MainForm.EmulatorPaused; - - // can't re-enter lua while doing this - _luaLibsImpl.IsUpdateSupressed = true; - while (Emulator.Frame != frame) - { - MainForm.SeekFrameAdvance(); - } - - _luaLibsImpl.IsUpdateSupressed = false; - - if (!wasPaused) - { - MainForm.UnpauseEmulator(); - } + Log("Deprecated function client.seekframe() used. Replace the call with pause/unpause functions and a loop that waits for the desired frame."); } [LuaMethodExample("local sounds_terrible = client.get_approx_framerate() < 55;")] diff --git a/src/BizHawk.Client.Common/movie/interfaces/ITasMovie.cs b/src/BizHawk.Client.Common/movie/interfaces/ITasMovie.cs index 0752930020..ab9ab063ba 100644 --- a/src/BizHawk.Client.Common/movie/interfaces/ITasMovie.cs +++ b/src/BizHawk.Client.Common/movie/interfaces/ITasMovie.cs @@ -7,7 +7,6 @@ namespace BizHawk.Client.Common public interface ITasMovie : IMovie, INotifyPropertyChanged, IDisposable { bool BindMarkersToInput { get; set; } - bool LastPositionStable { get; set; } IMovieChangeLog ChangeLog { get; } IStateManager TasStateManager { get; } @@ -20,6 +19,7 @@ namespace BizHawk.Client.Common TasLagLog LagLog { get; } IStringLog VerificationLog { get; } int LastEditedFrame { get; } + bool LastEditWasRecording { get; } Action GreenzoneInvalidated { get; set; } diff --git a/src/BizHawk.Client.Common/movie/tasproj/TasMovie.Editing.cs b/src/BizHawk.Client.Common/movie/tasproj/TasMovie.Editing.cs index 9b72f80127..e9f41a70d2 100644 --- a/src/BizHawk.Client.Common/movie/tasproj/TasMovie.Editing.cs +++ b/src/BizHawk.Client.Common/movie/tasproj/TasMovie.Editing.cs @@ -27,6 +27,7 @@ namespace BizHawk.Client.Common if (this.IsRecording()) { + LastEditWasRecording = true; InvalidateAfter(frame); } @@ -82,9 +83,12 @@ namespace BizHawk.Client.Common 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, Bk2LogEntryGenerator.EmptyEntry(Session.MovieController)); + SetFrameAt(frame, empty); Changes = true; InvalidateAfter(frame); @@ -211,6 +215,7 @@ namespace BizHawk.Client.Common if (Log.Count < states.Count + frame) { + firstChangedFrame = Log.Count; ExtendMovieForEdit(states.Count + frame - Log.Count); } @@ -224,7 +229,7 @@ namespace BizHawk.Client.Common } var entry = Bk2LogEntryGenerator.GenerateLogEntry(states[i]); - if (firstChangedFrame == -1 && Log[frame + i] != entry) + if ((firstChangedFrame == -1 || firstChangedFrame > frame + i) && Log[frame + i] != entry) { firstChangedFrame = frame + i; } @@ -234,7 +239,11 @@ namespace BizHawk.Client.Common ChangeLog.EndBatch(); Changes = true; - InvalidateAfter(frame); + if (firstChangedFrame != -1) + { + // TODO: Throw out the undo action if there are no changes. + InvalidateAfter(firstChangedFrame); + } ChangeLog.SetGeneralRedo(); return firstChangedFrame; diff --git a/src/BizHawk.Client.Common/movie/tasproj/TasMovie.cs b/src/BizHawk.Client.Common/movie/tasproj/TasMovie.cs index 9fb2bb5dac..b0bf0a1268 100644 --- a/src/BizHawk.Client.Common/movie/tasproj/TasMovie.cs +++ b/src/BizHawk.Client.Common/movie/tasproj/TasMovie.cs @@ -73,6 +73,7 @@ namespace BizHawk.Client.Common public ITasSession TasSession { get; private set; } = new TasSession(); public int LastEditedFrame { get; private set; } = -1; + public bool LastEditWasRecording { get; private set; } public bool LastPositionStable { get; set; } = true; public TasMovieMarkerList Markers { get; private set; } public bool BindMarkersToInput { get; set; } @@ -129,6 +130,7 @@ namespace BizHawk.Client.Common } LastEditedFrame = frame; + LastEditWasRecording = false; // We can set it here; it's only used in the GreenzoneInvalidated action. if (anyStateInvalidated && IsCountingRerecords) { @@ -178,14 +180,6 @@ namespace BizHawk.Client.Common public void GreenzoneCurrentFrame() { - // todo: this isn't working quite right when autorestore is off and we're editing while seeking - // but accounting for that requires access to Mainform.IsSeeking - if (Emulator.Frame != LastEditedFrame) - { - // emulated a new frame, current editing segment may change now. taseditor logic - LastPositionStable = false; - } - LagLog[Emulator.Frame] = _inputPollable.IsLagFrame; // We will forbibly capture a state for the last edited frame (requested by #916 for case of "platforms with analog stick") diff --git a/src/BizHawk.Client.EmuHawk/MainForm.Hotkey.cs b/src/BizHawk.Client.EmuHawk/MainForm.Hotkey.cs index 2ca37938bd..86c5da113c 100644 --- a/src/BizHawk.Client.EmuHawk/MainForm.Hotkey.cs +++ b/src/BizHawk.Client.EmuHawk/MainForm.Hotkey.cs @@ -378,6 +378,10 @@ namespace BizHawk.Client.EmuHawk var playbackBox1 = Tools.TAStudio.TasPlaybackBox; playbackBox1.AutoRestore = !playbackBox1.AutoRestore; break; + case "Seek To Green Arrow": + if (!Tools.IsLoaded()) return false; + Tools.TAStudio.RestorePosition(); + break; case "Toggle Turbo Seek": if (!Tools.IsLoaded()) return false; var playbackBox2 = Tools.TAStudio.TasPlaybackBox; diff --git a/src/BizHawk.Client.EmuHawk/MainForm.cs b/src/BizHawk.Client.EmuHawk/MainForm.cs index 4baa981fdd..4250a9ce0c 100644 --- a/src/BizHawk.Client.EmuHawk/MainForm.cs +++ b/src/BizHawk.Client.EmuHawk/MainForm.cs @@ -1073,7 +1073,6 @@ namespace BizHawk.Client.EmuHawk /// Accessing this from Lua allows to keep internal code hacks to minimum. /// /// - /// /// /// public bool InvisibleEmulation { get; set; } @@ -3258,16 +3257,7 @@ namespace BizHawk.Client.EmuHawk if (PauseOnFrame.Value == Emulator.Frame) { PauseEmulator(); - if (Tools.IsLoaded()) Tools.TAStudio.StopSeeking(); - else PauseOnFrame = null; - } - else if (Tools.IsLoaded() - && Tools.TAStudio.LastPositionFrame == Emulator.Frame - && ((ITasMovie) MovieSession.Movie)[Emulator.Frame].Lagged is null) - { - // haven't yet greenzoned the frame, hence it's after editing - // then we want to pause here. taseditor fashion - PauseEmulator(); + PauseOnFrame = null; } } diff --git a/src/BizHawk.Client.EmuHawk/config/HotkeyConfig.Designer.cs b/src/BizHawk.Client.EmuHawk/config/HotkeyConfig.Designer.cs index a7e520b520..d44d4fbc3e 100644 --- a/src/BizHawk.Client.EmuHawk/config/HotkeyConfig.Designer.cs +++ b/src/BizHawk.Client.EmuHawk/config/HotkeyConfig.Designer.cs @@ -62,7 +62,7 @@ // this.AutoTabCheckBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.AutoTabCheckBox.AutoSize = true; - this.AutoTabCheckBox.Location = new System.Drawing.Point(432, 440); + this.AutoTabCheckBox.Location = new System.Drawing.Point(462, 440); this.AutoTabCheckBox.Name = "AutoTabCheckBox"; this.AutoTabCheckBox.Size = new System.Drawing.Size(70, 17); this.AutoTabCheckBox.TabIndex = 101; @@ -79,7 +79,7 @@ this.HotkeyTabControl.Location = new System.Drawing.Point(12, 28); this.HotkeyTabControl.Name = "HotkeyTabControl"; this.HotkeyTabControl.SelectedIndex = 0; - this.HotkeyTabControl.Size = new System.Drawing.Size(729, 396); + this.HotkeyTabControl.Size = new System.Drawing.Size(759, 396); this.HotkeyTabControl.TabIndex = 102; this.HotkeyTabControl.SelectedIndexChanged += new System.EventHandler(this.HotkeyTabControl_SelectedIndexChanged); // @@ -88,7 +88,7 @@ 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(721, 370); + this.tabPage1.Size = new System.Drawing.Size(751, 370); this.tabPage1.TabIndex = 0; this.tabPage1.Text = "For designer"; this.tabPage1.UseVisualStyleBackColor = true; @@ -97,7 +97,7 @@ // this.IDB_CANCEL.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.IDB_CANCEL.DialogResult = System.Windows.Forms.DialogResult.Cancel; - this.IDB_CANCEL.Location = new System.Drawing.Point(681, 436); + this.IDB_CANCEL.Location = new System.Drawing.Point(711, 436); this.IDB_CANCEL.Name = "IDB_CANCEL"; this.IDB_CANCEL.Size = new System.Drawing.Size(60, 22); this.IDB_CANCEL.TabIndex = 103; @@ -109,7 +109,7 @@ // IDB_SAVE // this.IDB_SAVE.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.IDB_SAVE.Location = new System.Drawing.Point(615, 436); + this.IDB_SAVE.Location = new System.Drawing.Point(645, 436); this.IDB_SAVE.Name = "IDB_SAVE"; this.IDB_SAVE.Size = new System.Drawing.Size(60, 22); this.IDB_SAVE.TabIndex = 104; @@ -123,7 +123,7 @@ this.SearchBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); this.SearchBox.AutoCompleteMode = System.Windows.Forms.AutoCompleteMode.SuggestAppend; - this.SearchBox.Location = new System.Drawing.Point(592, 9); + this.SearchBox.Location = new System.Drawing.Point(622, 9); this.SearchBox.Name = "SearchBox"; this.SearchBox.Size = new System.Drawing.Size(149, 20); this.SearchBox.TabIndex = 106; @@ -131,7 +131,7 @@ // // label1 // - this.label1.Location = new System.Drawing.Point(556, 12); + this.label1.Location = new System.Drawing.Point(586, 12); this.label1.Name = "label1"; this.label1.Text = "Find:"; // @@ -152,7 +152,7 @@ // MiscButton // this.MiscButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.MiscButton.Location = new System.Drawing.Point(526, 436); + this.MiscButton.Location = new System.Drawing.Point(556, 436); this.MiscButton.Menu = this.clearBtnContextMenu; this.MiscButton.Name = "MiscButton"; this.MiscButton.Size = new System.Drawing.Size(60, 22); @@ -198,7 +198,7 @@ this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.CancelButton = this.IDB_CANCEL; - this.ClientSize = new System.Drawing.Size(753, 463); + this.ClientSize = new System.Drawing.Size(783, 463); this.Controls.Add(this.MiscButton); this.Controls.Add(this.label3); this.Controls.Add(this.label2); diff --git a/src/BizHawk.Client.EmuHawk/config/HotkeyConfig.cs b/src/BizHawk.Client.EmuHawk/config/HotkeyConfig.cs index 05cb11b014..98838b41e4 100644 --- a/src/BizHawk.Client.EmuHawk/config/HotkeyConfig.cs +++ b/src/BizHawk.Client.EmuHawk/config/HotkeyConfig.cs @@ -95,7 +95,7 @@ namespace BizHawk.Client.EmuHawk .OrderBy(static kvp => kvp.Value.Ordinal).ThenBy(static kvp => kvp.Value.DisplayName); int x = UIHelper.ScaleX(6); int y = UIHelper.ScaleY(14); - int iwOffsetX = UIHelper.ScaleX(110); + int iwOffsetX = UIHelper.ScaleX(120); int iwOffsetY = UIHelper.ScaleY(-4); int iwWidth = UIHelper.ScaleX(120); diff --git a/src/BizHawk.Client.EmuHawk/tools/Lua/Libraries/TAStudioLuaLibrary.cs b/src/BizHawk.Client.EmuHawk/tools/Lua/Libraries/TAStudioLuaLibrary.cs index 1593a24edb..7b844ed783 100644 --- a/src/BizHawk.Client.EmuHawk/tools/Lua/Libraries/TAStudioLuaLibrary.cs +++ b/src/BizHawk.Client.EmuHawk/tools/Lua/Libraries/TAStudioLuaLibrary.cs @@ -143,30 +143,30 @@ namespace BizHawk.Client.EmuHawk throw new InvalidOperationException("tastudio.setplayback() is not allowed during input/memory callbacks"); } - _luaLibsImpl.IsUpdateSupressed = true; int f; if (frame is long frameNumber) { f = (int)frameNumber; } + else if (frame is double frameNumber2) + { + f = (int)frameNumber2; + } else { - f = Tastudio.CurrentTasMovie.Markers.FindIndex((string)frame); - if (f == -1) - { - return; - } + int markerIndex = Tastudio.CurrentTasMovie.Markers.FindIndex((string)frame); + if (markerIndex == -1) return; - f = Tastudio.CurrentTasMovie.Markers[f].Frame; + f = Tastudio.CurrentTasMovie.Markers[markerIndex].Frame; } - if (0.RangeToExclusive(Tastudio.CurrentTasMovie.InputLogLength).Contains(f)) + if (f >= 0) { - Tastudio.GoToFrame(f, true); + _luaLibsImpl.IsUpdateSupressed = true; + Tastudio.GoToFrame(f); + _luaLibsImpl.IsUpdateSupressed = false; } - - _luaLibsImpl.IsUpdateSupressed = false; } } @@ -300,6 +300,11 @@ namespace BizHawk.Client.EmuHawk [LuaMethod("applyinputchanges", "")] public void ApplyInputChanges() { + if (_changeList.Count == 0) + { + return; + } + if (Engaged()) { if (_luaLibsImpl.IsInInputOrMemoryCallback) @@ -309,7 +314,7 @@ namespace BizHawk.Client.EmuHawk _luaLibsImpl.IsUpdateSupressed = true; - if (_changeList.Count > 0) + Tastudio.ApiHawkBatchEdit(() => { int size = _changeList.Count; @@ -327,7 +332,6 @@ namespace BizHawk.Client.EmuHawk Tastudio.CurrentTasMovie.SetAxisState(_changeList[i].Frame, _changeList[i].Button, _changeList[i].ValueAxis); break; } - Tastudio.RefreshForInputChange(_changeList[i].Frame); break; case LuaChangeTypes.InsertFrames: Tastudio.InsertNumFrames(_changeList[i].Frame, _changeList[i].Number); @@ -341,9 +345,7 @@ namespace BizHawk.Client.EmuHawk } } _changeList.Clear(); - Tastudio.JumpToGreenzone(); - Tastudio.DoAutoRestore(); - } + }); _luaLibsImpl.IsUpdateSupressed = false; } diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/BookmarksBranchesBox.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/BookmarksBranchesBox.cs index 17ad218b94..6085159feb 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/BookmarksBranchesBox.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/BookmarksBranchesBox.cs @@ -177,32 +177,11 @@ namespace BizHawk.Client.EmuHawk }; } - private void LoadBranch(TasBranch branch) - { - if (Tastudio.Settings.OldControlSchemeForBranches && !Tastudio.TasPlaybackBox.RecordingMode) - { - JumpToBranchToolStripMenuItem_Click(null, null); - return; - } - - Movie.LoadBranch(branch); - Tastudio.LoadState(new(branch.Frame, new MemoryStream(branch.CoreData, false))); - - Movie.TasStateManager.Capture(Tastudio.Emulator.Frame, Tastudio.Emulator.AsStatable()); - QuickBmpFile.Copy(new BitmapBufferVideoProvider(branch.CoreFrameBuffer), Tastudio.VideoProvider); - - if (Tastudio.Settings.OldControlSchemeForBranches && Tastudio.TasPlaybackBox.RecordingMode) - Movie.Truncate(branch.Frame); - - MainForm.PauseOnFrame = null; - Tastudio.RefreshDialog(); - } - private bool LoadSelectedBranch() { if (SelectedBranch == null) return false; Branches.Current = BranchView.FirstSelectedRowIndex; - LoadBranch(SelectedBranch); + Tastudio.LoadBranch(SelectedBranch); BranchView.Refresh(); Tastudio.MainForm.AddOnScreenMessage($"Loaded branch {Branches.Current + 1}"); return true; @@ -352,7 +331,7 @@ namespace BizHawk.Client.EmuHawk { if (_branchUndo == BranchUndo.Load) { - LoadBranch(_backupBranch); + Tastudio.LoadBranch(_backupBranch); Tastudio.BranchLoadedCallback?.Invoke(Branches.IndexOf(_backupBranch)); Tastudio.MainForm.AddOnScreenMessage("Branch Load canceled"); } diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Callbacks.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Callbacks.cs index a8de69c7d0..8c41e3972d 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Callbacks.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Callbacks.cs @@ -13,10 +13,5 @@ namespace BizHawk.Client.EmuHawk public Action BranchLoadedCallback { get; set; } public Action BranchSavedCallback { get; set; } public Action BranchRemovedCallback { get; set; } - - private void GreenzoneInvalidated(int index) - { - GreenzoneInvalidatedCallback?.Invoke(index); - } } } diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Designer.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Designer.cs index f565d8b4d6..657f8edf57 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Designer.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Designer.cs @@ -1180,6 +1180,7 @@ namespace BizHawk.Client.EmuHawk this.MinimumSize = new System.Drawing.Size(200, 148); this.Name = "TAStudio"; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Deactivate += new System.EventHandler(this.TAStudio_Deactivate); this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.Tastudio_Closing); this.Load += new System.EventHandler(this.Tastudio_Load); this.DragDrop += new System.Windows.Forms.DragEventHandler(this.TAStudio_DragDrop); diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IControlMainForm.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IControlMainForm.cs index f8860f73d9..272afddc2b 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IControlMainForm.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IControlMainForm.cs @@ -78,27 +78,7 @@ public bool Rewind() { int rewindStep = MainForm.IsFastForwarding ? Settings.RewindStepFast : Settings.RewindStep; - // copy pasted from TasView_MouseWheel(), just without notch logic - if (MainForm.IsSeeking && !MainForm.EmulatorPaused) - { - MainForm.PauseOnFrame -= rewindStep; - - // that's a weird condition here, but for whatever reason it works best - if (Emulator.Frame >= MainForm.PauseOnFrame) - { - MainForm.PauseEmulator(); - StopSeeking(); - GoToFrame(Math.Max(0, Emulator.Frame - rewindStep)); - } - - RefreshDialog(); - } - else - { - StopSeeking(); // late breaking memo: don't know whether this is needed - GoToFrame(Math.Max(0, Emulator.Frame - rewindStep)); - } - + WheelSeek(rewindStep); return true; } diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IToolForm.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IToolForm.cs index 4cdad461d6..e5057f76f5 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IToolForm.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IToolForm.cs @@ -24,10 +24,10 @@ namespace BizHawk.Client.EmuHawk private void UpdateProgressBar() { - if (MainForm.PauseOnFrame.HasValue) + if (_seekingTo != -1) { - int diff = Emulator.Frame - _seekStartFrame.Value; - int unit = MainForm.PauseOnFrame.Value - _seekStartFrame.Value; + int diff = Emulator.Frame - _seekStartFrame; + int unit = _seekingTo - _seekStartFrame; double progress = 0; if (diff != 0 && unit != 0) @@ -94,18 +94,27 @@ namespace BizHawk.Client.EmuHawk refreshNeeded = true; } - if (Settings.AutoPause) + if (Settings.AutoPause && _seekingTo == -1) { if (_doPause && CurrentTasMovie.IsAtEnd()) MainForm.PauseEmulator(); _doPause = !CurrentTasMovie.IsAtEnd(); } + if (!_seekingByEdit) + { + _shouldMoveGreenArrow = true; + } + + FastUpdateAfter(); RefreshDialog(refreshNeeded, refreshBranches: false); - UpdateProgressBar(); } protected override void FastUpdateAfter() { + if (_seekingTo != -1 && Emulator.Frame >= _seekingTo) + { + StopSeeking(); + } UpdateProgressBar(); } diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs index 361bc158fc..cd4c267532 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs @@ -16,7 +16,6 @@ namespace BizHawk.Client.EmuHawk // Input Painting private string _startBoolDrawColumn = ""; private string _startAxisDrawColumn = ""; - private bool _drewAxis; private bool _boolPaintState; private int _axisPaintState; private int _axisBackupState; @@ -26,7 +25,9 @@ namespace BizHawk.Client.EmuHawk private bool _selectionDragState; private bool _suppressContextMenu; private int _startRow; - private int _paintingMinFrame = -1; + private int _batchEditMinFrame = -1; + private bool _batchEditing; + private bool _editIsFromLua; // Editing analog input private string _axisEditColumn = ""; @@ -56,10 +57,8 @@ namespace BizHawk.Client.EmuHawk private bool MouseButtonHeld => _rightClickFrame != -1 || _leftButtonHeld; - private bool _triggerAutoRestore; // If true, autorestore will be called on mouse up - private bool? _autoRestorePaused; - private int? _seekStartFrame; - private bool _unpauseAfterSeeking; + private int _seekStartFrame; + private bool _pauseAfterSeeking; private readonly Dictionary _alternateRowColor = new(); @@ -69,38 +68,21 @@ namespace BizHawk.Client.EmuHawk public AutoPatternBool[] BoolPatterns; public AutoPatternAxis[] AxisPatterns; - public void JumpToGreenzone(bool OnLeftMouseDown = false) + private void StartSeeking(int frame) { - if (Emulator.Frame > CurrentTasMovie.LastEditedFrame) - { - GoToLastEmulatedFrameIfNecessary(CurrentTasMovie.LastEditedFrame, OnLeftMouseDown); - } - } - - private void StartSeeking(int? frame, bool fromMiddleClick = false) - { - if (!frame.HasValue) + if (frame <= Emulator.Frame) { return; } - if (!fromMiddleClick) - { - if (MainForm.PauseOnFrame != null) - { - StopSeeking(true); // don't restore rec mode just yet, as with heavy editing checkbox updating causes lag - } - _seekStartFrame = Emulator.Frame; - } + _seekStartFrame = Emulator.Frame; + _seekingByEdit = false; - MainForm.PauseOnFrame = frame.Value; - int? diff = MainForm.PauseOnFrame - _seekStartFrame; - - WasRecording = CurrentTasMovie.IsRecording() || WasRecording; - TastudioPlayMode(); // suspend rec mode until seek ends, to allow mouse editing + _seekingTo = frame; + MainForm.PauseOnFrame = int.MaxValue; // This being set is how MainForm knows we are seeking, and controls TurboSeek. MainForm.UnpauseEmulator(); - if (diff > TasView.VisibleRows) + if (_seekingTo - _seekStartFrame > 1) { MessageStatusLabel.Text = "Seeking..."; ProgressBar.Visible = true; @@ -115,11 +97,12 @@ namespace BizHawk.Client.EmuHawk WasRecording = false; } - MainForm.PauseOnFrame = null; - if (_unpauseAfterSeeking) + _seekingByEdit = false; + _seekingTo = -1; + MainForm.PauseOnFrame = null; // This being unset is how MainForm knows we are not seeking, and controls TurboSeek. + if (_pauseAfterSeeking) { - MainForm.UnpauseEmulator(); - _unpauseAfterSeeking = false; + MainForm.PauseEmulator(); } if (CurrentTasMovie != null) @@ -129,6 +112,23 @@ namespace BizHawk.Client.EmuHawk } } + private void CancelSeek() + { + _shouldMoveGreenArrow = true; + _seekingByEdit = false; + _seekingTo = -1; + MainForm.PauseOnFrame = null; // This being unset is how MainForm knows we are not seeking, and controls TurboSeek. + _pauseAfterSeeking = false; + if (WasRecording) + { + TastudioRecordMode(); + WasRecording = false; + } + + RefreshDialog(); + UpdateProgressBar(); + } + private Bitmap ts_v_arrow_green_blue => Properties.Resources.ts_v_arrow_green_blue; private Bitmap ts_h_arrow_green_blue => Properties.Resources.ts_h_arrow_green_blue; private Bitmap ts_v_arrow_blue => Properties.Resources.ts_v_arrow_blue; @@ -167,11 +167,11 @@ namespace BizHawk.Client.EmuHawk if (index == Emulator.Frame) { - bitmap = index == MainForm.PauseOnFrame + 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; } - else if (index == LastPositionFrame) + else if (index == RestorePositionFrame) { bitmap = TasView.HorizontalOrientation ? ts_v_arrow_green : @@ -255,11 +255,11 @@ namespace BizHawk.Client.EmuHawk var record = CurrentTasMovie[index]; - if (MainForm.IsSeeking && MainForm.PauseOnFrame == index) + if (_seekingTo == index) { color = Palette.CurrentFrame_InputLog; } - else if (!MainForm.IsSeeking && Emulator.Frame == index) + else if (_seekingTo == -1 && Emulator.Frame == index) { color = Palette.CurrentFrame_InputLog; } @@ -378,6 +378,7 @@ namespace BizHawk.Client.EmuHawk if (columnName == FrameColumnName) { CurrentTasMovie.Markers.Add(TasView.SelectionEndIndex!.Value, ""); + RefreshDialog(); } else if (columnName != CursorColumnName) { @@ -415,13 +416,7 @@ namespace BizHawk.Client.EmuHawk // feos: there's no default value other than neutral, and we can't go arbitrary here, so do nothing for now // autohold is ignored for axes too for the same reasons: lack of demand + ambiguity } - - _triggerAutoRestore = true; - TastudioPlayMode(true); - JumpToGreenzone(); } - - RefreshDialog(); } } @@ -523,10 +518,16 @@ namespace BizHawk.Client.EmuHawk { if (MainForm.EmulatorPaused) { - var record = CurrentTasMovie[LastPositionFrame]; - if (!record.Lagged.HasValue && LastPositionFrame > Emulator.Frame) + if (_seekingTo != -1) { - StartSeeking(LastPositionFrame, true); + MainForm.UnpauseEmulator(); // resume seek + return; + } + + var record = CurrentTasMovie[RestorePositionFrame]; + if (record.Lagged is null) + { + RestorePosition(); return; } } @@ -543,7 +544,6 @@ namespace BizHawk.Client.EmuHawk if (e.Button == MouseButtons.Left) { _leftButtonHeld = true; - _paintingMinFrame = frame; // SuuperW: Exit axis editing mode, or re-enter mouse editing if (AxisEditingMode) @@ -575,8 +575,6 @@ namespace BizHawk.Client.EmuHawk _axisEditYPos = e.Y; _axisPaintState = CurrentTasMovie.GetAxisState(frame, buttonName); - _triggerAutoRestore = true; - TastudioPlayMode(true); return; } } @@ -584,7 +582,7 @@ namespace BizHawk.Client.EmuHawk if (targetCol.Name is CursorColumnName) { _startCursorDrag = true; - GoToFrame(frame, fromLua: false, fromRewinding: false, OnLeftMouseDown: true); + GoToFrame(frame, OnLeftMouseDown: true); } else if (targetCol.Name is FrameColumnName) { @@ -642,9 +640,6 @@ namespace BizHawk.Client.EmuHawk } CurrentTasMovie.SetBoolStates(firstSel, lastSel - firstSel + 1, buttonName, !allPressed); _boolPaintState = CurrentTasMovie.BoolIsPressed(lastSel, buttonName); - _triggerAutoRestore = true; - TastudioPlayMode(true); - RefreshDialog(); } #if false // to match previous behaviour else if (altOrShift4State is not 0) @@ -658,9 +653,6 @@ namespace BizHawk.Client.EmuHawk CurrentTasMovie.ToggleBoolState(frame, buttonName); _boolPaintState = CurrentTasMovie.BoolIsPressed(frame, buttonName); - _triggerAutoRestore = true; - TastudioPlayMode(true); - RefreshDialog(); } } else @@ -743,8 +735,6 @@ namespace BizHawk.Client.EmuHawk if (_rightClickAlt || _rightClickControl || _rightClickShift) { - JumpToGreenzone(); - // TODO: Turn off ChangeLog.IsRecording and handle the GeneralUndo here. string undoStepName = "Right-Click Edit:"; if (_rightClickShift) @@ -773,29 +763,135 @@ namespace BizHawk.Client.EmuHawk } } + /// + /// Begins a batch of edits, for auto-restore purposes. Auto-restore will be delayed until EndBatchEdit is called. + /// + private void BeginBatchEdit() + { + _batchEditing = true; + } + /// Returns true if the input list was redrawn. + private bool EndBatchEdit() + { + _batchEditing = false; + if (_batchEditMinFrame != -1) + { + return FrameEdited(_batchEditMinFrame); + } + + return false; + } + + public void ApiHawkBatchEdit(Action action) + { + // This is only caled from Lua. + _editIsFromLua = true; + BeginBatchEdit(); + try + { + action(); + } + finally + { + EndBatchEdit(); + _editIsFromLua = false; + } + } + + /// + /// Disables recording mode, ensures we are in the greenzone, and does autorestore if needed. + /// If a mouse button is down, only tracks the edit so we can do this stuff on mouse up. + /// + /// The frame that was just edited, or the earliest one if multiple were edited. + /// Returns true if the input list was redrawn. + public bool FrameEdited(int frame) + { + GreenzoneInvalidatedCallback?.Invoke(frame); // lua callback + + if (CurrentTasMovie.LastEditWasRecording) + { + return false; + } + + bool needsRefresh = !_batchEditing; + if (MouseButtonHeld || _batchEditing) + { + if (_batchEditMinFrame == -1) + { + _batchEditMinFrame = frame; + } + else + { + _batchEditMinFrame = Math.Min(_batchEditMinFrame, frame); + } + } + else + { + if (!_editIsFromLua) + { + // Lua users will want to preserve recording mode. + TastudioPlayMode(true); + } + + if (Emulator.Frame > frame) + { + if (_shouldMoveGreenArrow) + { + RestorePositionFrame = _seekingTo != -1 ? _seekingTo : Emulator.Frame; + // Green arrow should not move again until the user changes frame. + // This means any state load or unpause/frame advance/seek, that is not caused by an input edit. + // This is so that the user can make multiple edits with auto restore off, in any order, before a manual restore. + _shouldMoveGreenArrow = false; + } + + _seekingByEdit = true; // must be before GoToFrame (it will load a state, and state loading checks _seekingByEdit) + GoToFrame(frame); + if (Settings.AutoRestoreLastPosition) + { + RestorePosition(); + } + _seekingByEdit = true; // must be after GoToFrame & RestorePosition too (they'll set _seekingByEdit to false) + + needsRefresh = false; // Refresh will happen via GoToFrame. + } + _batchEditMinFrame = -1; + } + + if (needsRefresh) + { + if (TasView.IsPartiallyVisible(frame) || frame < TasView.FirstVisibleRow) + { + // frame < FirstVisibleRow: Greenzone in visible rows has been invalidated + RefreshDialog(); + return true; + } + else if (TasView.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; + return true; + } + } + + return false; + } + private void ClearLeftMouseStates() { + _leftButtonHeld = false; _startCursorDrag = false; _startSelectionDrag = false; _startBoolDrawColumn = ""; _startAxisDrawColumn = ""; - _drewAxis = false; - _paintingMinFrame = -1; TasView.ReleaseCurrentCell(); // Exit axis editing if value was changed with cursor if (AxisEditingMode && _axisPaintState != CurrentTasMovie.GetAxisState(_axisEditRow, _axisEditColumn)) { AxisEditRow = -1; - _triggerAutoRestore = true; - TastudioPlayMode(true); - JumpToGreenzone(); - DoTriggeredAutoRestoreIfNeeded(); - RefreshDialog(); } _axisPaintState = 0; _axisEditYPos = -1; - _leftButtonHeld = false; if (!AxisEditingMode) { @@ -847,17 +943,8 @@ namespace BizHawk.Client.EmuHawk } else { - if (!string.IsNullOrWhiteSpace(_startBoolDrawColumn) || _drewAxis) - { - // If painting up, we have altered frames without loading states (for smoothness) - // So now we have to ensure that all the edited frames are invalidated - GoToLastEmulatedFrameIfNecessary(_paintingMinFrame); - } - ClearLeftMouseStates(); } - - DoTriggeredAutoRestoreIfNeeded(); } if (e.Button == MouseButtons.Right) @@ -871,9 +958,31 @@ namespace BizHawk.Client.EmuHawk } } + EndBatchEdit(); // We didn't call BeginBatchEdit, but implicitly began one with mouse down. We must explicitly end it. + _suppressContextMenu = false; } + private void WheelSeek(int count) + { + if (_seekingTo != -1) + { + _seekingTo -= count; + + // that's a weird condition here, but for whatever reason it works best + if (count > 0 && Emulator.Frame >= _seekingTo) + { + GoToFrame(Emulator.Frame - count); + } + + RefreshDialog(); + } + else + { + GoToFrame(Emulator.Frame - count); + } + } + private void TasView_MouseWheel(object sender, MouseEventArgs e) { if (TasView.RightButtonHeld && TasView?.CurrentCell.RowIndex.HasValue == true) @@ -885,25 +994,7 @@ namespace BizHawk.Client.EmuHawk notch *= 2; } - // warning: tastudio rewind hotkey/button logic is copy pasted from here! - if (MainForm.IsSeeking && !MainForm.EmulatorPaused) - { - MainForm.PauseOnFrame -= notch; - - // that's a weird condition here, but for whatever reason it works best - if (notch > 0 && Emulator.Frame >= MainForm.PauseOnFrame) - { - MainForm.PauseEmulator(); - StopSeeking(); - GoToFrame(Emulator.Frame - notch); - } - - RefreshDialog(); - } - else - { - GoToFrame(Emulator.Frame - notch); - } + WheelSeek(notch); } } @@ -923,7 +1014,6 @@ namespace BizHawk.Client.EmuHawk } else { - ClearLeftMouseStates(); MarkerControl.AddMarker(TasView.CurrentCell.RowIndex.Value); } } @@ -944,11 +1034,6 @@ namespace BizHawk.Client.EmuHawk return; } - if (_paintingMinFrame >= 0) - { - _paintingMinFrame = Math.Min(_paintingMinFrame, e.NewCell.RowIndex.Value); - } - // skip rerecord counting on drawing entirely, mouse down is enough // avoid introducing another global bool wasCountingRerecords = CurrentTasMovie.IsCountingRerecords; @@ -975,7 +1060,7 @@ namespace BizHawk.Client.EmuHawk } } - if (_startCursorDrag && !MainForm.IsSeeking) + if (_startCursorDrag) { GoToFrame(e.NewCell.RowIndex.Value); } @@ -992,6 +1077,7 @@ namespace BizHawk.Client.EmuHawk } SetSplicer(); + RefreshDialog(); } else if (_rightClickFrame != -1) { @@ -1096,9 +1182,6 @@ namespace BizHawk.Client.EmuHawk if (_rightClickAlt || _rightClickControl || _rightClickShift) { - _triggerAutoRestore = true; - TastudioPlayMode(true); - JumpToGreenzone(); _suppressContextMenu = true; } } @@ -1125,12 +1208,6 @@ namespace BizHawk.Client.EmuHawk } CurrentTasMovie.SetBoolState(i, _startBoolDrawColumn, setVal); // Notice it uses new row, old column, you can only paint across a single column - - if (!_triggerAutoRestore) - { - TastudioPlayMode(true); - JumpToGreenzone(); - } } } @@ -1154,12 +1231,7 @@ namespace BizHawk.Client.EmuHawk } CurrentTasMovie.SetAxisState(i, _startAxisDrawColumn, setVal); // Notice it uses new row, old column, you can only paint across a single column - _triggerAutoRestore = true; - TastudioPlayMode(true); - RefreshDialog(); } - - _drewAxis = true; } CurrentTasMovie.IsCountingRerecords = wasCountingRerecords; @@ -1168,8 +1240,6 @@ namespace BizHawk.Client.EmuHawk { TasView.MakeIndexVisible(TasView.CurrentCell.RowIndex.Value); // todo: limit scrolling speed } - - SetTasViewRowCount(); } private void TasView_MouseMove(object sender, MouseEventArgs e) @@ -1250,8 +1320,9 @@ namespace BizHawk.Client.EmuHawk return; } + BeginBatchEdit(); + int value = CurrentTasMovie.GetAxisState(_axisEditRow, _axisEditColumn); - int prev = value; string prevTyped = _axisTypedValue; var range = ControllerType.Axes[_axisEditColumn]; @@ -1328,10 +1399,6 @@ namespace BizHawk.Client.EmuHawk if (_axisBackupState != _axisPaintState) { CurrentTasMovie.SetAxisState(_axisEditRow, _axisEditColumn, _axisBackupState); - _triggerAutoRestore = Emulator.Frame > _axisEditRow; - TastudioPlayMode(true); - JumpToGreenzone(); - DoTriggeredAutoRestoreIfNeeded(); } AxisEditRow = -1; @@ -1388,17 +1455,13 @@ namespace BizHawk.Client.EmuHawk { CurrentTasMovie.SetAxisState(row, _axisEditColumn, value); } - - if (value != prev) // Auto-restore - { - _triggerAutoRestore = Emulator.Frame > _axisEditRow; - TastudioPlayMode(true); - JumpToGreenzone(); - DoTriggeredAutoRestoreIfNeeded(); - } } - RefreshDialog(); + bool didRefresh = EndBatchEdit(); + if (!didRefresh && (prevTyped != _axisTypedValue || !AxisEditingMode)) + { + RefreshDialog(); + } } private void TasView_KeyDown(object sender, KeyEventArgs e) @@ -1429,8 +1492,6 @@ namespace BizHawk.Client.EmuHawk { EditAnalogProgrammatically(e); } - - RefreshDialog(); } } } diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs index 5448462118..8fe80742f0 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs @@ -422,14 +422,7 @@ namespace BizHawk.Client.EmuHawk _tasClipboard.Add(new TasClipboardEntry(i, line)); } - var rollbackFrame = CurrentTasMovie.CopyOverInput(TasView.SelectionStartIndex ?? 0, _tasClipboard.Select(static x => x.ControllerState)); - if (rollbackFrame > 0) - { - GoToLastEmulatedFrameIfNecessary(rollbackFrame); - DoAutoRestore(); - } - - FullRefresh(); + CurrentTasMovie.CopyOverInput(TasView.SelectionStartIndex ?? 0, _tasClipboard.Select(static x => x.ControllerState)); } } } @@ -465,15 +458,7 @@ namespace BizHawk.Client.EmuHawk } var selectionStart = TasView.SelectionStartIndex; - var needsToRollback = selectionStart < Emulator.Frame; CurrentTasMovie.InsertInput(selectionStart ?? 0, _tasClipboard.Select(static x => x.ControllerState)); - if (needsToRollback) - { - GoToLastEmulatedFrameIfNecessary(selectionStart!.Value); - DoAutoRestore(); - } - - FullRefresh(); } } } @@ -484,9 +469,6 @@ namespace BizHawk.Client.EmuHawk { if (TasView.Focused && TasView.AnyRowsSelected) { - var selectionStart = TasView.SelectionStartIndex; - var needsToRollback = selectionStart < Emulator.Frame; - var rollBackFrame = selectionStart ?? 0; _tasClipboard.Clear(); var list = TasView.SelectedRows.ToArray(); @@ -505,16 +487,11 @@ namespace BizHawk.Client.EmuHawk } Clipboard.SetDataObject(sb.ToString()); + BeginBatchEdit(); // movie's RemoveFrames may make multiple separate invalidations CurrentTasMovie.RemoveFrames(list); + EndBatchEdit(); SetSplicer(); - if (needsToRollback) - { - GoToLastEmulatedFrameIfNecessary(rollBackFrame); - DoAutoRestore(); - } - - FullRefresh(); } } @@ -522,10 +499,7 @@ namespace BizHawk.Client.EmuHawk { if (TasView.Focused && TasView.AnyRowsSelected) { - var firstWithInput = FirstNonEmptySelectedFrame; - bool needsToRollback = firstWithInput.HasValue && firstWithInput < Emulator.Frame; - var rollBackFrame = TasView.SelectionStartIndex ?? 0; - + BeginBatchEdit(); CurrentTasMovie.ChangeLog.BeginNewBatch($"Clear frames {TasView.SelectionStartIndex}-{TasView.SelectionEndIndex}"); foreach (int frame in TasView.SelectedRows) { @@ -533,14 +507,7 @@ namespace BizHawk.Client.EmuHawk } CurrentTasMovie.ChangeLog.EndBatch(); - - if (needsToRollback) - { - GoToLastEmulatedFrameIfNecessary(rollBackFrame); - DoAutoRestore(); - } - - FullRefresh(); + EndBatchEdit(); } } @@ -549,26 +516,19 @@ namespace BizHawk.Client.EmuHawk if (TasView.Focused && TasView.AnyRowsSelected) { var selectionStart = TasView.SelectionStartIndex; - var needsToRollback = selectionStart < Emulator.Frame; var rollBackFrame = selectionStart ?? 0; if (rollBackFrame >= CurrentTasMovie.InputLogLength) { // Cannot delete non-existent frames - FullRefresh(); + RefreshDialog(); return; } + BeginBatchEdit(); // movie's RemoveFrames may make multiple separate invalidations CurrentTasMovie.RemoveFrames(TasView.SelectedRows.ToArray()); + EndBatchEdit(); SetTasViewRowCount(); SetSplicer(); - - if (needsToRollback) - { - GoToLastEmulatedFrameIfNecessary(rollBackFrame); - DoAutoRestore(); - } - - FullRefresh(); } } @@ -588,48 +548,29 @@ namespace BizHawk.Client.EmuHawk private void CloneFramesXTimes(int timesToClone) { + BeginBatchEdit(); for (int i = 0; i < timesToClone; i++) { if (TasView.Focused && TasView.AnyRowsSelected) { var framesToInsert = TasView.SelectedRows; var insertionFrame = Math.Min((TasView.SelectionEndIndex ?? 0) + 1, CurrentTasMovie.InputLogLength); - var needsToRollback = TasView.SelectionStartIndex < Emulator.Frame; var inputLog = framesToInsert .Select(frame => CurrentTasMovie.GetInputLogEntry(frame)) .ToList(); CurrentTasMovie.InsertInput(insertionFrame, inputLog); - - if (needsToRollback) - { - GoToLastEmulatedFrameIfNecessary(insertionFrame); - DoAutoRestore(); - } - - FullRefresh(); } } + EndBatchEdit(); } private void InsertFrameMenuItem_Click(object sender, EventArgs e) { if (TasView.Focused && TasView.AnyRowsSelected) { - var selectionStart = TasView.SelectionStartIndex; - var insertionFrame = selectionStart ?? 0; - var needsToRollback = selectionStart < Emulator.Frame; - - CurrentTasMovie.InsertEmptyFrame(insertionFrame); - - if (needsToRollback) - { - GoToLastEmulatedFrameIfNecessary(insertionFrame); - DoAutoRestore(); - } - - FullRefresh(); + CurrentTasMovie.InsertEmptyFrame(TasView.SelectionStartIndex ?? 0); } } @@ -650,18 +591,8 @@ namespace BizHawk.Client.EmuHawk { if (TasView.Focused && TasView.AnyRowsSelected) { - var rollbackFrame = TasView.SelectionEndIndex ?? 0; - var needsToRollback = TasView.SelectionStartIndex < Emulator.Frame; - - CurrentTasMovie.Truncate(rollbackFrame); + CurrentTasMovie.Truncate(TasView.SelectionEndIndex ?? 0); MarkerControl.MarkerInputRoll.TruncateSelection(CurrentTasMovie.Markers.Count - 1); - - if (needsToRollback) - { - GoToFrame(rollbackFrame); - } - - FullRefresh(); } } @@ -1382,7 +1313,7 @@ namespace BizHawk.Client.EmuHawk 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). - CancelSeekContextMenuItem.Enabled = MainForm.PauseOnFrame.HasValue; + CancelSeekContextMenuItem.Enabled = _seekingTo != -1; BranchContextMenuItem.Visible = TasView.CurrentCell?.RowIndex == Emulator.Frame; SelectBetweenMarkersContextMenuItem.ShortcutKeyDisplayString = Config.HotkeyBindings["Sel. bet. Markers"]; @@ -1396,8 +1327,7 @@ namespace BizHawk.Client.EmuHawk private void CancelSeekContextMenuItem_Click(object sender, EventArgs e) { - MainForm.PauseOnFrame = null; - TasView.Refresh(); + CancelSeek(); } private void BranchContextMenuItem_Click(object sender, EventArgs e) diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs index 56ada6ce1a..85eff833df 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs @@ -5,73 +5,36 @@ namespace BizHawk.Client.EmuHawk public partial class TAStudio { /// - /// Only goes to go to the frame if it is an event before current emulation, otherwise it is just a future event that can freely be edited + /// Seek to the given frame, past or future, and load a state first if doing so gets us there faster. + /// Does nothing if we are already on the given frame. /// - private void GoToLastEmulatedFrameIfNecessary(int frame, bool OnLeftMouseDown = false) + public void GoToFrame(int frame, bool OnLeftMouseDown = false) { - if (frame != Emulator.Frame) // Don't go to a frame if you are already on it! + if (frame == Emulator.Frame) { - if (frame <= Emulator.Frame) - { - if ((MainForm.EmulatorPaused || !MainForm.IsSeeking) - && !CurrentTasMovie.LastPositionStable) - { - LastPositionFrame = Emulator.Frame; - CurrentTasMovie.LastPositionStable = true; // until new frame is emulated - } - - GoToFrame(frame, false, false, OnLeftMouseDown); - } - else - { - _triggerAutoRestore = false; - } + return; } - } - public void GoToFrame(int frame, bool fromLua = false, bool fromRewinding = false, bool OnLeftMouseDown = false) - { - // If seeking to a frame before or at the end of the movie, use StartAtNearestFrameAndEmulate - // Otherwise, load the latest state (if not already there) and seek while recording. + // Unpausing after a seek may seem like we aren't really seeking at all: + // what is the significance of a seek to frame if we don't pause? + // Answer: We use this in order to temporarily disable recording mode when the user navigates to a frame. (to avoid recording between whatever is the most recent state and the user-specified frame) + // Other answer: turbo seek, navigating while unpaused + _pauseAfterSeeking = MainForm.EmulatorPaused || (_seekingTo != -1 && _pauseAfterSeeking); WasRecording = CurrentTasMovie.IsRecording() || WasRecording; + TastudioPlayMode(); - if (frame <= CurrentTasMovie.InputLogLength) + var closestState = GetPriorStateForFramebuffer(frame); + if (frame < Emulator.Frame || closestState.Key > Emulator.Frame) { - // Get as close as we can then emulate there - StartAtNearestFrameAndEmulate(frame, fromLua, fromRewinding); - if (!OnLeftMouseDown) { MaybeFollowCursor(); } + LoadState(closestState, true); } - else // Emulate to a future frame + closestState.Value.Dispose(); + + StartSeeking(frame); + + if (!OnLeftMouseDown) { - if (frame == Emulator.Frame + 1) // We are at the end of the movie and advancing one frame, therefore we are recording, simply emulate a frame - { - bool wasPaused = MainForm.EmulatorPaused; - MainForm.FrameAdvance(); - if (!wasPaused) - { - MainForm.UnpauseEmulator(); - } - } - else - { - TastudioPlayMode(); - - var lastState = GetPriorStateForFramebuffer(frame); - if (lastState.Key > Emulator.Frame) - { - LoadState(lastState, true); - } - - StartSeeking(frame); - } - } - } - - public void GoToPreviousFrame() - { - if (Emulator.Frame > 0) - { - GoToFrame(Emulator.Frame - 1); + MaybeFollowCursor(); } } @@ -92,6 +55,17 @@ namespace BizHawk.Client.EmuHawk GoToFrame(next); } + public void RestorePosition() + { + if (RestorePositionFrame != -1) + { + // restore makes no sense without pausing + // Pausing here ensures any seek done by GoToFrame pauses after completing. + MainForm.PauseEmulator(); + GoToFrame(RestorePositionFrame); + } + } + /// /// Makes the given frame visible. If no frame is given, makes the current frame visible. /// diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs index 0bf446b61f..2869325df4 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs @@ -50,7 +50,11 @@ namespace BizHawk.Client.EmuHawk /// Gets a value that separates "restore last position" logic from seeking caused by navigation. /// TASEditor never kills LastPositionFrame, and it only pauses on it, if it hasn't been greenzoned beforehand and middle mouse button was pressed. /// - public int LastPositionFrame { get; private set; } + public int RestorePositionFrame { get; private set; } + private bool _shouldMoveGreenArrow; + private bool _seekingByEdit; + + private int _seekingTo = -1; [ConfigPersist] public TAStudioSettings Settings { get; set; } = new TAStudioSettings(); @@ -162,7 +166,7 @@ namespace BizHawk.Client.EmuHawk TasView.QueryItemIcon += TasView_QueryItemIcon; TasView.QueryFrameLag += TasView_QueryFrameLag; TasView.PointedCellChanged += TasView_PointedCellChanged; - LastPositionFrame = -1; + RestorePositionFrame = -1; TasView.MouseLeave += TAStudio_MouseLeave; TasView.CellHovered += (_, e) => @@ -549,7 +553,7 @@ namespace BizHawk.Client.EmuHawk movie.InputRollSettingsForSave = () => TasView.UserSettingsSerialized(); movie.BindMarkersToInput = Settings.BindMarkersToInput; - movie.GreenzoneInvalidated = GreenzoneInvalidated; + movie.GreenzoneInvalidated = (f) => _ = FrameEdited(f); movie.ChangeLog.MaxSteps = Settings.MaxUndoSteps; movie.PropertyChanged += TasMovie_OnPropertyChanged; @@ -773,20 +777,6 @@ namespace BizHawk.Client.EmuHawk public IEnumerable GetSelection() => TasView.SelectedRows; - // Slow but guarantees the entire dialog refreshes - private void FullRefresh() - { - SetTasViewRowCount(); - TasView.Refresh(); // An extra refresh potentially but we need to guarantee - MarkerControl.UpdateValues(); - BookMarkControl.UpdateValues(); - - if (_undoForm != null && !_undoForm.IsDisposed) - { - _undoForm.UpdateValues(); - } - } - public void RefreshDialog(bool refreshTasView = true, bool refreshBranches = true) { if (_exiting) @@ -812,41 +802,12 @@ namespace BizHawk.Client.EmuHawk } } - public void RefreshForInputChange(int firstChangedFrame) - { - if (TasView.IsPartiallyVisible(firstChangedFrame) || firstChangedFrame < TasView.FirstVisibleRow) - { - RefreshDialog(); - } - } - private void SetTasViewRowCount() { TasView.RowCount = CurrentTasMovie.InputLogLength + 1; _lastRefresh = Emulator.Frame; } - public void DoAutoRestore() - { - if (Settings.AutoRestoreLastPosition && LastPositionFrame != -1) - { - if (LastPositionFrame > Emulator.Frame) // Don't unpause if we are already on the desired frame, else runaway seek - { - StartSeeking(LastPositionFrame); - } - } - else - { - if (_autoRestorePaused.HasValue && !_autoRestorePaused.Value) - { - // this happens when we're holding the left button while unpaused - view scrolls down, new input gets drawn, seek pauses - MainForm.UnpauseEmulator(); - } - - _autoRestorePaused = null; - } - } - /// /// Get a savestate prior to the previous frame so code following the call can frame advance and have a framebuffer. /// If frame is 0, return the initial state. @@ -856,66 +817,6 @@ namespace BizHawk.Client.EmuHawk return CurrentTasMovie.TasStateManager.GetStateClosestToFrame(frame > 0 ? frame - 1 : 0); } - private void StartAtNearestFrameAndEmulate(int frame, bool fromLua, bool fromRewinding) - { - if (frame == Emulator.Frame) - { - return; - } - - _unpauseAfterSeeking = (fromRewinding || WasRecording) && !MainForm.EmulatorPaused; - TastudioPlayMode(); - var closestState = GetPriorStateForFramebuffer(frame); - if (closestState.Value.Length > 0 && (frame < Emulator.Frame || closestState.Key > Emulator.Frame)) - { - LoadState(closestState, true); - } - closestState.Value.Dispose(); - - if (fromLua) - { - bool wasPaused = MainForm.EmulatorPaused; - - // why not use this? because I'm not letting the form freely run. it all has to be under this loop. - // i could use this and then poll StepRunLoop_Core() repeatedly, but.. that's basically what I'm doing - // PauseOnFrame = frame; - - while (Emulator.Frame != frame) - { - MainForm.SeekFrameAdvance(); - } - - if (!wasPaused) - { - MainForm.UnpauseEmulator(); - } - - // lua botting users will want to re-activate record mode automatically -- it should be like nothing ever happened - if (WasRecording) - { - TastudioRecordMode(); - } - - // now the next section won't happen since we're at the right spot - } - - // frame == Emulator.Frame when frame == 0 - if (frame > Emulator.Frame) - { - // make seek frame keep up with emulation on fast scrolls - if (MainForm.EmulatorPaused || MainForm.IsSeeking || fromRewinding || WasRecording) - { - StartSeeking(frame); - } - else - { - // GUI users may want to be protected from clobbering their video when skipping around... - // well, users who are rewinding aren't. (that gets done through the seeking system in the call above) - // users who are clicking around.. I don't know. - } - } - } - public void LoadState(KeyValuePair state, bool discardApiHawkSurfaces = false) { StatableEmulator.LoadStateBinary(new BinaryReader(state.Value)); @@ -956,35 +857,11 @@ namespace BizHawk.Client.EmuHawk SplicerStatusLabel.Text = temp; } - private void DoTriggeredAutoRestoreIfNeeded() - { - if (_triggerAutoRestore) - { - TastudioPlayMode(true); // once user started editing, rec mode is unsafe - DoAutoRestore(); - - _triggerAutoRestore = false; - _autoRestorePaused = null; - } - } - public void InsertNumFrames(int insertionFrame, int numberOfFrames) { if (insertionFrame <= CurrentTasMovie.InputLogLength) { - var needsToRollback = TasView.SelectionStartIndex < Emulator.Frame; - CurrentTasMovie.InsertEmptyFrame(insertionFrame, numberOfFrames); - - if (needsToRollback) - { - GoToLastEmulatedFrameIfNecessary(insertionFrame); - DoAutoRestore(); - } - else - { - RefreshForInputChange(insertionFrame); - } } } @@ -992,20 +869,14 @@ namespace BizHawk.Client.EmuHawk { if (beginningFrame < CurrentTasMovie.InputLogLength) { + // movie's RemoveFrames might do multiple separate invalidations + BeginBatchEdit(); + int[] framesToRemove = Enumerable.Range(beginningFrame, numberOfFrames).ToArray(); CurrentTasMovie.RemoveFrames(framesToRemove); SetSplicer(); - var needsToRollback = beginningFrame < Emulator.Frame; - if (needsToRollback) - { - GoToLastEmulatedFrameIfNecessary(beginningFrame); - DoAutoRestore(); - } - else - { - RefreshForInputChange(beginningFrame); - } + EndBatchEdit(); } } @@ -1013,22 +884,15 @@ namespace BizHawk.Client.EmuHawk { if (beginningFrame < CurrentTasMovie.InputLogLength) { - var needsToRollback = TasView.SelectionStartIndex < Emulator.Frame; + BeginBatchEdit(); + int last = Math.Min(beginningFrame + numberOfFrames, CurrentTasMovie.InputLogLength); for (int i = beginningFrame; i < last; i++) { CurrentTasMovie.ClearFrame(i); } - if (needsToRollback) - { - GoToLastEmulatedFrameIfNecessary(beginningFrame); - DoAutoRestore(); - } - else - { - RefreshForInputChange(beginningFrame); - } + EndBatchEdit(); } } @@ -1076,7 +940,19 @@ namespace BizHawk.Client.EmuHawk private void TAStudio_MouseLeave(object sender, EventArgs e) { toolTip1.SetToolTip(TasView, null); - DoTriggeredAutoRestoreIfNeeded(); + } + + private void TAStudio_Deactivate(object sender, EventArgs e) + { + if (_leftButtonHeld) + { + TasView_MouseUp(this, new(MouseButtons.Left, 0, 0, 0, 0)); + } + if (_rightClickFrame != -1) + { + _suppressContextMenu = true; + TasView_MouseUp(this, new(MouseButtons.Right, 0, 0, 0, 0)); + } } protected override bool ProcessCmdKey(ref Message msg, Keys keyData) @@ -1117,8 +993,6 @@ namespace BizHawk.Client.EmuHawk } CurrentTasMovie.ChangeLog.IsRecording = wasRecording; - GoToLastEmulatedFrameIfNecessary(Emulator.Frame - 1); - DoAutoRestore(); return true; } @@ -1251,5 +1125,28 @@ namespace BizHawk.Client.EmuHawk TasView.AllColumns.ColumnsChanged(); } + + public void LoadBranch(TasBranch branch) + { + if (Settings.OldControlSchemeForBranches && !TasPlaybackBox.RecordingMode) + { + GoToFrame(branch.Frame); + return; + } + + CurrentTasMovie.LoadBranch(branch); + LoadState(new(branch.Frame, new MemoryStream(branch.CoreData, false))); + + CurrentTasMovie.TasStateManager.Capture(Emulator.Frame, Emulator.AsStatable()); + QuickBmpFile.Copy(new BitmapBufferVideoProvider(branch.CoreFrameBuffer), VideoProvider); + + if (Settings.OldControlSchemeForBranches && TasPlaybackBox.RecordingMode) + { + CurrentTasMovie.Truncate(branch.Frame); + } + + CancelSeek(); + RefreshDialog(); + } } }