diff --git a/Plugin/ImGuiInstance.cs b/Plugin/ImGuiInstance.cs
index dc5d272..30239b3 100644
--- a/Plugin/ImGuiInstance.cs
+++ b/Plugin/ImGuiInstance.cs
@@ -15,7 +15,14 @@ public class ImGuiInstance
}
public string Name => _name;
+ ///
+ /// Called whenever ImGui is being rendered. You can use ImGui functions inside here.
+ ///
public event Action? Layout;
+ ///
+ /// Called whenever the window is processing an event. Returning false means that the event will not be processed further.
+ ///
+ public event Func? SdlEvent;
private string _name;
private ConfigEntry _entry;
@@ -26,6 +33,14 @@ public class ImGuiInstance
internal static ConcurrentQueue newInstances = new();
internal static ConcurrentQueue<(string, Action)> windowRequests = new();
+ ///
+ /// Tries to open the window. If it's already open, nothing happens. This function may be expensive to call rapidly.
+ ///
+ public void OpenWindow()
+ {
+ newInstances.Enqueue(this);
+ }
+
///
/// Gets a reference to the SDL3Window inside of a callback.
/// The callback will be called inside the SDL3 Thread.
@@ -74,7 +89,7 @@ public class ImGuiInstance
if (IsSDL3Running)
return;
- _sdl3Thread = new Thread(() => RunSDL3())
+ _sdl3Thread = new Thread(RunSDL3)
{
Name = $"Resonite ImGui SDL3",
Priority = ThreadPriority.Highest,
@@ -93,16 +108,21 @@ public class ImGuiInstance
{
while (newInstances.TryDequeue(out var request))
{
+ if (windows.ContainsKey(request.Name)) continue;
+
SDL3Window app = new SDL3Window("ImGuiContext: " + request.Name, request._entry.Value.x, request._entry.Value.y, request._entry.Value.z, request._entry.Value.w);
app.WindowRectModified += (rect) => request._entry.Value = rect;
// TODO: maybe listen to config changes to resize window
// request._entry.SettingChanged += (_, _) => { };
- app.RenderCallback = request.Layout!;
- windows[request.Name] = app;
- var winId = SDL.GetWindowID(app.Window);
- if (!windowsById.TryAdd(winId, app))
+ app.RenderCallback = request.Layout;
+ app.SdlEventReceived = request.SdlEvent;
+ if (!windows.TryAdd(request.Name, app))
{
- Plugin.Log.LogError($"Failed to add window with id {(uint) app.Window}, name {request.Name}!");
+ Plugin.Log.LogError($"Failed to add window with name {request.Name}!");
+ }
+ if (!windowsById.TryAdd(app.SdlWindowId, app))
+ {
+ Plugin.Log.LogError($"Failed to add window with id {app.SdlWindowId}, name {request.Name}!");
}
}
for (int i = 0; i < windowRequests.Count; i++)
@@ -119,7 +139,6 @@ public class ImGuiInstance
}
}
}
-
while (SDL.PollEvent(out SDL.Event ev))
{
if (windowsById.TryGetValue(ev.Window.WindowID, out var window))
@@ -128,10 +147,47 @@ public class ImGuiInstance
}
}
+ var removed = new HashSet<(string, uint)>();
foreach (var (key, window) in windows)
{
+ if(window.Disposed)
+ {
+ removed.Add((key, window.SdlWindowId));
+ continue;
+ }
+ window.SetContext();
+
+ if (Plugin.CancellationToken.IsCancellationRequested)
+ {
+ window.ForceDispose();
+ continue;
+ }
+
+ if (window.ShouldClose)
+ {
+ removed.Add((key, window.SdlWindowId));
+ window.Dispose();
+ continue;
+ }
+
window.RunOneFrame();
}
+ foreach (var (k, k2) in removed)
+ {
+ var b = windows.Remove(k);
+ var b2 = windowsById.Remove(k2);
+ Plugin.Log.LogDebug($"Removed window {k}, success codes: {b}|{b2}");
+ }
+ if (Plugin.CancellationToken.IsCancellationRequested)
+ {
+ break;
+ }
+
+ // Prevent busy looping when nothing is happening.
+ if (windows.Count == 0)
+ {
+ Thread.Sleep(1000);
+ }
}
}
catch (Exception e)
diff --git a/Plugin/ResoniteImGuiLib.csproj b/Plugin/ResoniteImGuiLib.csproj
index 13c02e3..bf36d01 100644
--- a/Plugin/ResoniteImGuiLib.csproj
+++ b/Plugin/ResoniteImGuiLib.csproj
@@ -1,7 +1,7 @@
- 2.0.0
+ 2.1.0
art0007i
net9.0
https://github.com/art0007i/ResoniteImGuiLib
diff --git a/Plugin/SDL3/SDL3Window.cs b/Plugin/SDL3/SDL3Window.cs
index 0b61ce0..3f8e9f3 100644
--- a/Plugin/SDL3/SDL3Window.cs
+++ b/Plugin/SDL3/SDL3Window.cs
@@ -1,5 +1,4 @@
-using CodeGenerationConfig;
-using Elements.Core;
+using Elements.Core;
using ImGuiNET;
using SDL3;
using System.Diagnostics;
@@ -12,12 +11,14 @@ public class SDL3Window : IDisposable
public readonly IntPtr Device;
public readonly ImGuiSDL3 Platform;
public readonly ImGuiSDL3Renderer Renderer;
+ public readonly uint SdlWindowId;
internal readonly Queue EventQueue = new();
+ internal Action? RenderCallback { get; set; }
+ internal Func? SdlEventReceived { get; set; }
public event Action? WindowRectModified;
public color? ClearColor = null;
- public Action? RenderCallback { get; set; }
private readonly Stopwatch _timer = Stopwatch.StartNew();
private TimeSpan _time = TimeSpan.Zero;
@@ -57,9 +58,11 @@ public class SDL3Window : IDisposable
Platform = new ImGuiSDL3(Window, Device);
Renderer = new ImGuiSDL3Renderer(Device);
+ SdlWindowId = SDL.GetWindowID(Window);
//ImGuiManager.TriggerImGuiRecreated();
}
+ public bool Disposed => _disposed;
private bool _disposed;
~SDL3Window()
@@ -68,6 +71,11 @@ public class SDL3Window : IDisposable
}
public void Dispose()
+ {
+ Dispose(false);
+ }
+
+ public void ForceDispose()
{
Dispose(true);
GC.SuppressFinalize(this);
@@ -102,8 +110,8 @@ public class SDL3Window : IDisposable
if (_imGuiContext != IntPtr.Zero)
{
- ImGui.SetCurrentContext(_imGuiContext);
- ImGui.DestroyContext();
+ ImGui.SetCurrentContext(IntPtr.Zero);
+ ImGui.DestroyContext(_imGuiContext);
_imGuiContext = IntPtr.Zero;
}
@@ -118,12 +126,15 @@ public class SDL3Window : IDisposable
}
}
+ public void SetContext()
+ {
+ ImGui.SetCurrentContext(_imGuiContext);
+ }
+
public void RunOneFrame()
{
if (_disposed) return;
- ImGui.SetCurrentContext(_imGuiContext);
-
ImGui.GetIO().DeltaTime = (float) (_timer.Elapsed - _time).TotalSeconds;
_time = _timer.Elapsed;
@@ -136,14 +147,10 @@ public class SDL3Window : IDisposable
Render();
- if (_shouldClose || Plugin.CancellationToken.IsCancellationRequested)
- {
- Dispose();
- }
ImGui.SetCurrentContext(IntPtr.Zero);
}
- private bool _shouldClose;
+ public bool ShouldClose;
private void PollEvents()
{
@@ -154,6 +161,17 @@ public class SDL3Window : IDisposable
while (EventQueue.TryDequeue(out var ev))
{
+ var skip = false;
+ foreach (var func in Delegate.EnumerateInvocationList(SdlEventReceived))
+ {
+ if (!func(ev))
+ {
+ skip = true;
+ break;
+ }
+ }
+ if (skip) continue;
+
Platform.ProcessEvent(ev);
switch ((SDL.EventType) ev.Type)
@@ -168,7 +186,7 @@ public class SDL3Window : IDisposable
case SDL.EventType.Terminating:
case SDL.EventType.WindowCloseRequested:
case SDL.EventType.Quit:
- _shouldClose = true;
+ ShouldClose = true;
break;
case SDL.EventType.WindowResized:
SetupScreenClipRect();
diff --git a/README.md b/README.md
index 783ff4e..82d6e1b 100644
--- a/README.md
+++ b/README.md
@@ -3,6 +3,8 @@
A [Resonite](https://resonite.com/) library that allows modders to create ImGui windows.
+Example usage can be found here: https://github.com/art0007i/ImGuiExample
+
## Installation (Manual)
1. Install [BepisLoader](https://github.com/ResoniteModding/BepisLoader) for Resonite.
2. Download the latest release ZIP file (e.g., `art0007i-ResoniteImGuiLib-2.0.0.zip`) from the [Releases](https://github.com/art0007i/ResoniteImGuiLib/releases) page.
diff --git a/thunderstore.toml b/thunderstore.toml
index 2737c4c..71417a8 100644
--- a/thunderstore.toml
+++ b/thunderstore.toml
@@ -10,7 +10,7 @@ description = "Library that allows modders to create ImGui windows."
websiteUrl = "https://github.com/art0007i/ResoniteImGuiLib"
containsNsfwContent = false
-[package.dependencies]
+[package.dependencies]
ResoniteModding-BepisLoader = "1.4.1"
ResoniteModding-BepisResoniteWrapper = "1.0.0"
@@ -33,11 +33,11 @@ target = "plugins/ResoniteImGuiLib/"
[[build.copy]]
source = "./Plugin/bin/Release/runtimes/linux-x64/native/libcimgui.so"
-target = "plugins/ResoniteImGuiLib/native/"
+target = "plugins/ResoniteImGuiLib/"
[[build.copy]]
source = "./Plugin/bin/Release/runtimes/win-x64/native/cimgui.dll"
-target = "plugins/ResoniteImGuiLib/native/"
+target = "plugins/ResoniteImGuiLib/"
[[build.copy]]
source = "./CHANGELOG.md"