using System.Numerics; using Hexa.NET.ImGui; using Hexa.NET.ImGuizmo; using Hexa.NET.ImPlot; using Hexa.NET.ImNodes; using Hexa.NET.OpenGL; using HexaGen.Runtime; using SDL3; namespace SDL3_TestingSuite.SDL3; public sealed unsafe class SDL3Window : IDisposable { public readonly nint Window; public readonly Vector4 DefaultClearColor = new Vector4(0.06f, 0.06f, 0.06f, 1f); private Vector4? _clearColor; public Vector4 ClearColor { get => _clearColor ?? DefaultClearColor; set => _clearColor = value; } public event Action? RenderCallback; public event Action? Disposing; public event Action? Resized; public event Action? EventCallback; private ImGuiContextPtr _imGuiContext; private ImPlotContextPtr _imPlotContext; private ImNodesContextPtr _imNodesContext; // private ImPLot3DContextPtr _imPlot3DContext; public bool Disposed => _disposed; private bool _disposed; private readonly FileSystemWatcher? _watcher; public readonly GL GL; public SDL3Window(string name, int posX, int posY, int width, int height, SDL.WindowFlags flags = SDL.WindowFlags.Resizable | SDL.WindowFlags.HighPixelDensity) { if (OperatingSystem.IsLinux()) SDL.SetHint(SDL.Hints.VideoDriver, "x11"); if (!SDL.Init(SDL.InitFlags.Events | SDL.InitFlags.Video | SDL.InitFlags.Gamepad)) throw new Exception($"SDL_Init failed: {SDL.GetError()}"); SDL.GLSetAttribute(SDL.GLAttr.ContextFlags, (int)SDL.GLContextFlag.ForwardCompatible); SDL.GLSetAttribute(SDL.GLAttr.ContextProfileMask, (int)SDL.GLProfile.Core); SDL.GLSetAttribute(SDL.GLAttr.ContextMajorVersion, 4); SDL.GLSetAttribute(SDL.GLAttr.ContextMinorVersion, 6); SDL.SetHint(SDL.Hints.IMEImplementedUI, "1"); // Create window & renderer SDL.GLSetAttribute(SDL.GLAttr.DoubleBuffer, 1); SDL.GLSetAttribute(SDL.GLAttr.DepthSize, 24); SDL.GLSetAttribute(SDL.GLAttr.StencilSize, 8); Window = SDL.CreateWindow(name, width, height, flags | SDL.WindowFlags.OpenGL); if (Window == nint.Zero) throw new Exception($"SDL_CreateWindowAndRenderer failed: {SDL.GetError()}"); nint glContext = SDL.GLCreateContext(Window); if (glContext == nint.Zero) throw new Exception($"SDL_GL_CreateContext failed: {SDL.GetError()}"); SDL.GLMakeCurrent(Window, glContext); SDL.GLSetSwapInterval(1); SDL.SetWindowPosition(Window, posX, posY); // SDL.SetRenderVSync(Renderer, 1); SDL.ShowWindow(Window); // Create ImGui context _imGuiContext = ImGui.CreateContext(); _imPlotContext = ImPlot.CreateContext(); _imNodesContext = ImNodes.CreateContext(); // _imPlot3DContext = ImPlot3D.CreateContext(); ImPlot.SetImGuiContext(_imGuiContext); ImGuizmo.SetImGuiContext(_imGuiContext); ImNodes.SetImGuiContext(_imGuiContext); // ImPlot3D.SetImGuiContext(_imGuiContext); 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 += (_, b) => { if (!File.Exists(b.FullPath)) return; if (Path.GetExtension(b.FullPath) != ".ttf") return; ImGui.GetIO().AddFont(b.FullPath); }; _watcher.Deleted += (_, b) => { if (Path.GetExtension(b.FullPath) != ".ttf") return; ImGui.GetIO().RemoveFont(b.Name); }; _watcher.Renamed += (_, b) => { if (Path.GetExtension(b.OldFullPath) != ".ttf") return; ImGui.GetIO().RemoveFont(b.OldName); if (!File.Exists(b.FullPath)) return; if (Path.GetExtension(b.FullPath) != ".ttf") return; ImGui.GetIO().AddFont(b.FullPath); }; _watcher.IncludeSubdirectories = false; _watcher.EnableRaisingEvents = true; List fonts = Directory.GetFiles(fontsPath, "*.ttf", SearchOption.AllDirectories).ToList(); fonts.Sort(); List mergeFonts = new List(); for (int i = fonts.Count - 1; i >= 0; i--) { if (Path.GetFileName(fonts[i]).StartsWith("merge_", StringComparison.OrdinalIgnoreCase)) { mergeFonts.Add(fonts[i]); fonts.RemoveAt(i); } } mergeFonts.Reverse(); foreach (string merge in mergeFonts) { io.AddFont(merge, true); } foreach (string font in fonts) { io.AddFont(font); foreach (string merge in mergeFonts) { io.AddFont(merge, true); } } } catch (Exception e) { Console.WriteLine(e); } GL = new GL(new BindingsContext(Window, glContext)); // Init platform and renderer ImGuiSDL3Platform.Init(GL, Window, glContext); ImGuiSDL3Renderer.Init(GL, "#version 330"); } public bool ShouldClose; public void Run() { while (!_disposed) { SDL.PumpEvents(); 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; case SDL.EventType.WindowResized: case SDL.EventType.WindowMoved: SDL.GetWindowSize(Window, out int w, out int h); SDL.GetWindowPosition(Window, out int x, out int y); Resized?.Invoke(new Vector4(x, y, w, h)); break; } EventCallback?.Invoke(ev); } if (SDL.GetWindowFlags(Window).HasFlag(SDL.WindowFlags.Minimized)) { SDL.Delay(10); continue; } GL.MakeCurrent(); GL.ClearColor(ClearColor.X, ClearColor.Y, ClearColor.Z, ClearColor.W); GL.Clear(GLClearBufferMask.ColorBufferBit); ImGuiSDL3Platform.NewFrame(); ImGuiSDL3Renderer.NewFrame(); ImGui.NewFrame(); RenderCallback?.Invoke(); ImGuiIOPtr io = ImGui.GetIO(); ImGui.Render(); GL.MakeCurrent(); ImGuiSDL3Renderer.RenderDrawData(ImGui.GetDrawData()); if ((io.ConfigFlags & ImGuiConfigFlags.ViewportsEnable) != 0) { ImGui.UpdatePlatformWindows(); ImGui.RenderPlatformWindowsDefault(); } GL.MakeCurrent(); GL.SwapBuffers(); if (ShouldClose) { Dispose(); break; } } if (!_disposed) Dispose(); } ~SDL3Window() { Dispose(false); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } private void Dispose(bool disposing) { if (_disposed) return; _disposed = true; if (disposing) { if (_imGuiContext.Handle != null) { ImGui.SetCurrentContext(_imGuiContext); } if (_imPlotContext.Handle != null) { ImPlot.SetCurrentContext(_imPlotContext); } Disposing?.Invoke(); ImGuiSDL3Renderer.Dispose(); ImGuiSDL3Platform.Dispose(); RenderCallback = null; Disposing = null; } if (_imGuiContext.Handle != null) { ImGui.SetCurrentContext(null); ImGui.DestroyContext(_imGuiContext); _imGuiContext = null; } if (_imPlotContext.Handle != null) { ImPlot.SetCurrentContext(null); ImPlot.DestroyContext(_imPlotContext); _imPlotContext = null; } if (_imNodesContext.Handle != null) { ImNodes.SetCurrentContext(null); ImNodes.DestroyContext(_imNodesContext); _imNodesContext = null; } // if (_imPlot3DContext.Handle != null) // { // ImPlot3D.SetCurrentContext(null); // ImPlot3D.DestroyContext(_imPlot3DContext); // _imPlot3DContext = null; // } // if (Renderer != nint.Zero) // { // SDL.DestroyRenderer(Renderer); // } if (Window != nint.Zero) { SDL.DestroyWindow(Window); } } } public unsafe class BindingsContext : IGLContext { private readonly nint window; private readonly nint context; public BindingsContext(nint window, nint context) { this.window = window; this.context = context; } public nint Handle => window; public bool IsCurrent => SDL.GLGetCurrentContext() == context; public void Dispose() { } public nint GetProcAddress(string procName) { return (nint)SDL.GLGetProcAddress(procName).GetPtrForDelegate(); } public bool IsExtensionSupported(string extensionName) { return SDL.GLExtensionSupported(extensionName); } public void MakeCurrent() { SDL.GLMakeCurrent(window, context); } public void SwapBuffers() { SDL.GLSwapWindow(window); } public void SwapInterval(int interval) { SDL.GLSetSwapInterval(interval); } public bool TryGetProcAddress(string procName, out nint procAddress) { procAddress = (nint)SDL.GLGetProcAddress(procName).GetPtrForDelegate(); return procAddress != 0; } }