using System; using System.Diagnostics; using System.Numerics; using System.Runtime.InteropServices; using Hexa.NET.ImGui; using Hexa.NET.ImGui.Utilities; using Hexa.NET.ImGuizmo; using Hexa.NET.ImPlot; using Hexa.NET.ImPlot3D; using SDL3; namespace SDL3_TestingSuite.SDL3; public sealed unsafe class SDL3Window : IDisposable { public readonly nint Window; public readonly nint Renderer; internal Action? RenderCallback { get; set; } public Vector4 ClearColor = new Vector4(0.06f, 0.05882353f, 0.05882353f, 1f); private readonly Stopwatch _timer = Stopwatch.StartNew(); private TimeSpan _time = TimeSpan.Zero; private SDL.Rect _screenClipRect; private ImGuiContextPtr _imGuiContext; public bool Disposed => _disposed; private bool _disposed; private FileSystemWatcher? _watcher; public SDL3Window(string name, int posX, int posY, int width, int height, SDL.WindowFlags flags = SDL.WindowFlags.Resizable | SDL.WindowFlags.HighPixelDensity) { if (!SDL.Init(SDL.InitFlags.Events | SDL.InitFlags.Video | SDL.InitFlags.Gamepad)) throw new Exception($"SDL_Init failed: {SDL.GetError()}"); // Create window & renderer if (!SDL.CreateWindowAndRenderer(name, width, height, flags, out Window, out Renderer)) throw new Exception($"SDL_CreateWindowAndRenderer failed: {SDL.GetError()}"); SDL.SetWindowPosition(Window, posX, posY); SDL.SetRenderVSync(Renderer, 1); SDL.ShowWindow(Window); // Create ImGui context var context = ImGui.CreateContext(); ImPlot.CreateContext(); ImPlot.SetImGuiContext(context); ImGuizmo.SetImGuiContext(context); // ImPlot3D.SetImGuiContext(context); // ImPlot3D.CreateContext(); ImGuiIOPtr io = ImGui.GetIO(); io.ConfigFlags |= ImGuiConfigFlags.NavEnableKeyboard | ImGuiConfigFlags.NavEnableGamepad | ImGuiConfigFlags.DockingEnable; // io.ConfigFlags |= ImGuiConfigFlags.ViewportsEnable; io.Fonts.Flags |= ImFontAtlasFlags.NoBakedLines; io.Fonts.AddFontDefault(); try { string fontsPath = Path.Combine(AppContext.BaseDirectory, "Fonts"); if (!Path.Exists(fontsPath)) Directory.CreateDirectory(fontsPath); _watcher = new FileSystemWatcher(fontsPath); _watcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.CreationTime; _watcher.Created += (a, b) => { if (!File.Exists(b.FullPath)) return; if (Path.GetExtension(b.FullPath) != ".ttf") return; ImGui.GetIO().AddFont(b.FullPath); }; _watcher.Deleted += (a, b) => { if (Path.GetExtension(b.FullPath) != ".ttf") return; ImGui.GetIO().RemoveFont(b.Name); }; _watcher.IncludeSubdirectories = false; _watcher.EnableRaisingEvents = true; string[] fonts = Directory.GetFiles(fontsPath, "*.ttf", SearchOption.AllDirectories); foreach (string font in fonts) { io.AddFont(font); } } catch (Exception e) { Console.WriteLine(e); } // Init platform and renderer ImGuiSDL3Platform.Init(Window, Renderer); ImGuiSDL3Renderer.Init(Renderer); } public void Run() { while (!_disposed) { ImGui.GetIO().DeltaTime = (float)(_timer.Elapsed - _time).TotalSeconds; _time = _timer.Elapsed; PollEvents(); Update(); RenderCallback?.Invoke(); Render(); if (ShouldClose) { Dispose(); break; } } if (!_disposed) Dispose(); } public bool ShouldClose; private void PollEvents() { if (ImGui.GetIO().WantTextInput && !SDL.TextInputActive(Window)) SDL.StartTextInput(Window); else if (!ImGui.GetIO().WantTextInput && SDL.TextInputActive(Window)) SDL.StopTextInput(Window); while (SDL.PollEvent(out SDL.Event ev)) { ImGuiSDL3Platform.ProcessEvent(ev); switch ((SDL.EventType)ev.Type) { case SDL.EventType.Terminating: case SDL.EventType.WindowCloseRequested: case SDL.EventType.Quit: ShouldClose = true; break; } } } private void Update() { ImGuiSDL3Platform.NewFrame(); ImGuiSDL3Renderer.NewFrame(); ImGui.NewFrame(); } private void Render() { ImGuiIOPtr io = ImGui.GetIO(); ImGui.Render(); SDL.SetRenderScale(Renderer, io.DisplayFramebufferScale.X, io.DisplayFramebufferScale.Y); SDL.SetRenderDrawColorFloat(Renderer, ClearColor.X, ClearColor.Y, ClearColor.Z, ClearColor.W); SDL.RenderClear(Renderer); ImGuiSDL3Renderer.RenderDrawData(ImGui.GetDrawData(), Renderer); if ((io.ConfigFlags & ImGuiConfigFlags.ViewportsEnable) != 0) { ImGui.UpdatePlatformWindows(); ImGui.RenderPlatformWindowsDefault(); } SDL.RenderPresent(Renderer); } ~SDL3Window() { Dispose(false); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } private void Dispose(bool disposing) { if (_disposed) return; _disposed = true; if (disposing) { ImGuiSDL3Renderer.Dispose(); ImGuiSDL3Platform.Dispose(); RenderCallback = null; } if (_imGuiContext.Handle != null) { ImGui.SetCurrentContext(null); ImGui.DestroyContext(_imGuiContext); _imGuiContext = null; } if (Renderer != nint.Zero) { SDL.DestroyRenderer(Renderer); } if (Window != nint.Zero) { SDL.DestroyWindow(Window); } } }