From 7d5f242a437ed85c9f3eb202d61f46992de57ac6 Mon Sep 17 00:00:00 2001 From: NepuShiro Date: Wed, 20 May 2026 04:23:47 -0500 Subject: [PATCH] opengl4 --- Program.cs | 202 +++++------ SDL3 TestingSuite.csproj | 2 +- SDL3/ImGuiSDL3Platform.cs | 711 ++++++++++++++++++++------------------ SDL3/ImGuiSDL3Renderer.cs | 32 +- SDL3/SDL3Window.cs | 9 +- 5 files changed, 502 insertions(+), 454 deletions(-) diff --git a/Program.cs b/Program.cs index 86a51e3..d898eb4 100644 --- a/Program.cs +++ b/Program.cs @@ -26,155 +26,141 @@ public class Program ImGui.SetNextWindowSize(viewport.WorkSize); ImGui.SetNextWindowViewport(viewport.ID); - if (ImGui.Begin("MainWindow", ImGuiWindowFlags.NoBackground | ImGuiWindowFlags.MenuBar | ImGuiWindowFlags.NoDocking | ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoBringToFrontOnFocus)) + ImGui.Begin("MainWindow", ImGuiWindowFlags.NoBackground | ImGuiWindowFlags.MenuBar | ImGuiWindowFlags.NoDocking | ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoBringToFrontOnFocus); + if (ImGui.BeginMenuBar()) { - if (ImGui.BeginMenuBar()) + if (ImGui.BeginMenu("Stuff")) { - if (ImGui.BeginMenu("Stuff")) + if (ImGui.MenuItem("Quit")) { - if (ImGui.MenuItem("Quit")) - { - window.ShouldClose = true; - } - - ImGui.EndMenu(); + window.ShouldClose = true; } - ImGui.Spacing(); - ImGui.MenuItem("Demo Window", "", ref _demoWindowVisible); - ImGui.Spacing(); - ImGui.MenuItem("ImPlot Demo Window", "", ref _imPlotDemoVisible); - ImGui.Spacing(); - ImGui.MenuItem("Font Stuff", "", ref _fontStuff); - ImGui.EndMenuBar(); + ImGui.EndMenu(); } + ImGui.Spacing(); + ImGui.MenuItem("Demo Window", "", ref _demoWindowVisible); + ImGui.Spacing(); + ImGui.MenuItem("ImPlot Demo Window", "", ref _imPlotDemoVisible); + ImGui.Spacing(); + ImGui.MenuItem("Font Stuff", "", ref _fontStuff); - ImGui.End(); + ImGui.EndMenuBar(); } + ImGui.End(); if (_fontStuff) { ImGui.SetNextWindowSize(new Vector2(250, 500), ImGuiCond.FirstUseEver); - if (ImGui.Begin("FontStuff", ref _fontStuff)) + ImGui.Begin("FontStuff", ref _fontStuff); + + ImGui.ShowFontSelector("Font"); + + bool change = false; + if (ImGui.GetFont() != _font) { - ImGui.ShowFontSelector("Font"); + _font = ImGui.GetFont(); + change = true; + } - bool change = false; - if (ImGui.GetFont() != _font) + string fontName = _font.GetDebugNameS(); + fontName = string.IsNullOrEmpty(fontName) ? _font.FontId.ToString() : fontName; + if (!_initialized || change) + { + if (!GlyphsByName.TryGetValue(fontName, out List? glyphs)) { - _font = ImGui.GetFont(); - change = true; - } + glyphs = new List(); - string fontName = _font.GetDebugNameS(); - fontName = string.IsNullOrEmpty(fontName) ? _font.FontId.ToString() : fontName; - if (!_initialized || change) - { - if (!GlyphsByName.TryGetValue(fontName, out List? glyphs)) + ImFontPtr font = _font; + + for (uint codepoint = 0; codepoint <= 0x10FFFF; codepoint++) { - glyphs = new List(); - - ImFontPtr font = _font; - - for (uint codepoint = 0; codepoint <= 0x10FFFF; codepoint++) + if (codepoint >= 0xD800 && codepoint <= 0xDFFF) { - if (codepoint >= 0xD800 && codepoint <= 0xDFFF) - { - continue; - } - - if (!font.IsGlyphInFont(codepoint)) continue; - - glyphs.Add(codepoint); + continue; } - GlyphsByName[fontName] = glyphs; - Logger.Log($"Initialized font {fontName} with {glyphs.Count} glyphs"); + if (!font.IsGlyphInFont(codepoint)) continue; + + glyphs.Add(codepoint); } - _initialized = true; + GlyphsByName[fontName] = glyphs; + Logger.Log($"Initialized font {fontName} with {glyphs.Count} glyphs"); } - ImGui.PushFont(null, 24); - - if (GlyphsByName.TryGetValue(fontName, out List? glyphs2)) - { - float cursorXStart = ImGui.GetCursorPosX(); - float maxWidth = ImGui.GetContentRegionAvail().X; - - foreach (uint codepoint in glyphs2!) - { - string text = char.ConvertFromUtf32((int)codepoint); - float glyphWidth = ImGui.CalcTextSize(text).X; - - float cursorX = ImGui.GetCursorPosX(); - if (cursorX > cursorXStart && (cursorX + glyphWidth) > (cursorXStart + maxWidth)) - { - ImGui.NewLine(); - } - - ImGui.TextUnformatted(text); - - ImGui.SameLine(); - } - } - - ImGui.PopFont(); - - ImGui.End(); + _initialized = true; } - } - if (ImGui.Begin("Thing")) - { - float time = (float)ImGui.GetTime() * 5; - if (ImPlot.BeginPlot("Moving Rainbow Sine Wave")) + ImGui.PushFont(null, 24); + + if (GlyphsByName.TryGetValue(fontName, out List? glyphs2)) { - int count = 200; + float cursorXStart = ImGui.GetCursorPosX(); + float maxWidth = ImGui.GetContentRegionAvail().X; - float[] xs = new float[2]; - float[] ys = new float[2]; - - for (int i = 0; i < count - 1; i++) + foreach (uint codepoint in glyphs2!) { - float x0 = i * 0.1f; - float x1 = (i + 1) * 0.1f; + string text = char.ConvertFromUtf32((int)codepoint); + float glyphWidth = ImGui.CalcTextSize(text).X; - float y0 = MathF.Sin(x0 * size + time); - float y1 = MathF.Sin(x1 * size + time); + float cursorX = ImGui.GetCursorPosX(); + if (cursorX > cursorXStart && (cursorX + glyphWidth) > (cursorXStart + maxWidth)) + { + ImGui.NewLine(); + } - xs[0] = x0; - xs[1] = x1; - - ys[0] = y0; - ys[1] = y1; - - float t = i / (float)count; - - float r = 0.5f + 1f * MathF.Sin(6.2831f * (t)); - float g = 0.5f + 1f * MathF.Sin(6.2831f * (t + 0.33f)); - float b = 0.5f + 1f * MathF.Sin(6.2831f * (t + 0.66f)); - - - ImPlot.PushStyleColor(ImPlotCol.Line, new Vector4(r, g, b, 1f)); - ImPlot.PlotLine("##seg", ref xs[0], ref ys[0], 2); - ImPlot.PopStyleColor(); + ImGui.TextUnformatted(text); + ImGui.SameLine(); } - - ImPlot.EndPlot(); } - ImGui.SliderFloat("Sine", ref size, 1f, 120f); + ImGui.PopFont(); ImGui.End(); } - if (ImGui.Begin("ImageThing")) + ImGui.Begin("Thing"); + float time = (float)ImGui.GetTime() * 5; + if (ImPlot.BeginPlot("Moving Rainbow Sine Wave")) { + int count = 200; - ImGui.End(); + float[] xs = new float[2]; + float[] ys = new float[2]; + + for (int i = 0; i < count - 1; i++) + { + float x0 = i * 0.1f; + float x1 = (i + 1) * 0.1f; + + float y0 = MathF.Sin(x0 * size + time); + float y1 = MathF.Sin(x1 * size + time); + + xs[0] = x0; + xs[1] = x1; + + ys[0] = y0; + ys[1] = y1; + + float t = i / (float)count; + + float r = 0.5f + 1f * MathF.Sin(6.2831f * (t)); + float g = 0.5f + 1f * MathF.Sin(6.2831f * (t + 0.33f)); + float b = 0.5f + 1f * MathF.Sin(6.2831f * (t + 0.66f)); + + + ImPlot.PushStyleColor(ImPlotCol.Line, new Vector4(r, g, b, 1f)); + ImPlot.PlotLine("##seg", ref xs[0], ref ys[0], 2); + ImPlot.PopStyleColor(); + + } + + ImPlot.EndPlot(); } - + ImGui.SliderFloat("Sine", ref size, 1f, 120f); + ImGui.End(); + if (_demoWindowVisible) { ImGui.ShowDemoWindow(ref _demoWindowVisible); diff --git a/SDL3 TestingSuite.csproj b/SDL3 TestingSuite.csproj index d0ccddd..97336e7 100644 --- a/SDL3 TestingSuite.csproj +++ b/SDL3 TestingSuite.csproj @@ -15,7 +15,7 @@ - + diff --git a/SDL3/ImGuiSDL3Platform.cs b/SDL3/ImGuiSDL3Platform.cs index 1a2a28b..e9aa0e8 100644 --- a/SDL3/ImGuiSDL3Platform.cs +++ b/SDL3/ImGuiSDL3Platform.cs @@ -18,6 +18,7 @@ public unsafe static class ImGuiSDL3Platform public nint Window; public uint WindowID; // public nint Renderer; + public GL Gl = null!; public ulong Time; public nint ClipboardTextData; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 48)] @@ -35,7 +36,7 @@ public unsafe static class ImGuiSDL3Platform public int MouseButtonsDown; public readonly nint[] MouseCursors = new nint[(int)ImGuiMouseCursor.Count]; public nint MouseLastCursor; - public int MouseLastLeaveFrame; + public int MousePendingLeaveFrame; public bool MouseCanUseGlobalState; public bool MouseCanReportHoveredViewport; public MouseCaptureMode MouseCaptureMode; @@ -60,21 +61,16 @@ public unsafe static class ImGuiSDL3Platform public static PlatformData GetPlatformData() => ImGui.GetCurrentContext().Handle != null ? ImGuiUserData.Get(ImGui.GetIO().BackendPlatformUserData)! : null!; - public static SDL3Window? Parent; - private static GL _gl; private static nint _glContext; - - public static bool Init(GL gl, nint ctx, nint window, SDL3Window? parentWindow = null) + public static bool Init(GL gl, nint window, nint ctx) { - _gl = gl; _glContext = ctx; - Parent = parentWindow; ImGuiIOPtr io = ImGui.GetIO(); PlatformData data = new PlatformData { Window = window, - // Renderer = renderer, - WindowID = SDL.GetWindowID(window) + WindowID = SDL.GetWindowID(window), + Gl = gl }; io.BackendPlatformUserData = ImGuiUserData.Store(data); @@ -84,19 +80,25 @@ public unsafe static class ImGuiSDL3Platform data.MouseCanUseGlobalState = false; data.MouseCaptureMode = MouseCaptureMode.Disabled; + // Check and store if we are on a SDL backend that supports SDL_GetGlobalMouseState() and SDL_CaptureMouse() + // ("wayland" and "rpi" don't support it, but we chose to use a white-list instead of a black-list) string? backend = SDL.GetCurrentVideoDriver(); - string[] captureAndGlobalStateWhitelist = new string[] { "windows", "cocoa", "x11", "DIVE", "VMAN" }; + string[] captureAndGlobalStateWhitelist = { "windows", "cocoa", "x11", "DIVE", "VMAN" }; foreach (string item in captureAndGlobalStateWhitelist) { if (item != backend) continue; data.MouseCanUseGlobalState = true; data.MouseCaptureMode = item == "x11" ? MouseCaptureMode.EnabledAfterDrag : MouseCaptureMode.Enabled; } + if (data.MouseCanUseGlobalState) { - io.BackendFlags |= ImGuiBackendFlags.PlatformHasViewports; // We can create multi-viewports on the Platform side (optional) + io.BackendFlags |= ImGuiBackendFlags.PlatformHasViewports; + // io.BackendFlags |= ImGuiBackendFlags.HasParentViewport; } + // SDL on Linux/OSX doesn't report events for unfocused windows (see https://github.com/ocornut/imgui/issues/4960) + // We will use MouseCanReportHoveredViewport to set HasMouseHoveredViewport dynamically each frame. #if __APPLE__ data.MouseCanReportHoveredViewport = false; #else @@ -138,17 +140,21 @@ public unsafe static class ImGuiSDL3Platform return true; } + public static void NewFrame() { ImGuiIOPtr io = ImGui.GetIO(); PlatformData data = GetPlatformData(); GetWindowSizeAndFramebufferScale(data.Window, out Vector2 size, out Vector2 framebufferScale); + io.DisplaySize = size; + io.DisplayFramebufferScale = framebufferScale; +#if WIN32 + data.WantUpdateMonitors = true; +#endif if (data.WantUpdateMonitors) - { UpdateMonitors(); - } ulong frequency = SDL.GetPerformanceFrequency(); ulong currentTime = SDL.GetPerformanceCounter(); @@ -157,24 +163,17 @@ public unsafe static class ImGuiSDL3Platform io.DeltaTime = data.Time > 0 ? (float)((currentTime - data.Time) / (double)frequency) : 1.0f / 60.0f; data.Time = currentTime; - io.DisplaySize = size; - io.DisplayFramebufferScale = framebufferScale; - - if (data.MouseLastLeaveFrame > 0 && data.MouseLastLeaveFrame >= ImGui.GetFrameCount() && data.MouseButtonsDown == 0) + if (data.MousePendingLeaveFrame != 0 && data.MousePendingLeaveFrame >= ImGui.GetFrameCount() && data.MouseButtonsDown == 0) { data.MouseWindowID = 0; - data.MouseLastLeaveFrame = 0; + data.MousePendingLeaveFrame = 0; io.AddMousePosEvent(-float.MaxValue, -float.MaxValue); } if (data.MouseCanReportHoveredViewport && ImGui.GetDragDropPayload().IsNull) - { io.BackendFlags |= ImGuiBackendFlags.HasMouseHoveredViewport; - } else - { io.BackendFlags &= ~ImGuiBackendFlags.HasMouseHoveredViewport; - } UpdateMouseData(); UpdateMouseCursor(); @@ -218,9 +217,15 @@ public unsafe static class ImGuiSDL3Platform if (SDL.GetWindowFlags(window).HasFlag(SDL.WindowFlags.Minimized)) w = h = 0; +#if __APPLE__ + // SDL_GetWindowDisplayScale() seems more reliable during resolution changes e.g. going fullscreen (#8703, #4414) + float fbScaleX = SDL.GetWindowDisplayScale(window); + float fbScaleY = fbScaleX; +#else SDL.GetWindowSizeInPixels(window, out int displayW, out int displayH); float fbScaleX = w > 0 ? displayW / (float)w : 1.0f; float fbScaleY = h > 0 ? displayH / (float)h : 1.0f; +#endif out_size = new Vector2(w, h); out_framebuffer_scale = new Vector2(fbScaleX, fbScaleY); @@ -234,26 +239,35 @@ public unsafe static class ImGuiSDL3Platform switch ((SDL.EventType)e.Type) { case SDL.EventType.MouseMotion: + { if (GetViewportForWindowId(e.Motion.WindowID) == null) return false; + Vector2 mousePos = new Vector2(e.Motion.X, e.Motion.Y); + if (io.ConfigFlags.HasFlag(ImGuiConfigFlags.ViewportsEnable)) + { + SDL.GetWindowPosition(SDL.GetWindowFromID(e.Motion.WindowID), out int windowX, out int windowY); + mousePos.X += windowX; + mousePos.Y += windowY; + } io.AddMouseSourceEvent(e.Motion.Which == SDL.TouchMouseID ? ImGuiMouseSource.TouchScreen : ImGuiMouseSource.Mouse); - // ImGui expects desktop/global coordinates when viewports are enabled. - SDL.GetGlobalMouseState(out float mouseX, out float mouseY); - io.AddMousePosEvent(mouseX, mouseY); + io.AddMousePosEvent(mousePos.X, mousePos.Y); return true; + } case SDL.EventType.MouseWheel: + { if (GetViewportForWindowId(e.Wheel.WindowID) == null) return false; float wheelX = -e.Wheel.X; float wheelY = e.Wheel.Y; - io.AddMouseSourceEvent(e.Wheel.Which == SDL.TouchMouseID ? ImGuiMouseSource.TouchScreen : ImGuiMouseSource.Mouse); io.AddMouseWheelEvent(wheelX, wheelY); return true; + } case SDL.EventType.MouseButtonDown: case SDL.EventType.MouseButtonUp: + { if (GetViewportForWindowId(e.Button.WindowID) == null) return false; @@ -272,45 +286,90 @@ public unsafe static class ImGuiSDL3Platform io.AddMouseButtonEvent(mouseButton, (SDL.EventType)e.Type == SDL.EventType.MouseButtonDown); data.MouseButtonsDown = (SDL.EventType)e.Type == SDL.EventType.MouseButtonDown ? data.MouseButtonsDown | 1 << mouseButton : data.MouseButtonsDown & ~(1 << mouseButton); return true; + } case SDL.EventType.TextInput: + { if (GetViewportForWindowId(e.Text.WindowID) == null) return false; - ImGui.GetIO().AddInputCharactersUTF8((byte*)e.Text.Text); + io.AddInputCharactersUTF8((byte*)e.Text.Text); return true; + } case SDL.EventType.KeyDown: case SDL.EventType.KeyUp: + { if (GetViewportForWindowId(e.Key.WindowID) == null) return false; UpdateKeyModifiers(e.Key.Mod); ImGuiKey imguiKey = KeyEventToImGui(e.Key.Key, e.Key.Scancode); bool pressed = (SDL.EventType)e.Type == SDL.EventType.KeyDown; - io.AddKeyEvent(imguiKey, pressed); io.SetKeyEventNativeData(imguiKey, (int)e.Key.Key, (int)e.Key.Scancode, (int)e.Key.Scancode); - return true; + } + case SDL.EventType.DisplayOrientation: + case SDL.EventType.DisplayAdded: + case SDL.EventType.DisplayRemoved: + case SDL.EventType.DisplayMoved: + case SDL.EventType.DisplayContentScaleChanged: + // SDL_HAS_EVENT_DISPLAY_USABLE_BOUNDS_CHANGED (SDL 3.4.0+): add DisplayUsableBoundsChanged here when available + { + data.WantUpdateMonitors = true; + return true; + } case SDL.EventType.WindowMouseEnter: + { if (GetViewportForWindowId(e.Window.WindowID) == null) return false; data.MouseWindowID = e.Window.WindowID; - data.MouseLastLeaveFrame = 0; + data.MousePendingLeaveFrame = 0; return true; + } + // In some cases, when detaching a window from main viewport SDL may send SDL_WINDOWEVENT_ENTER one frame + // too late, causing SDL_WINDOWEVENT_LEAVE on previous frame to interrupt drag operation by clearing mouse + // position. This is why we delay processing the SDL_WINDOWEVENT_LEAVE events by one frame. + // See issue #5012 for details. FIXME: Unconfirmed whether this is still needed with SDL3. case SDL.EventType.WindowMouseLeave: + { if (GetViewportForWindowId(e.Window.WindowID) == null) return false; - data.MouseLastLeaveFrame = ImGui.GetFrameCount() + 1; + data.MousePendingLeaveFrame = ImGui.GetFrameCount() + 1; return true; + } case SDL.EventType.WindowFocusGained: case SDL.EventType.WindowFocusLost: + { if (GetViewportForWindowId(e.Window.WindowID) == null) return false; io.AddFocusEvent((SDL.EventType)e.Type == SDL.EventType.WindowFocusGained); return true; + } + case SDL.EventType.WindowCloseRequested: + case SDL.EventType.WindowMoved: + case SDL.EventType.WindowResized: + { + ImGuiViewportPtr? viewport = GetViewportForWindowId(e.Window.WindowID); + if (viewport == null) + return false; + + if ((SDL.EventType)e.Type == SDL.EventType.WindowCloseRequested) + viewport.Value.PlatformRequestClose = true; + if ((SDL.EventType)e.Type == SDL.EventType.WindowMoved) + viewport.Value.PlatformRequestMove = true; + if ((SDL.EventType)e.Type == SDL.EventType.WindowResized) + viewport.Value.PlatformRequestResize = true; + return true; + } + case SDL.EventType.GamepadAdded: + case SDL.EventType.GamepadRemoved: + { + data.WantUpdateGamepadsList = true; + return true; + } } return false; @@ -322,9 +381,11 @@ public unsafe static class ImGuiSDL3Platform PlatformData bd = GetPlatformData(); ImGuiIOPtr io = ImGui.GetIO(); - // We forward mouse input when hovered or captured (via SDL.MOUSEMOTION) or when focused (below) - // - SDL.CaptureMouse() let the OS know e.g. that our drags can extend outside of parent boundaries (we want updated position) and shouldn't trigger other operations outside. - // - Debuggers under Linux tends to leave captured mouse on break, which may be very inconvenient, so to mitigate the issue on X11 we we wait until mouse has moved to begin capture. + // We forward mouse input when hovered or captured (via SDL_EVENT_MOUSE_MOTION) or when focused (below). + // SDL_CaptureMouse() lets the OS know e.g. that our drags can extend outside of parent boundaries (we want + // updated position) and shouldn't trigger other operations outside. + // Debuggers under Linux tend to leave captured mouse on break, which may be very inconvenient, so to mitigate + // the issue on X11 we wait until mouse has moved to begin capture. if (bd.MouseCaptureMode == MouseCaptureMode.Enabled) { SDL.CaptureMouse(bd.MouseButtonsDown != 0); @@ -345,23 +406,28 @@ public unsafe static class ImGuiSDL3Platform if (isAppFocused) { - // (Optional) Set OS mouse position from Dear ImGui if requested (rarely used, only when io.ConfigNavMoveSetMousePos is enabled by user) + // (Optional) Set OS mouse position from Dear ImGui if requested (rarely used, only when + // io.ConfigNavMoveSetMousePos is enabled by user) if (io.WantSetMousePos) { if (io.ConfigFlags.HasFlag(ImGuiConfigFlags.ViewportsEnable)) - SDL.WarpMouseGlobal((int)io.MousePos.X, (int)io.MousePos.Y); + SDL.WarpMouseGlobal(io.MousePos.X, io.MousePos.Y); else - SDL.WarpMouseInWindow(bd.Window, (int)io.MousePos.X, (int)io.MousePos.Y); + SDL.WarpMouseInWindow(bd.Window, io.MousePos.X, io.MousePos.Y); } - // (Optional) Fallback to provide unclamped mouse position when focused but not hovered (SDL.MOUSEMOTION already provides this when hovered or captured) - // Note that SDL.GetGlobalMouseState() is in theory slow on X11, but this only runs on rather specific cases. If a problem we may provide a way to opt-out this feature. + // (Optional) Fallback to provide unclamped mouse position when focused but not hovered + // (SDL_EVENT_MOUSE_MOTION already provides this when hovered or captured) + // Note that SDL_GetGlobalMouseState() is in theory slow on X11, but this only runs on rather specific + // cases. If a problem we may provide a way to opt-out this feature. nint hoveredWindow = SDL.GetMouseFocus(); - bool isRelativeMouseMode = false /*SDL.GetRelativeMouseMode() != 0*/; + bool isRelativeMouseMode = SDL.GetWindowRelativeMouseMode(bd.Window); if (hoveredWindow == nint.Zero && bd.MouseCanUseGlobalState && bd.MouseButtonsDown == 0 && !isRelativeMouseMode) { - // Single-viewport mode: mouse position in client window coordinates (io.MousePos is (0,0) when the mouse is on the upper-left corner of the app window) - // Multi-viewport mode: mouse position in OS absolute coordinates (io.MousePos is (0,0) when the mouse is on the upper-left of the primary monitor) + // Single-viewport mode: mouse position in client window coordinates + // (io.MousePos is (0,0) when the mouse is on the upper-left corner of the app window) + // Multi-viewport mode: mouse position in OS absolute coordinates + // (io.MousePos is (0,0) when the mouse is on the upper-left of the primary monitor) SDL.GetGlobalMouseState(out float mouseX, out float mouseY); if (!io.ConfigFlags.HasFlag(ImGuiConfigFlags.ViewportsEnable)) { @@ -373,13 +439,16 @@ public unsafe static class ImGuiSDL3Platform } } - // (Optional) When using multiple viewports: call io.AddMouseViewportEvent() with the viewport the OS mouse cursor is hovering. - // If ImGuiBackendFlags_HasMouseHoveredViewport is not set by the backend, Dear imGui will ignore this field and infer the information using its flawed heuristic. + // (Optional) When using multiple viewports: call io.AddMouseViewportEvent() with the viewport the OS mouse + // cursor is hovering. If ImGuiBackendFlags_HasMouseHoveredViewport is not set by the backend, Dear ImGui will + // ignore this field and infer the information using its flawed heuristic. // - [!] SDL backend does NOT correctly ignore viewports with the _NoInputs flag. - // Some backend are not able to handle that correctly. If a backend report an hovered viewport that has the _NoInputs flag (e.g. when dragging a window - // for docking, the viewport has the _NoInputs flag in order to allow us to find the viewport under), then Dear ImGui is forced to ignore the value reported + // Some backends are not able to handle that correctly. If a backend reports a hovered viewport that has + // the _NoInputs flag (e.g. when dragging a window for docking, the viewport has the _NoInputs flag in + // order to allow us to find the viewport under), then Dear ImGui is forced to ignore the value reported // by the backend, and use its flawed heuristic to guess the viewport behind. - // - [X] SDL backend correctly reports this regardless of another viewport behind focused and dragged from (we need this to find a useful drag and drop target). + // - [X] SDL backend correctly reports this regardless of another viewport behind focused and dragged from + // (we need this to find a useful drag and drop target). if (io.BackendFlags.HasFlag(ImGuiBackendFlags.HasMouseHoveredViewport)) { uint mouseViewportID = 0; @@ -427,112 +496,88 @@ public unsafe static class ImGuiSDL3Platform CloseGamepads(); uint[] sdlGamepads = SDL.GetGamepads(out int count)!; - for (int n = 0; n < count; n++) { if (data.GamepadCount >= data.Gamepads.Length) break; - data.Gamepads[data.GamepadCount++] = SDL.OpenGamepad(sdlGamepads[n]); - - if (data.GamepadMode == GamepadMode.AutoFirst) - break; + nint gamepad = SDL.OpenGamepad(sdlGamepads[n]); + if (gamepad != nint.Zero) + { + data.Gamepads[data.GamepadCount++] = gamepad; + if (data.GamepadMode == GamepadMode.AutoFirst) + break; + } } data.WantUpdateGamepadsList = false; } io.BackendFlags &= ~ImGuiBackendFlags.HasGamepad; - if (data.GamepadCount == 0) return; - io.BackendFlags |= ImGuiBackendFlags.HasGamepad; - const int thumbDeadZone = 8000; - + // Update gamepad inputs + const int thumbDeadZone = 8000; // SDL_gamepad.h suggests using this value. UpdateGamepadButton(data, io, ImGuiKey.GamepadStart, SDL.GamepadButton.Start); UpdateGamepadButton(data, io, ImGuiKey.GamepadBack, SDL.GamepadButton.Back); - UpdateGamepadButton(data, io, ImGuiKey.GamepadFaceLeft, SDL.GamepadButton.West); - UpdateGamepadButton(data, io, ImGuiKey.GamepadFaceRight, SDL.GamepadButton.East); - UpdateGamepadButton(data, io, ImGuiKey.GamepadFaceUp, SDL.GamepadButton.North); - UpdateGamepadButton(data, io, ImGuiKey.GamepadFaceDown, SDL.GamepadButton.South); + UpdateGamepadButton(data, io, ImGuiKey.GamepadFaceLeft, SDL.GamepadButton.West); // Xbox X, PS Square + UpdateGamepadButton(data, io, ImGuiKey.GamepadFaceRight, SDL.GamepadButton.East); // Xbox B, PS Circle + UpdateGamepadButton(data, io, ImGuiKey.GamepadFaceUp, SDL.GamepadButton.North); // Xbox Y, PS Triangle + UpdateGamepadButton(data, io, ImGuiKey.GamepadFaceDown, SDL.GamepadButton.South); // Xbox A, PS Cross UpdateGamepadButton(data, io, ImGuiKey.GamepadDpadLeft, SDL.GamepadButton.DPadLeft); UpdateGamepadButton(data, io, ImGuiKey.GamepadDpadRight, SDL.GamepadButton.DPadRight); UpdateGamepadButton(data, io, ImGuiKey.GamepadDpadUp, SDL.GamepadButton.DPadUp); UpdateGamepadButton(data, io, ImGuiKey.GamepadDpadDown, SDL.GamepadButton.DPadDown); UpdateGamepadButton(data, io, ImGuiKey.GamepadL1, SDL.GamepadButton.LeftShoulder); UpdateGamepadButton(data, io, ImGuiKey.GamepadR1, SDL.GamepadButton.RightShoulder); - UpdateGamepadAnalog(data, io, ImGuiKey.GamepadL2, SDL.GamepadAxis.LeftTrigger, 0.0f, 32767); UpdateGamepadAnalog(data, io, ImGuiKey.GamepadR2, SDL.GamepadAxis.RightTrigger, 0.0f, 32767); - UpdateGamepadButton(data, io, ImGuiKey.GamepadL3, SDL.GamepadButton.LeftStick); UpdateGamepadButton(data, io, ImGuiKey.GamepadR3, SDL.GamepadButton.RightStick); - UpdateGamepadAnalog(data, io, ImGuiKey.GamepadLStickLeft, SDL.GamepadAxis.LeftX, -thumbDeadZone, -32768); - UpdateGamepadAnalog(data, io, ImGuiKey.GamepadLStickRight, SDL.GamepadAxis.LeftX, thumbDeadZone, 32767); + UpdateGamepadAnalog(data, io, ImGuiKey.GamepadLStickRight, SDL.GamepadAxis.LeftX, +thumbDeadZone, +32767); UpdateGamepadAnalog(data, io, ImGuiKey.GamepadLStickUp, SDL.GamepadAxis.LeftY, -thumbDeadZone, -32768); - UpdateGamepadAnalog(data, io, ImGuiKey.GamepadLStickDown, SDL.GamepadAxis.LeftY, thumbDeadZone, 32767); - + UpdateGamepadAnalog(data, io, ImGuiKey.GamepadLStickDown, SDL.GamepadAxis.LeftY, +thumbDeadZone, +32767); UpdateGamepadAnalog(data, io, ImGuiKey.GamepadRStickLeft, SDL.GamepadAxis.RightX, -thumbDeadZone, -32768); - UpdateGamepadAnalog(data, io, ImGuiKey.GamepadRStickRight, SDL.GamepadAxis.RightX, thumbDeadZone, 32767); + UpdateGamepadAnalog(data, io, ImGuiKey.GamepadRStickRight, SDL.GamepadAxis.RightX, +thumbDeadZone, +32767); UpdateGamepadAnalog(data, io, ImGuiKey.GamepadRStickUp, SDL.GamepadAxis.RightY, -thumbDeadZone, -32768); - UpdateGamepadAnalog(data, io, ImGuiKey.GamepadRStickDown, SDL.GamepadAxis.RightY, thumbDeadZone, 32767); + UpdateGamepadAnalog(data, io, ImGuiKey.GamepadRStickDown, SDL.GamepadAxis.RightY, +thumbDeadZone, +32767); } private static void UpdateGamepadButton(PlatformData bd, ImGuiIOPtr io, ImGuiKey key, SDL.GamepadButton button_no) { bool mergedValue = false; - for (int i = 0; i < bd.GamepadCount; i++) - { - nint gamepad = bd.Gamepads[i]; - mergedValue |= SDL.GetGamepadButton(gamepad, button_no); - } - + mergedValue |= SDL.GetGamepadButton(bd.Gamepads[i], button_no); io.AddKeyEvent(key, mergedValue); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static float Saturate(float v) - { - return v < 0.0f ? 0.0f : v > 1.0f ? 1.0f : v; - } + private static float Saturate(float v) => v < 0.0f ? 0.0f : v > 1.0f ? 1.0f : v; private static void UpdateGamepadAnalog(PlatformData bd, ImGuiIOPtr io, ImGuiKey key, SDL.GamepadAxis axis_no, float v0, float v1) { float mergedValue = 0.0f; - for (int i = 0; i < bd.GamepadCount; i++) { - nint gamepad = bd.Gamepads[i]; - - float raw = SDL.GetGamepadAxis(gamepad, axis_no); - float vn = Saturate((raw - v0) / (v1 - v0)); - + float vn = Saturate((SDL.GetGamepadAxis(bd.Gamepads[i], axis_no) - v0) / (v1 - v0)); if (mergedValue < vn) mergedValue = vn; } - io.AddKeyAnalogEvent(key, mergedValue > 0.1f, mergedValue); } internal static void SetGamepadMode(GamepadMode mode, nint[] manual_gamepads_array, int manual_gamepads_count) { PlatformData bd = GetPlatformData(); - CloseGamepads(); if (mode == GamepadMode.Manual) { - int count = manual_gamepads_count; - - if (count > bd.Gamepads.Length) - count = bd.Gamepads.Length; - + int count = Math.Min(manual_gamepads_count, bd.Gamepads.Length); for (int n = 0; n < count; n++) bd.Gamepads[n] = manual_gamepads_array[n]; - bd.GamepadCount = count; } else @@ -559,138 +604,154 @@ public unsafe static class ImGuiSDL3Platform bd.GamepadCount = 0; } - private static ImGuiKey KeyEventToImGui(SDL.Keycode keycode, SDL.Scancode scancode) + // Not static to allow third-party code to use that if they want to (but undocumented) + public static ImGuiKey KeyEventToImGui(SDL.Keycode keycode, SDL.Scancode scancode) { - return scancode switch + // Keypad doesn't have individual key values in SDL3 — always use scancode for these. + switch (scancode) { - SDL.Scancode.Kp0 => ImGuiKey.Keypad0, - SDL.Scancode.Kp1 => ImGuiKey.Keypad1, - SDL.Scancode.Kp2 => ImGuiKey.Keypad2, - SDL.Scancode.Kp3 => ImGuiKey.Keypad3, - SDL.Scancode.Kp4 => ImGuiKey.Keypad4, - SDL.Scancode.Kp5 => ImGuiKey.Keypad5, - SDL.Scancode.Kp6 => ImGuiKey.Keypad6, - SDL.Scancode.Kp7 => ImGuiKey.Keypad7, - SDL.Scancode.Kp8 => ImGuiKey.Keypad8, - SDL.Scancode.Kp9 => ImGuiKey.Keypad9, - SDL.Scancode.KpPeriod => ImGuiKey.KeypadDecimal, - SDL.Scancode.KpDivide => ImGuiKey.KeypadDivide, - SDL.Scancode.KpMultiply => ImGuiKey.KeypadMultiply, - SDL.Scancode.KpMinus => ImGuiKey.KeypadSubtract, - SDL.Scancode.KpPlus => ImGuiKey.KeypadAdd, - SDL.Scancode.KpEnter => ImGuiKey.KeypadEnter, - SDL.Scancode.KpEquals => ImGuiKey.KeypadEqual, - _ => keycode switch - { - SDL.Keycode.Tab => ImGuiKey.Tab, - SDL.Keycode.Left => ImGuiKey.LeftArrow, - SDL.Keycode.Right => ImGuiKey.RightArrow, - SDL.Keycode.Up => ImGuiKey.UpArrow, - SDL.Keycode.Down => ImGuiKey.DownArrow, - SDL.Keycode.Pageup => ImGuiKey.PageUp, - SDL.Keycode.Pagedown => ImGuiKey.PageDown, - SDL.Keycode.Home => ImGuiKey.Home, - SDL.Keycode.End => ImGuiKey.End, - SDL.Keycode.Insert => ImGuiKey.Insert, - SDL.Keycode.Delete => ImGuiKey.Delete, - SDL.Keycode.Backspace => ImGuiKey.Backspace, - SDL.Keycode.Space => ImGuiKey.Space, - SDL.Keycode.Return => ImGuiKey.Enter, - SDL.Keycode.Escape => ImGuiKey.Escape, - SDL.Keycode.Apostrophe => ImGuiKey.Apostrophe, - SDL.Keycode.Comma => ImGuiKey.Comma, - SDL.Keycode.Minus => ImGuiKey.Minus, - SDL.Keycode.Period => ImGuiKey.Period, - SDL.Keycode.Slash => ImGuiKey.Slash, - SDL.Keycode.Semicolon => ImGuiKey.Semicolon, - SDL.Keycode.Equals => ImGuiKey.Equal, - SDL.Keycode.LeftBracket => ImGuiKey.LeftBracket, - SDL.Keycode.Backslash => ImGuiKey.Backslash, - SDL.Keycode.RightBracket => ImGuiKey.RightBracket, - SDL.Keycode.Grave => ImGuiKey.GraveAccent, - SDL.Keycode.Capslock => ImGuiKey.CapsLock, - SDL.Keycode.ScrollLock => ImGuiKey.ScrollLock, - SDL.Keycode.NumLockClear => ImGuiKey.NumLock, - SDL.Keycode.PrintScreen => ImGuiKey.PrintScreen, - SDL.Keycode.Pause => ImGuiKey.Pause, - SDL.Keycode.LCtrl => ImGuiKey.LeftCtrl, - SDL.Keycode.LShift => ImGuiKey.LeftShift, - SDL.Keycode.LAlt => ImGuiKey.LeftAlt, - SDL.Keycode.LGui => ImGuiKey.LeftSuper, - SDL.Keycode.RCtrl => ImGuiKey.RightCtrl, - SDL.Keycode.RShift => ImGuiKey.RightShift, - SDL.Keycode.RAlt => ImGuiKey.RightAlt, - SDL.Keycode.RGUI => ImGuiKey.RightSuper, - SDL.Keycode.Application => ImGuiKey.Menu, - SDL.Keycode.Alpha0 => ImGuiKey.Key0, - SDL.Keycode.Alpha1 => ImGuiKey.Key1, - SDL.Keycode.Alpha2 => ImGuiKey.Key2, - SDL.Keycode.Alpha3 => ImGuiKey.Key3, - SDL.Keycode.Alpha4 => ImGuiKey.Key4, - SDL.Keycode.Alpha5 => ImGuiKey.Key5, - SDL.Keycode.Alpha6 => ImGuiKey.Key6, - SDL.Keycode.Alpha7 => ImGuiKey.Key7, - SDL.Keycode.Alpha8 => ImGuiKey.Key8, - SDL.Keycode.Alpha9 => ImGuiKey.Key9, - SDL.Keycode.A => ImGuiKey.A, - SDL.Keycode.B => ImGuiKey.B, - SDL.Keycode.C => ImGuiKey.C, - SDL.Keycode.D => ImGuiKey.D, - SDL.Keycode.E => ImGuiKey.E, - SDL.Keycode.F => ImGuiKey.F, - SDL.Keycode.G => ImGuiKey.G, - SDL.Keycode.H => ImGuiKey.H, - SDL.Keycode.I => ImGuiKey.I, - SDL.Keycode.J => ImGuiKey.J, - SDL.Keycode.K => ImGuiKey.K, - SDL.Keycode.L => ImGuiKey.L, - SDL.Keycode.M => ImGuiKey.M, - SDL.Keycode.N => ImGuiKey.N, - SDL.Keycode.O => ImGuiKey.O, - SDL.Keycode.P => ImGuiKey.P, - SDL.Keycode.Q => ImGuiKey.Q, - SDL.Keycode.R => ImGuiKey.R, - SDL.Keycode.S => ImGuiKey.S, - SDL.Keycode.T => ImGuiKey.T, - SDL.Keycode.U => ImGuiKey.U, - SDL.Keycode.V => ImGuiKey.V, - SDL.Keycode.W => ImGuiKey.W, - SDL.Keycode.X => ImGuiKey.X, - SDL.Keycode.Y => ImGuiKey.Y, - SDL.Keycode.Z => ImGuiKey.Z, - SDL.Keycode.F1 => ImGuiKey.F1, - SDL.Keycode.F2 => ImGuiKey.F2, - SDL.Keycode.F3 => ImGuiKey.F3, - SDL.Keycode.F4 => ImGuiKey.F4, - SDL.Keycode.F5 => ImGuiKey.F5, - SDL.Keycode.F6 => ImGuiKey.F6, - SDL.Keycode.F7 => ImGuiKey.F7, - SDL.Keycode.F8 => ImGuiKey.F8, - SDL.Keycode.F9 => ImGuiKey.F9, - SDL.Keycode.F10 => ImGuiKey.F10, - SDL.Keycode.F11 => ImGuiKey.F11, - SDL.Keycode.F12 => ImGuiKey.F12, - SDL.Keycode.F13 => ImGuiKey.F13, - SDL.Keycode.F14 => ImGuiKey.F14, - SDL.Keycode.F15 => ImGuiKey.F15, - SDL.Keycode.F16 => ImGuiKey.F16, - SDL.Keycode.F17 => ImGuiKey.F17, - SDL.Keycode.F18 => ImGuiKey.F18, - SDL.Keycode.F19 => ImGuiKey.F19, - SDL.Keycode.F20 => ImGuiKey.F20, - SDL.Keycode.F21 => ImGuiKey.F21, - SDL.Keycode.F22 => ImGuiKey.F22, - SDL.Keycode.F23 => ImGuiKey.F23, - SDL.Keycode.F24 => ImGuiKey.F24, - SDL.Keycode.AcBack => ImGuiKey.AppBack, - SDL.Keycode.AcForward => ImGuiKey.AppForward, - _ => ImGuiKey.None - } - }; + case SDL.Scancode.Kp0: return ImGuiKey.Keypad0; + case SDL.Scancode.Kp1: return ImGuiKey.Keypad1; + case SDL.Scancode.Kp2: return ImGuiKey.Keypad2; + case SDL.Scancode.Kp3: return ImGuiKey.Keypad3; + case SDL.Scancode.Kp4: return ImGuiKey.Keypad4; + case SDL.Scancode.Kp5: return ImGuiKey.Keypad5; + case SDL.Scancode.Kp6: return ImGuiKey.Keypad6; + case SDL.Scancode.Kp7: return ImGuiKey.Keypad7; + case SDL.Scancode.Kp8: return ImGuiKey.Keypad8; + case SDL.Scancode.Kp9: return ImGuiKey.Keypad9; + case SDL.Scancode.KpPeriod: return ImGuiKey.KeypadDecimal; + case SDL.Scancode.KpDivide: return ImGuiKey.KeypadDivide; + case SDL.Scancode.KpMultiply: return ImGuiKey.KeypadMultiply; + case SDL.Scancode.KpMinus: return ImGuiKey.KeypadSubtract; + case SDL.Scancode.KpPlus: return ImGuiKey.KeypadAdd; + case SDL.Scancode.KpEnter: return ImGuiKey.KeypadEnter; + case SDL.Scancode.KpEquals: return ImGuiKey.KeypadEqual; + } + // For regular keys, prefer keycode (layout-aware) but fall back to scancode for OEM/symbol keys. + // This matches the C++ behaviour where OEM keys are commented out of the keycode switch and instead + // handled by the scancode fallback below. + switch (keycode) + { + case SDL.Keycode.Tab: return ImGuiKey.Tab; + case SDL.Keycode.Left: return ImGuiKey.LeftArrow; + case SDL.Keycode.Right: return ImGuiKey.RightArrow; + case SDL.Keycode.Up: return ImGuiKey.UpArrow; + case SDL.Keycode.Down: return ImGuiKey.DownArrow; + case SDL.Keycode.Pageup: return ImGuiKey.PageUp; + case SDL.Keycode.Pagedown: return ImGuiKey.PageDown; + case SDL.Keycode.Home: return ImGuiKey.Home; + case SDL.Keycode.End: return ImGuiKey.End; + case SDL.Keycode.Insert: return ImGuiKey.Insert; + case SDL.Keycode.Delete: return ImGuiKey.Delete; + case SDL.Keycode.Backspace: return ImGuiKey.Backspace; + case SDL.Keycode.Space: return ImGuiKey.Space; + case SDL.Keycode.Return: return ImGuiKey.Enter; + case SDL.Keycode.Escape: return ImGuiKey.Escape; + // OEM keys intentionally omitted here — handled by scancode fallback below. + // (Apostrophe, Minus, Slash, Equals, LeftBracket, Backslash, RightBracket, Grave) + case SDL.Keycode.Comma: return ImGuiKey.Comma; + case SDL.Keycode.Period: return ImGuiKey.Period; + case SDL.Keycode.Semicolon: return ImGuiKey.Semicolon; + case SDL.Keycode.Capslock: return ImGuiKey.CapsLock; + case SDL.Keycode.ScrollLock: return ImGuiKey.ScrollLock; + case SDL.Keycode.NumLockClear: return ImGuiKey.NumLock; + case SDL.Keycode.PrintScreen: return ImGuiKey.PrintScreen; + case SDL.Keycode.Pause: return ImGuiKey.Pause; + case SDL.Keycode.LCtrl: return ImGuiKey.LeftCtrl; + case SDL.Keycode.LShift: return ImGuiKey.LeftShift; + case SDL.Keycode.LAlt: return ImGuiKey.LeftAlt; + case SDL.Keycode.LGui: return ImGuiKey.LeftSuper; + case SDL.Keycode.RCtrl: return ImGuiKey.RightCtrl; + case SDL.Keycode.RShift: return ImGuiKey.RightShift; + case SDL.Keycode.RAlt: return ImGuiKey.RightAlt; + case SDL.Keycode.RGUI: return ImGuiKey.RightSuper; + case SDL.Keycode.Application: return ImGuiKey.Menu; + case SDL.Keycode.Alpha0: return ImGuiKey.Key0; + case SDL.Keycode.Alpha1: return ImGuiKey.Key1; + case SDL.Keycode.Alpha2: return ImGuiKey.Key2; + case SDL.Keycode.Alpha3: return ImGuiKey.Key3; + case SDL.Keycode.Alpha4: return ImGuiKey.Key4; + case SDL.Keycode.Alpha5: return ImGuiKey.Key5; + case SDL.Keycode.Alpha6: return ImGuiKey.Key6; + case SDL.Keycode.Alpha7: return ImGuiKey.Key7; + case SDL.Keycode.Alpha8: return ImGuiKey.Key8; + case SDL.Keycode.Alpha9: return ImGuiKey.Key9; + case SDL.Keycode.A: return ImGuiKey.A; + case SDL.Keycode.B: return ImGuiKey.B; + case SDL.Keycode.C: return ImGuiKey.C; + case SDL.Keycode.D: return ImGuiKey.D; + case SDL.Keycode.E: return ImGuiKey.E; + case SDL.Keycode.F: return ImGuiKey.F; + case SDL.Keycode.G: return ImGuiKey.G; + case SDL.Keycode.H: return ImGuiKey.H; + case SDL.Keycode.I: return ImGuiKey.I; + case SDL.Keycode.J: return ImGuiKey.J; + case SDL.Keycode.K: return ImGuiKey.K; + case SDL.Keycode.L: return ImGuiKey.L; + case SDL.Keycode.M: return ImGuiKey.M; + case SDL.Keycode.N: return ImGuiKey.N; + case SDL.Keycode.O: return ImGuiKey.O; + case SDL.Keycode.P: return ImGuiKey.P; + case SDL.Keycode.Q: return ImGuiKey.Q; + case SDL.Keycode.R: return ImGuiKey.R; + case SDL.Keycode.S: return ImGuiKey.S; + case SDL.Keycode.T: return ImGuiKey.T; + case SDL.Keycode.U: return ImGuiKey.U; + case SDL.Keycode.V: return ImGuiKey.V; + case SDL.Keycode.W: return ImGuiKey.W; + case SDL.Keycode.X: return ImGuiKey.X; + case SDL.Keycode.Y: return ImGuiKey.Y; + case SDL.Keycode.Z: return ImGuiKey.Z; + case SDL.Keycode.F1: return ImGuiKey.F1; + case SDL.Keycode.F2: return ImGuiKey.F2; + case SDL.Keycode.F3: return ImGuiKey.F3; + case SDL.Keycode.F4: return ImGuiKey.F4; + case SDL.Keycode.F5: return ImGuiKey.F5; + case SDL.Keycode.F6: return ImGuiKey.F6; + case SDL.Keycode.F7: return ImGuiKey.F7; + case SDL.Keycode.F8: return ImGuiKey.F8; + case SDL.Keycode.F9: return ImGuiKey.F9; + case SDL.Keycode.F10: return ImGuiKey.F10; + case SDL.Keycode.F11: return ImGuiKey.F11; + case SDL.Keycode.F12: return ImGuiKey.F12; + case SDL.Keycode.F13: return ImGuiKey.F13; + case SDL.Keycode.F14: return ImGuiKey.F14; + case SDL.Keycode.F15: return ImGuiKey.F15; + case SDL.Keycode.F16: return ImGuiKey.F16; + case SDL.Keycode.F17: return ImGuiKey.F17; + case SDL.Keycode.F18: return ImGuiKey.F18; + case SDL.Keycode.F19: return ImGuiKey.F19; + case SDL.Keycode.F20: return ImGuiKey.F20; + case SDL.Keycode.F21: return ImGuiKey.F21; + case SDL.Keycode.F22: return ImGuiKey.F22; + case SDL.Keycode.F23: return ImGuiKey.F23; + case SDL.Keycode.F24: return ImGuiKey.F24; + case SDL.Keycode.AcBack: return ImGuiKey.AppBack; + case SDL.Keycode.AcForward: return ImGuiKey.AppForward; + } + + // Fallback to scancode for OEM/symbol keys so non-US keyboard layouts still work (#7136, #7201, #7306). + switch (scancode) + { + case SDL.Scancode.Grave: return ImGuiKey.GraveAccent; + case SDL.Scancode.Minus: return ImGuiKey.Minus; + case SDL.Scancode.Equals: return ImGuiKey.Equal; + case SDL.Scancode.Leftbracket: return ImGuiKey.LeftBracket; + case SDL.Scancode.Rightbracket: return ImGuiKey.RightBracket; + case SDL.Scancode.NonUsbackslash: return ImGuiKey.Oem102; + case SDL.Scancode.Backslash: return ImGuiKey.Backslash; + case SDL.Scancode.Semicolon: return ImGuiKey.Semicolon; + case SDL.Scancode.Apostrophe: return ImGuiKey.Apostrophe; + case SDL.Scancode.Comma: return ImGuiKey.Comma; + case SDL.Scancode.Period: return ImGuiKey.Period; + case SDL.Scancode.Slash: return ImGuiKey.Slash; + } + + return ImGuiKey.None; } - public static void UpdateIme() { PlatformData data = GetPlatformData(); @@ -698,7 +759,7 @@ public unsafe static class ImGuiSDL3Platform nint window = SDL.GetKeyboardFocus(); // Stop previous input - if ((imeData.WantVisible != 1 || data.ImeWindow != window) && data.ImeWindow != nint.Zero) + if ((!(imeData.WantVisible == 1 || imeData.WantTextInput == 1) || data.ImeWindow != window) && data.ImeWindow != nint.Zero) { SDL.StopTextInput(data.ImeWindow); data.ImeWindow = nint.Zero; @@ -710,21 +771,27 @@ public unsafe static class ImGuiSDL3Platform data.ImeDirty = false; if (imeData.WantVisible == 1) { + // Offset the IME input area by the viewport position so it tracks the correct screen location + // when multiple viewports are in use. + Vector2 viewportPos = Vector2.Zero; + ImGuiViewportPtr? viewport = GetViewportForWindowId(SDL.GetWindowID(window)); + if (viewport != null && !viewport.Value.IsNull) + viewportPos = viewport.Value.Pos; + SDL.Rect r = new SDL.Rect { - X = (int)imeData.InputPos.X, - Y = (int)imeData.InputPos.Y, + X = (int)(imeData.InputPos.X - viewportPos.X), + Y = (int)(imeData.InputPos.Y - viewportPos.Y), W = 1, H = (int)imeData.InputLineHeight }; SDL.SetTextInputArea(window, r, 0); data.ImeWindow = window; } - if (!SDL.TextInputActive(window) && imeData.WantVisible == 1) + if (!SDL.TextInputActive(window) && (imeData.WantVisible == 1 || imeData.WantTextInput == 1)) SDL.StartTextInput(window); } - private static void UpdateKeyModifiers(SDL.Keymod mod) { ImGuiIOPtr io = ImGui.GetIO(); @@ -738,12 +805,12 @@ public unsafe static class ImGuiSDL3Platform { public nint Window; public nint ParentWindow; - public uint WindowID; // Stored in ImGuiViewport.PlatformHandle. Use SDL.GetWindowFromID() to get SDL.Window* from Uint32 WindowID. + public uint WindowID; // Stored in ImGuiViewport.PlatformHandle. Use SDL.GetWindowFromID() to get SDL_Window* from Uint32 WindowID. public bool WindowOwned; public nint GLContext; } - private static void InitMultiViewportSupport(nint _gl, nint window) + private static void InitMultiViewportSupport(nint glContext, nint window) { ImGuiPlatformIOPtr platformIO = ImGui.GetPlatformIO(); @@ -754,6 +821,7 @@ public unsafe static class ImGuiSDL3Platform platformIO.PlatformGetWindowPos = DelegateStorage.GetWindowPosDelegate.GetPtrForDelegate(); platformIO.PlatformSetWindowSize = DelegateStorage.SetWindowSizeDelegate.GetPtrForDelegate(); platformIO.PlatformGetWindowSize = DelegateStorage.GetWindowSizeDelegate.GetPtrForDelegate(); + platformIO.PlatformGetWindowFramebufferScale = DelegateStorage.GetWindowFramebufferScaleDelegate.GetPtrForDelegate(); platformIO.PlatformSetWindowFocus = DelegateStorage.SetWindowFocusDelegate.GetPtrForDelegate(); platformIO.PlatformGetWindowFocus = DelegateStorage.GetWindowFocusDelegate.GetPtrForDelegate(); platformIO.PlatformGetWindowMinimized = DelegateStorage.GetWindowMinimizedDelegate.GetPtrForDelegate(); @@ -762,28 +830,28 @@ public unsafe static class ImGuiSDL3Platform platformIO.PlatformUpdateWindow = DelegateStorage.UpdateWindowDelegate.GetPtrForDelegate(); platformIO.PlatformRenderWindow = DelegateStorage.RenderWindowDelegate.GetPtrForDelegate(); platformIO.PlatformSwapBuffers = DelegateStorage.SwapBuffersDelegate.GetPtrForDelegate(); - platformIO.PlatformGetWindowFramebufferScale = DelegateStorage.GetWindowFramebufferScaleDelegate.GetPtrForDelegate(); + // Register main window handle (owned by the main application, not by us). + // This is mostly for simplicity and consistency so that our code (e.g. mouse handling) can use + // the same logic for main and secondary viewports. ImGuiViewportPtr mainViewport = ImGui.GetMainViewport(); - ViewPortData vd = new ViewPortData { Window = window, WindowID = SDL.GetWindowID(window), WindowOwned = false, - GLContext = _gl + GLContext = glContext }; mainViewport.PlatformUserData = ImGuiUserData.Store(vd); - mainViewport.PlatformHandle = (void*)(nint)SDL.GetWindowID(window); - + mainViewport.PlatformHandle = (void*)(nint)vd.WindowID; #if WINDOWS - mainViewport.PlatformHandleRaw = SDL.GetPointerProperty( - SDL.GetWindowProperties(window), - SDL.Properties.WindowWin32HWNDPointer, - nint.Zero - ).ToPointer(); + mainViewport.PlatformHandleRaw = SDL.GetPointerProperty( + SDL.GetWindowProperties(window), + SDL.Properties.WindowWin32HWNDPointer, + nint.Zero + ).ToPointer(); #endif } @@ -802,9 +870,9 @@ public unsafe static class ImGuiSDL3Platform viewport.PlatformHandle = (void*)(nint)SDL.GetWindowID(window); viewport.PlatformHandleRaw = (void*)nint.Zero; #if _WIN32 && !__WINTR__ - SDL.GetPointerProperty(SDL.GetWindowProperties(window), SDL.Props.WindowWin32HWNDPointer, 0); + viewport.PlatformHandleRaw = SDL.GetPointerProperty(SDL.GetWindowProperties(window), SDL.Props.WindowWin32HWNDPointer, 0).ToPointer(); #elif __APPLE__ && SDL_VIDEO_DRIVER_COCOA - SDL.GetPointerProperty(SDL.GetWindowProperties(window), SDL.Props.WindowCocoaWindow, 0); + viewport.PlatformHandleRaw = SDL.GetPointerProperty(SDL.GetWindowProperties(window), SDL.Props.WindowCocoaWindow, 0).ToPointer(); #endif } @@ -824,14 +892,21 @@ public unsafe static class ImGuiSDL3Platform if (cursor != nint.Zero) SDL.DestroyCursor(cursor); } + + CloseGamepads(); } + ImGui.DestroyPlatformWindows(); + ImGuiIOPtr io = ImGui.GetIO(); ImGuiPlatformIOPtr platformIo = ImGui.GetPlatformIO(); platformIo.PlatformGetClipboardTextFn = null; platformIo.PlatformSetClipboardTextFn = null; platformIo.PlatformSetImeDataFn = null; platformIo.PlatformOpenInShellFn = null; + + io.BackendFlags &= ~(ImGuiBackendFlags.HasMouseCursors | ImGuiBackendFlags.HasSetMousePos | ImGuiBackendFlags.HasGamepad | ImGuiBackendFlags.PlatformHasViewports | ImGuiBackendFlags.HasMouseHoveredViewport); + ImGuiUserData.Free(io.BackendPlatformUserData); io.BackendPlatformUserData = null; } @@ -845,18 +920,19 @@ public unsafe static class ImGuiSDL3Platform private static nint GetClipboardText(nint ctx) { PlatformData data = GetPlatformData(); - if (data.ClipboardTextData != nint.Zero) { Marshal.FreeHGlobal(data.ClipboardTextData); data.ClipboardTextData = nint.Zero; } - - string text = SDL.GetClipboardText(); - data.ClipboardTextData = Marshal.StringToHGlobalAnsi(text ?? string.Empty); + // Return nullptr on error (clipboard contents not text), consistent with other backends (#9168). + string? text = SDL.HasClipboardText() ? SDL.GetClipboardText() : null; + if (text == null) + return nint.Zero; + data.ClipboardTextData = Marshal.StringToHGlobalAnsi(text); return data.ClipboardTextData; } - + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate void PlatformSetClipboardTextFn(nint ctx, nint text); internal static readonly PlatformSetClipboardTextFn SetClipboardDelegate = SetClipboardText; @@ -864,21 +940,20 @@ public unsafe static class ImGuiSDL3Platform { string? managedText = Marshal.PtrToStringAnsi(text); if (managedText != null) - { SDL.SetClipboardText(managedText); - } } - + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate void PlatformSetImeDataFn(nint ctx, nint userData, nint imeData); internal static readonly PlatformSetImeDataFn SetImeDataDelegate = SetImeData; private static void SetImeData(nint ctx, nint userData, nint imeData) { PlatformData data = GetPlatformData(); - data.ImeData = Marshal.PtrToStructure(imeData); + data.ImeData = Marshal.PtrToStructure(imeData); data.ImeDirty = true; + UpdateIme(); } - + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate bool PlatformOpenInShellFn(nint ctx, nint url); internal static readonly PlatformOpenInShellFn OpenInShellDelegate = OpenInShell; @@ -887,7 +962,7 @@ public unsafe static class ImGuiSDL3Platform string managedUrl = Marshal.PtrToStringUTF8(url) ?? string.Empty; return SDL.OpenURL(managedUrl); } - + // // // // // // // // // // // // // // // Below is for Multi-Viewport Support // // // @@ -904,32 +979,42 @@ public unsafe static class ImGuiSDL3Platform internal delegate void ViewportCallbackSetString(ImGuiViewportPtr ctx, string title); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate void ViewportCallbackSetFloat(ImGuiViewportPtr ctx, float alpha); - + internal static readonly ViewportCallback CreateWindowDelegate = CreateWindow; private static void CreateWindow(ImGuiViewportPtr viewport) { PlatformData bd = GetPlatformData(); - + ViewPortData vd = new ViewPortData(); viewport.PlatformUserData = ImGuiUserData.Store(vd); - + + // Resolve parent window + vd.ParentWindow = nint.Zero; + if (viewport.ParentViewportId != 0) + { + ImGuiViewportPtr? parentViewport = ImGui.FindViewportByID(viewport.ParentViewportId); + if (parentViewport != null && parentViewport.Value.Handle != null) + vd.ParentWindow = GetSDLWindowFromViewport(parentViewport.Value); + } + bool useOpengl = _glContext != nint.Zero; nint backupContext = nint.Zero; if (useOpengl) { backupContext = SDL.GLGetCurrentContext(); SDL.GLSetAttribute(SDL.GLAttr.ShareWithCurrentContext, 1); + // Make the main context current before creating the shared context + ViewPortData? mainVd = ImGuiUserData.Get(ImGui.GetMainViewport().PlatformUserData); + if (mainVd != null) + SDL.GLMakeCurrent(mainVd.Window, mainVd.GLContext); } - + SDL.WindowFlags flags = SDL.WindowFlags.Hidden; - if (useOpengl) flags |= SDL.WindowFlags.OpenGL; else if (bd.UseVulkan) flags |= SDL.WindowFlags.Vulkan; - flags |= SDL.GetWindowFlags(bd.Window) & SDL.WindowFlags.HighPixelDensity; - if (viewport.Flags.HasFlag(ImGuiViewportFlags.NoDecoration)) flags |= SDL.WindowFlags.Borderless; else @@ -938,128 +1023,104 @@ public unsafe static class ImGuiSDL3Platform flags |= SDL.WindowFlags.Utility; if (viewport.Flags.HasFlag(ImGuiViewportFlags.TopMost)) flags |= SDL.WindowFlags.AlwaysOnTop; - - vd.Window = SDL.CreateWindow("Untitled", (int)viewport.Size.X, (int)viewport.Size.Y, flags); + + vd.Window = SDL.CreateWindow("No Title Yet", (int)viewport.Size.X, (int)viewport.Size.Y, flags); vd.WindowID = SDL.GetWindowID(vd.Window); - - ImGuiViewportPtr? parentViewport = viewport.ParentViewportId != 0 ? ImGui.FindViewportByID(viewport.ParentViewportId) : null; - if (parentViewport != null && parentViewport.Value.Handle != null) - { - vd.ParentWindow = GetSDLWindowFromViewport(parentViewport.Value); - if (vd.ParentWindow != nint.Zero) - SDL.SetWindowParent(vd.Window, vd.ParentWindow); - } - + +#if !__APPLE__ // On Mac, SDL3 parenting appears to prevent viewport from appearing on another monitor + if (vd.ParentWindow != nint.Zero) + SDL.SetWindowParent(vd.Window, vd.ParentWindow); +#endif SDL.SetWindowPosition(vd.Window, (int)viewport.Pos.X, (int)viewport.Pos.Y); vd.WindowOwned = true; + if (useOpengl) { vd.GLContext = SDL.GLCreateContext(vd.Window); SDL.GLSetSwapInterval(0); } if (useOpengl && backupContext != nint.Zero) - { SDL.GLMakeCurrent(bd.Window, backupContext); - } - + SetupPlatformHandles(viewport, vd.Window); - + Program.Logger.Log($"New SDL Viewport: {vd.Window} {vd.WindowID} {viewport.Pos.X} {viewport.Pos.Y} {viewport.Size.X} {viewport.Size.Y}"); - - #if WINDOWS + +#if WINDOWS viewport.PlatformHandleRaw = SDL.GetPointerProperty( SDL.GetWindowProperties(vd.Window), SDL.Properties.WindowWin32HWNDPointer, nint.Zero ).ToPointer(); - #endif +#endif } - + internal static readonly ViewportCallback DestroyWindowDelegate = DestroyWindow; private static void DestroyWindow(ImGuiViewportPtr viewport) { ViewPortData? vd = ImGuiUserData.Get(viewport.PlatformUserData); - if (vd != null) { if (vd.GLContext != nint.Zero && vd.WindowOwned) - { SDL.GLDestroyContext(vd.GLContext); - } if (vd.Window != nint.Zero && vd.WindowOwned) - { SDL.DestroyWindow(vd.Window); - } - vd.GLContext = nint.Zero; - vd.Window = nint.Zero; + vd.Window = nint.Zero; } - + ImGuiUserData.Free(viewport.PlatformUserData); - - viewport.PlatformUserData = null; - viewport.PlatformHandle = null; + viewport.PlatformUserData = null; + viewport.PlatformHandle = null; viewport.PlatformHandleRaw = null; } - + internal static readonly ViewportCallback ShowWindowDelegate = ShowWindow; private static void ShowWindow(ImGuiViewportPtr viewport) { ViewPortData vd = ImGuiUserData.Get(viewport.PlatformUserData)!; - - string oldHint = SDL.GetHint(SDL.Hints.WindowActivateWhenShown)!; - + +#if __APPLE__ + // On macOS new windows should always appear on top so they aren't hidden under existing ones. + SDL.SetHint(SDL.Hints.WindowActivateWhenShown, "1"); +#else SDL.SetHint(SDL.Hints.WindowActivateWhenShown, (viewport.Flags & ImGuiViewportFlags.NoFocusOnAppearing) != 0 ? "0" : "1"); - +#endif SDL.ShowWindow(vd.Window); - - SDL.SetHint(SDL.Hints.WindowActivateWhenShown, oldHint); } - + internal static readonly ViewportCallback UpdateWindowDelegate = UpdateWindow; private static void UpdateWindow(ImGuiViewportPtr viewport) { - ViewPortData vd = ImGuiUserData.Get(viewport.PlatformUserData)!; - - if ((viewport.Flags & ImGuiViewportFlags.TopMost) != 0) - { - SDL.SetWindowAlwaysOnTop(vd.Window, true); - } - else - { - SDL.SetWindowAlwaysOnTop(vd.Window, false); - } + // C++ implementation is a no-op stub; left empty intentionally. } - + internal static readonly ViewportCallback RenderWindowDelegate = RenderWindow; private static void RenderWindow(ImGuiViewportPtr viewport) { ViewPortData vd = ImGuiUserData.Get(viewport.PlatformUserData)!; - if (vd.GLContext != nint.Zero) SDL.GLMakeCurrent(vd.Window, vd.GLContext); } - + internal static readonly ViewportCallback SwapBuffersDelegate = SwapBuffers; private static void SwapBuffers(ImGuiViewportPtr viewport) { ViewPortData vd = ImGuiUserData.Get(viewport.PlatformUserData)!; - if (vd.GLContext != nint.Zero) { SDL.GLMakeCurrent(vd.Window, vd.GLContext); SDL.GLSwapWindow(vd.Window); } } - + internal static readonly ViewportCallbackSetVec2 SetWindowPosDelegate = SetWindowPos; private static void SetWindowPos(ImGuiViewportPtr viewport, Vector2 pos) { ViewPortData vd = ImGuiUserData.Get(viewport.PlatformUserData)!; - SDL.SetWindowPosition(vd.Window, (int)pos.X, (int)pos.Y); } - + internal static readonly ViewportCallbackGetVec2 GetWindowPosDelegate = GetWindowPos; private static Vector2 GetWindowPos(ImGuiViewportPtr viewport) { @@ -1067,72 +1128,62 @@ public unsafe static class ImGuiSDL3Platform SDL.GetWindowPosition(vd.Window, out int x, out int y); return new Vector2(x, y); } - + internal static readonly ViewportCallbackSetVec2 SetWindowSizeDelegate = SetWindowSize; private static void SetWindowSize(ImGuiViewportPtr viewport, Vector2 size) { ViewPortData vd = ImGuiUserData.Get(viewport.PlatformUserData)!; - SDL.SetWindowSize(vd.Window, (int)size.X, (int)size.Y); } - + internal static readonly ViewportCallbackGetVec2 GetWindowSizeDelegate = GetWindowSize; private static Vector2 GetWindowSize(ImGuiViewportPtr viewport) { ViewPortData vd = ImGuiUserData.Get(viewport.PlatformUserData)!; - SDL.GetWindowSize(vd.Window, out int w, out int h); - return new Vector2(w, h); } - + internal static readonly ViewportCallbackGetVec2 GetWindowFramebufferScaleDelegate = GetWindowFramebufferScale; private static Vector2 GetWindowFramebufferScale(ImGuiViewportPtr viewport) { ViewPortData vd = ImGuiUserData.Get(viewport.PlatformUserData)!; - - GetWindowSizeAndFramebufferScale(vd.Window, out Vector2 _, out Vector2 framebufferScale); - + GetWindowSizeAndFramebufferScale(vd.Window, out _, out Vector2 framebufferScale); return framebufferScale; } - + internal static readonly ViewportCallback SetWindowFocusDelegate = SetWindowFocus; private static void SetWindowFocus(ImGuiViewportPtr viewport) { ViewPortData vd = ImGuiUserData.Get(viewport.PlatformUserData)!; - SDL.RaiseWindow(vd.Window); } - + internal static readonly ViewportCallbackBool GetWindowFocusDelegate = GetWindowFocus; private static bool GetWindowFocus(ImGuiViewportPtr viewport) { ViewPortData vd = ImGuiUserData.Get(viewport.PlatformUserData)!; - return (SDL.GetWindowFlags(vd.Window) & SDL.WindowFlags.InputFocus) != 0; } - + internal static readonly ViewportCallbackBool GetWindowMinimizedDelegate = GetWindowMinimized; private static bool GetWindowMinimized(ImGuiViewportPtr viewport) { ViewPortData vd = ImGuiUserData.Get(viewport.PlatformUserData)!; - return (SDL.GetWindowFlags(vd.Window) & SDL.WindowFlags.Minimized) != 0; } - + internal static readonly ViewportCallbackSetString SetWindowTitleDelegate = SetWindowTitle; private static void SetWindowTitle(ImGuiViewportPtr viewport, string title) { ViewPortData vd = ImGuiUserData.Get(viewport.PlatformUserData)!; - SDL.SetWindowTitle(vd.Window, title); } - + internal static readonly ViewportCallbackSetFloat SetWindowAlphaDelegate = SetWindowAlpha; private static void SetWindowAlpha(ImGuiViewportPtr viewport, float alpha) { ViewPortData vd = ImGuiUserData.Get(viewport.PlatformUserData)!; - SDL.SetWindowOpacity(vd.Window, alpha); } } diff --git a/SDL3/ImGuiSDL3Renderer.cs b/SDL3/ImGuiSDL3Renderer.cs index eda7474..8f57625 100644 --- a/SDL3/ImGuiSDL3Renderer.cs +++ b/SDL3/ImGuiSDL3Renderer.cs @@ -10,6 +10,8 @@ public unsafe static class ImGuiSDL3Renderer { public class RendererData { + public GL Gl = null!; + public uint GlVersion; // e.g. 320 for GL 3.2 public string GlslVersionString = ""; // e.g. "#version 330 core\n" public bool GlProfileIsES2; @@ -35,12 +37,10 @@ public unsafe static class ImGuiSDL3Renderer public bool UseTexParameterToSetSampler; public int NextSampler; // GL_LINEAR or GL_NEAREST - - public GL Gl = null!; } - + public static RendererData? GetRendererData() => ImGui.GetCurrentContext().Handle != null ? ImGuiUserData.Get(ImGui.GetIO().BackendRendererUserData) : null; - + public static bool Init(GL gl, string? glslVersion = null) { ImGuiIOPtr io = ImGui.GetIO(); @@ -143,7 +143,6 @@ public unsafe static class ImGuiSDL3Renderer io.BackendRendererName = null; io.BackendRendererUserData = null; io.BackendFlags &= ~(ImGuiBackendFlags.RendererHasVtxOffset | ImGuiBackendFlags.RendererHasTextures | ImGuiBackendFlags.RendererHasViewports); - } public static void NewFrame() @@ -222,7 +221,6 @@ public unsafe static class ImGuiSDL3Renderer int idxSize = drawList.IdxBuffer.Size * sizeof(ushort); gl.BufferData(GLBufferTargetARB.ArrayBuffer, vtxSize, drawList.VtxBuffer.Data, GLBufferUsageARB.StreamDraw); - gl.BufferData(GLBufferTargetARB.ElementArrayBuffer, idxSize, drawList.IdxBuffer.Data, GLBufferUsageARB.StreamDraw); for (int cmdIdx = 0; cmdIdx < drawList.CmdBuffer.Size; cmdIdx++) @@ -234,11 +232,17 @@ public unsafe static class ImGuiSDL3Renderer nint cbPtr = (nint)cmd.UserCallback; if (cbPtr == (nint)DelegateStorage.DrawCallbackResetRenderStateDelegate.GetPtrForDelegate()) + { SetupRenderState(drawData, fbWidth, fbHeight, vao); + } else if (cbPtr == (nint)DelegateStorage.DrawCallbackSetSamplerLinearDelegate.GetPtrForDelegate()) + { ApplySamplerLinear(bd); + } else if (cbPtr == (nint)DelegateStorage.DrawCallbackSetSamplerNearestDelegate.GetPtrForDelegate()) + { ApplySamplerNearest(bd); + } else { ImDrawCmdPtr cmdPtr = new ImDrawCmdPtr { Handle = &cmd }; @@ -352,7 +356,7 @@ public unsafe static class ImGuiSDL3Renderer 2.0f / (R - L), 0.0f, 0.0f, 0.0f, 0.0f, 2.0f / (T - B), 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, - (R + L) / (L - R), (T + B) / (B - T), 0.0f, 1.0f, + (R + L) / (L - R), (T + B) / (B - T), 0.0f, 1.0f }; gl.UseProgram(bd.ShaderHandle); @@ -467,7 +471,7 @@ public unsafe static class ImGuiSDL3Renderer < 130 => (vertSrc120, fragSrc120), 300 => (vertSrc300es, fragSrc300es), >= 410 => (vertSrc410, fragSrc410), - _ => (vertSrc130, fragSrc130), + _ => (vertSrc130, fragSrc130) }; string fullVert = bd.GlslVersionString + vertSrc; @@ -577,7 +581,10 @@ public unsafe static class ImGuiSDL3Renderer private static void ApplySamplerLinear(RendererData bd) { - if (bd.HasBindSampler) bd.Gl.BindSampler(0, bd.TexSamplers[0]); + if (bd.HasBindSampler) + { + bd.Gl.BindSampler(0, bd.TexSamplers[0]); + } else { bd.UseTexParameterToSetSampler = true; @@ -587,7 +594,10 @@ public unsafe static class ImGuiSDL3Renderer private static void ApplySamplerNearest(RendererData bd) { - if (bd.HasBindSampler) bd.Gl.BindSampler(0, bd.TexSamplers[1]); + if (bd.HasBindSampler) + { + bd.Gl.BindSampler(0, bd.TexSamplers[1]); + } else { bd.UseTexParameterToSetSampler = true; @@ -630,8 +640,6 @@ public unsafe static class ImGuiSDL3Renderer public static class DelegateStorage { - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate void DrawCallbackFn(ImDrawListPtr drawList, ImDrawCmdPtr cmd); diff --git a/SDL3/SDL3Window.cs b/SDL3/SDL3Window.cs index 2d2f35b..4e6225e 100644 --- a/SDL3/SDL3Window.cs +++ b/SDL3/SDL3Window.cs @@ -35,13 +35,16 @@ public sealed unsafe class SDL3Window : IDisposable 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, 3); - SDL.GLSetAttribute(SDL.GLAttr.ContextMinorVersion, 3); + SDL.GLSetAttribute(SDL.GLAttr.ContextMajorVersion, 4); + SDL.GLSetAttribute(SDL.GLAttr.ContextMinorVersion, 6); SDL.SetHint(SDL.Hints.IMEImplementedUI, "1"); @@ -116,7 +119,7 @@ public sealed unsafe class SDL3Window : IDisposable _gl = new GL(new BindingsContext(Window, glContext)); // Init platform and renderer - ImGuiSDL3Platform.Init(_gl, glContext, Window, this); + ImGuiSDL3Platform.Init(_gl, Window, glContext); ImGuiSDL3Renderer.Init(_gl, "#version 330"); }