361 lines
11 KiB
C#
361 lines
11 KiB
C#
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<Vector4>? Resized;
|
|
public event Action<SDL.Event>? 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<string> fonts = Directory.GetFiles(fontsPath, "*.ttf", SearchOption.AllDirectories).ToList();
|
|
fonts.Sort();
|
|
|
|
List<string> mergeFonts = new List<string>();
|
|
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;
|
|
}
|
|
}
|