From 23ba9000fc6ee677d07dc401601754bd34cd64e0 Mon Sep 17 00:00:00 2001 From: SuuperW Date: Wed, 15 Apr 2026 02:29:20 -0500 Subject: [PATCH 1/4] replace old comment; the comment and code under it got moved around throughout the years such that the comment accidentally got put with the wrong code and the right code doesn't exist anymore --- src/BizHawk.Client.EmuHawk/MainForm.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BizHawk.Client.EmuHawk/MainForm.cs b/src/BizHawk.Client.EmuHawk/MainForm.cs index 01a208b9ccd..4009166166b 100644 --- a/src/BizHawk.Client.EmuHawk/MainForm.cs +++ b/src/BizHawk.Client.EmuHawk/MainForm.cs @@ -2918,7 +2918,7 @@ private void StepRunLoop_Core(bool force = false) _lastFastForwardingOrRewinding = isFastForwardingOrRewinding; - // client input-related duties + // Clear stuff previously drawn by gui API OSD.ClearGuiText(); CheatList.Pulse(); From 373b8fd1100ea43d2d82d391329ffd5d19649863 Mon Sep 17 00:00:00 2001 From: SuuperW Date: Wed, 15 Apr 2026 02:48:37 -0500 Subject: [PATCH 2/4] make everything that clears any gui API drawings clear all gui API drawings (except clear APIs, about to be deprecated) --- src/BizHawk.Client.Common/Api/Classes/GuiApi.cs | 2 +- .../DisplayManager/DisplayManagerBase.cs | 4 ++++ src/BizHawk.Client.Common/DisplayManager/OSDManager.cs | 2 +- src/BizHawk.Client.EmuHawk/MainForm.cs | 4 ++-- src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.cs | 2 -- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/BizHawk.Client.Common/Api/Classes/GuiApi.cs b/src/BizHawk.Client.Common/Api/Classes/GuiApi.cs index ce448247e00..c5e7dfcc340 100644 --- a/src/BizHawk.Client.Common/Api/Classes/GuiApi.cs +++ b/src/BizHawk.Client.Common/Api/Classes/GuiApi.cs @@ -142,7 +142,7 @@ public void AddMessage(string message, [LiteralExpected] int? duration = null) public void ClearGraphics(DisplaySurfaceID? surfaceID = null) => Get2DRenderer(surfaceID).Clear(); - public void ClearText() => _displayManager.OSD.ClearGuiText(); + public void ClearText() => _displayManager.OSD.ClearApiHawkText(); public void SetDefaultForegroundColor(Color color) => _defaultForeground = color; diff --git a/src/BizHawk.Client.Common/DisplayManager/DisplayManagerBase.cs b/src/BizHawk.Client.Common/DisplayManager/DisplayManagerBase.cs index 5a30295c011..ac1dc96b20f 100644 --- a/src/BizHawk.Client.Common/DisplayManager/DisplayManagerBase.cs +++ b/src/BizHawk.Client.Common/DisplayManager/DisplayManagerBase.cs @@ -961,12 +961,16 @@ public I2DRenderer GetApiHawk2DRenderer(DisplaySurfaceID surfaceID) ? _apiHawkIDTo2DRenderer.GetValueOrPut(surfaceID, _ => _gl.Create2DRenderer(_imGuiResourceCache)) : throw new ArgumentOutOfRangeException(paramName: nameof(surfaceID), surfaceID, message: "invalid surface ID"); + /// + /// Clear stuff drawn by the gui API + /// public void ClearApiHawkSurfaces() { foreach (var renderer in _apiHawkIDTo2DRenderer.Values) { renderer.Clear(); } + OSD.ClearApiHawkText(); } public void DiscardApiHawkSurfaces() diff --git a/src/BizHawk.Client.Common/DisplayManager/OSDManager.cs b/src/BizHawk.Client.Common/DisplayManager/OSDManager.cs index 1ab1aec2b0c..1a0d5ac6130 100644 --- a/src/BizHawk.Client.Common/DisplayManager/OSDManager.cs +++ b/src/BizHawk.Client.Common/DisplayManager/OSDManager.cs @@ -120,7 +120,7 @@ public void AddGuiText(string message, MessagePosition pos, Color backGround, Co }); } - public void ClearGuiText() + public void ClearApiHawkText() => _guiTextList.Clear(); private void DrawMessage(IBlitter g, UIMessage message, int yOffset) diff --git a/src/BizHawk.Client.EmuHawk/MainForm.cs b/src/BizHawk.Client.EmuHawk/MainForm.cs index 4009166166b..740675c2f46 100644 --- a/src/BizHawk.Client.EmuHawk/MainForm.cs +++ b/src/BizHawk.Client.EmuHawk/MainForm.cs @@ -2919,7 +2919,7 @@ private void StepRunLoop_Core(bool force = false) _lastFastForwardingOrRewinding = isFastForwardingOrRewinding; // Clear stuff previously drawn by gui API - OSD.ClearGuiText(); + DisplayManager.ClearApiHawkSurfaces(); CheatList.Pulse(); @@ -4080,7 +4080,7 @@ public bool LoadState(string path, string userFriendlyStateName, bool suppressOS return false; } - OSD.ClearGuiText(); + DisplayManager.ClearApiHawkSurfaces(); if (SavestateLoaded is not null) { StateLoadedEventArgs args = new(userFriendlyStateName); diff --git a/src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.cs b/src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.cs index 68c1a6acec6..2b1e4b76d50 100644 --- a/src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.cs +++ b/src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.cs @@ -149,7 +149,6 @@ public LuaConsole() ResetDrawSurfacePadding(); ClearFileWatches(); LuaImp?.Close(); - DisplayManager.OSD.ClearGuiText(); } else { @@ -1006,7 +1005,6 @@ private void RemoveScriptMenuItem_Click(object sender, EventArgs e) UpdateDialog(); DisplayManager.ClearApiHawkSurfaces(); DisplayManager.ClearApiHawkTextureCache(); - DisplayManager.OSD.ClearGuiText(); if (!_openedFiles.Any(static lf => !lf.IsSeparator)) ResetDrawSurfacePadding(); // just removed last script, reset padding } } From 6237eebceeeb0021440737b59b2e1518effeb366 Mon Sep 17 00:00:00 2001 From: SuuperW Date: Wed, 15 Apr 2026 03:36:23 -0500 Subject: [PATCH 3/4] replace the gui.clear methods with something more friendly to having multiple Lua scripts running at once --- .../Api/Classes/GuiApi.cs | 10 +++++-- .../Api/Interfaces/IGuiApi.cs | 16 ++++++++++ .../DisplayManager/DisplayManagerBase.cs | 11 +++++++ .../lua/NamedLuaFunction.cs | 2 ++ src/BizHawk.Client.EmuHawk/MainForm.cs | 1 + .../tools/Lua/Libraries/GuiLuaLibrary.cs | 29 +++++++++++++++++-- 6 files changed, 64 insertions(+), 5 deletions(-) diff --git a/src/BizHawk.Client.Common/Api/Classes/GuiApi.cs b/src/BizHawk.Client.Common/Api/Classes/GuiApi.cs index c5e7dfcc340..241eeebdbf6 100644 --- a/src/BizHawk.Client.Common/Api/Classes/GuiApi.cs +++ b/src/BizHawk.Client.Common/Api/Classes/GuiApi.cs @@ -140,9 +140,15 @@ public void DrawNew(string name, bool clear) public void AddMessage(string message, [LiteralExpected] int? duration = null) => _dialogController.AddOnScreenMessage(message, duration); - public void ClearGraphics(DisplaySurfaceID? surfaceID = null) => Get2DRenderer(surfaceID).Clear(); + public void ClearGraphics(DisplaySurfaceID? surfaceID = null) => LogCallback("the `ClearGraphics()` function has been deprecated; use `Draw()`\n"); - public void ClearText() => _displayManager.OSD.ClearApiHawkText(); + public void ClearText() => LogCallback("the `ClearText()` function has been deprecated; use `Draw()`\n"); + + public void Draw() => _displayManager.DoApiRedraw(); + + public void AddDrawCallback(Action callback) => _displayManager.OnDraw += callback; + + public void RemoveDrawCallback(Action callback) => _displayManager.OnDraw -= callback; public void SetDefaultForegroundColor(Color color) => _defaultForeground = color; diff --git a/src/BizHawk.Client.Common/Api/Interfaces/IGuiApi.cs b/src/BizHawk.Client.Common/Api/Interfaces/IGuiApi.cs index a2c3311037a..27e336ec012 100644 --- a/src/BizHawk.Client.Common/Api/Interfaces/IGuiApi.cs +++ b/src/BizHawk.Client.Common/Api/Interfaces/IGuiApi.cs @@ -33,8 +33,24 @@ public interface IGuiApi : IDisposable, IExternalApi void AddMessage(string message, int? duration = null); + [Obsolete("use Draw and AddDrawCallback instead")] void ClearGraphics(DisplaySurfaceID? surfaceID = null); + + [Obsolete("use Draw and AddDrawCallback instead")] void ClearText(); + + /// + /// Clears all prior drawings and calls any actions given to . + /// This should only be used when you want to redraw while paused. + /// + void Draw(); + + /// + /// The given action will be called before each API draw. This means after each frame and any time is called. + /// + void AddDrawCallback(Action callback); + void RemoveDrawCallback(Action callback); + void SetDefaultForegroundColor(Color color); void SetDefaultBackgroundColor(Color color); Color GetDefaultTextBackground(); diff --git a/src/BizHawk.Client.Common/DisplayManager/DisplayManagerBase.cs b/src/BizHawk.Client.Common/DisplayManager/DisplayManagerBase.cs index ac1dc96b20f..8e21b3b0bde 100644 --- a/src/BizHawk.Client.Common/DisplayManager/DisplayManagerBase.cs +++ b/src/BizHawk.Client.Common/DisplayManager/DisplayManagerBase.cs @@ -51,6 +51,8 @@ public DisplayManagerRenderTargetProvider(Func callback) public SnowyNullVideo SnowyVP { get; private set; } + public Action/*?*/ OnDraw; + protected DisplayManagerBase( Config config, IEmulator emulator, @@ -973,6 +975,15 @@ public void ClearApiHawkSurfaces() OSD.ClearApiHawkText(); } + /// + /// Clear stuff drawn by the gui API and raise the draw event. + /// + public void DoApiRedraw() + { + ClearApiHawkSurfaces(); + OnDraw?.Invoke(); + } + public void DiscardApiHawkSurfaces() { foreach (var renderer in _apiHawkIDTo2DRenderer.Values) diff --git a/src/BizHawk.Client.Common/lua/NamedLuaFunction.cs b/src/BizHawk.Client.Common/lua/NamedLuaFunction.cs index bf665b0b826..84d06748dfe 100644 --- a/src/BizHawk.Client.Common/lua/NamedLuaFunction.cs +++ b/src/BizHawk.Client.Common/lua/NamedLuaFunction.cs @@ -28,6 +28,8 @@ public sealed class NamedLuaFunction : INamedLuaFunction public const string EVENT_TYPE_FUTURE = "BeforeFutureFrame"; + public const string EVENT_TYPE_DRAW = "OnDraw"; + private readonly LuaFunction _function; private readonly ILuaLibraries _luaImp; diff --git a/src/BizHawk.Client.EmuHawk/MainForm.cs b/src/BizHawk.Client.EmuHawk/MainForm.cs index 740675c2f46..b472487b291 100644 --- a/src/BizHawk.Client.EmuHawk/MainForm.cs +++ b/src/BizHawk.Client.EmuHawk/MainForm.cs @@ -3033,6 +3033,7 @@ private void StepRunLoop_Core(bool force = false) { UpdateToolsAfter(); } + DisplayManager.OnDraw?.Invoke(); if (newFrame) { diff --git a/src/BizHawk.Client.EmuHawk/tools/Lua/Libraries/GuiLuaLibrary.cs b/src/BizHawk.Client.EmuHawk/tools/Lua/Libraries/GuiLuaLibrary.cs index 4c06f27caa2..fbcaf734ba5 100644 --- a/src/BizHawk.Client.EmuHawk/tools/Lua/Libraries/GuiLuaLibrary.cs +++ b/src/BizHawk.Client.EmuHawk/tools/Lua/Libraries/GuiLuaLibrary.cs @@ -6,10 +6,12 @@ namespace BizHawk.Client.EmuHawk { - public sealed class GuiLuaLibrary : LuaLibraryBase, IDisposable + public sealed class GuiLuaLibrary : LuaLibraryBase, IDisposable, IRegisterFunctions { private DisplaySurfaceID _rememberedSurfaceID = DisplaySurfaceID.EmuCore; + public NLFAddCallback CreateAndRegisterNamedFunction { get; set; } + public GuiLuaLibrary(ILuaLibraries luaLibsImpl, ApiContainer apiContainer, Action logOutputCallback) : base(luaLibsImpl, apiContainer, logOutputCallback) {} @@ -37,15 +39,36 @@ public void DrawFinish() public void AddMessage(string message) => APIs.Gui.AddMessage(message); - [LuaMethodExample("gui.clearGraphics( );")] +#pragma warning disable CS0618 + [LuaDeprecatedMethod] [LuaMethod("clearGraphics", "clears all lua drawn graphics from the screen")] public void ClearGraphics(string surfaceName = null) => APIs.Gui.ClearGraphics(surfaceID: UseOrFallback(surfaceName)); - [LuaMethodExample("gui.cleartext( );")] + [LuaDeprecatedMethod] [LuaMethod("cleartext", "clears all text created by gui.text()")] public void ClearText() => APIs.Gui.ClearText(); +#pragma warning restore CS0618 + + [LuaMethodExample("gui.draw();")] + [LuaMethod("draw", "Clears all prior drawings and calls any functions given to gui.addDrawCallback. Use this to re-draw while paused.")] + public void Draw() + { + APIs.Gui.Draw(); + } + + [LuaMethodExample("local draw_cb_id = gui.addDrawCallback(\r\n\tfunction()\r\n\t\t-- gui.draw*** calls go here\r\n\tend);")] + [LuaMethod("addDrawCallback", "The given function will be called before each API draw. This means after each frame and any time gui.draw is called.")] + public string AddDrawCallback(LuaFunction luaf, string name = null) + { + INamedLuaFunction nlf = CreateAndRegisterNamedFunction(luaf, NamedLuaFunction.EVENT_TYPE_DRAW, ApiGroup.PROHIBITED_MID_FRAME, name); + Action callback = () => nlf.Call(); + APIs.Gui.AddDrawCallback(callback); + nlf.OnRemove += () => APIs.Gui.RemoveDrawCallback(callback); + + return nlf.GuidStr; + } [LuaMethodExample("gui.defaultForeground( 0x000000FF );")] [LuaMethod("defaultForeground", "Sets the default foreground color to use in drawing methods, white by default")] From a7682e56b697e85e94aafd7c73b7d9330029b0c8 Mon Sep 17 00:00:00 2001 From: SuuperW Date: Tue, 21 Jul 2026 23:27:24 -0500 Subject: [PATCH 4/4] convert ClearApiHawkSurfaces calls to `DoApiRedraw` so they don't clear more than intended --- src/BizHawk.Client.EmuHawk/MainForm.cs | 2 ++ src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.cs | 8 +++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/BizHawk.Client.EmuHawk/MainForm.cs b/src/BizHawk.Client.EmuHawk/MainForm.cs index b472487b291..f27b26956bb 100644 --- a/src/BizHawk.Client.EmuHawk/MainForm.cs +++ b/src/BizHawk.Client.EmuHawk/MainForm.cs @@ -4096,6 +4096,8 @@ public bool LoadState(string path, string userFriendlyStateName, bool suppressOS UpdateToolsLoadstate(); InputManager.AutoFireController.ClearStarts(); + DisplayManager.OnDraw?.Invoke(); // not DoApiRedraw, so scripts drawing in the savestate load event still work + //we don't want to analyze how to intermix movies, rewinding, and states //so purge rewind history when loading a state while doing a movie if (ToolControllingRewind is null && MovieSession.Movie.IsActive()) diff --git a/src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.cs b/src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.cs index 2b1e4b76d50..5004cb9f3a8 100644 --- a/src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.cs +++ b/src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.cs @@ -144,7 +144,8 @@ public LuaConsole() { Settings.Columns = LuaListView.AllColumns; - DisplayManager.ClearApiHawkSurfaces(); + _openedFiles.StopAllScripts(); + DisplayManager.DoApiRedraw(); DisplayManager.ClearApiHawkTextureCache(); ResetDrawSurfacePadding(); ClearFileWatches(); @@ -1003,7 +1004,7 @@ private void RemoveScriptMenuItem_Click(object sender, EventArgs e) } UpdateDialog(); - DisplayManager.ClearApiHawkSurfaces(); + DisplayManager.DoApiRedraw(); DisplayManager.ClearApiHawkTextureCache(); if (!_openedFiles.Any(static lf => !lf.IsSeparator)) ResetDrawSurfacePadding(); // just removed last script, reset padding } @@ -1511,7 +1512,8 @@ protected override bool ProcessTabKey(bool forward) private void EraseToolbarItem_Click(object sender, EventArgs e) { - DisplayManager.ClearApiHawkSurfaces(); + // tooltip says this is specifically for "stale" or "stuck" stuff + DisplayManager.DoApiRedraw(); } // Stupid designer