using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using ImGuiNET; using SDL3; namespace SDL3_TestingSuite.SDL3; /// /// Implementation of SDL3 platform backend for ImGui. /// https://github.com/ocornut/imgui/blob/master/backends/imgui_impl_sdl3.h /// public unsafe static class ImGuiSDL3Platform { public class PlatformData { public nint Window; public uint WindowID; public nint Renderer; public ulong Time; public nint ClipboardTextData; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 48)] public byte[] BackendPlatformName = new byte[48]; public bool UseVulkan; public bool WantUpdateMonitors; // IME Handling public nint ImeWindow; public ImGuiPlatformImeData ImeData; public bool ImeDirty; // Mouse Handling public uint MouseWindowID; public int MouseButtonsDown; public readonly nint[] MouseCursors = new nint[(int)ImGuiMouseCursor.COUNT]; public nint MouseLastCursor; public int MouseLastLeaveFrame; public bool MouseCanUseGlobalState; public bool MouseCanReportHoveredViewport; public MouseCaptureMode MouseCaptureMode; // Gamepad Handling public readonly nint[] Gamepads = new nint[16]; public int GamepadCount; public GamepadMode GamepadMode; public bool WantUpdateGamepadsList; public PlatformData() { MouseLastCursor = nint.Zero; for (int i = 0; i < MouseCursors.Length; i++) MouseCursors[i] = nint.Zero; for (int i = 0; i < Gamepads.Length; i++) Gamepads[i] = nint.Zero; } } public class ViewPortData { public nint Window; public nint Renderer; public nint ParentWindow; public uint WindowID; // Stored in ImGuiViewport.PlatformHandle. Use SDL.GetWindowFromID() to get SDL.Window* from Uint32 WindowID. public bool WindowOwned; public bool RendererOwned; } // ReSharper disable NotAccessedField.Local [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate nint PlatformGetClipboardTextFn(nint ctx); private static readonly PlatformGetClipboardTextFn GetClipboardDelegate = GetClipboardText; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void PlatformSetClipboardTextFn(nint ctx, nint text); private static readonly PlatformSetClipboardTextFn SetClipboardDelegate = SetClipboardText; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void PlatformSetImeDataFn(nint ctx, nint userData, nint imeData); private static readonly PlatformSetImeDataFn SetImeDataDelegate = SetImeData; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate bool PlatformOpenInShellFn(nint ctx, nint url); private static readonly PlatformOpenInShellFn OpenInShellDelegate = OpenInShell; // ReSharper restore NotAccessedField.Local public static PlatformData Data => ImGui.GetCurrentContext() != nint.Zero ? ImGuiUserData.Get(ImGui.GetIO().BackendPlatformUserData)! : null!; public static bool Init(nint window, nint renderer) { ImGuiIOPtr io = ImGui.GetIO(); PlatformData data = new PlatformData { Window = window, Renderer = renderer, WindowID = SDL.GetWindowID(window) }; io.BackendPlatformUserData = ImGuiUserData.Store(data); io.BackendFlags |= ImGuiBackendFlags.HasMouseCursors; io.BackendFlags |= ImGuiBackendFlags.HasSetMousePos; data.MouseCanUseGlobalState = false; data.MouseCaptureMode = MouseCaptureMode.Disabled; string? backend = SDL.GetCurrentVideoDriver(); string[] captureAndGlobalStateWhitelist = new string[] { "windows", "cocoa", "x11", "DIVE", "VMAN", "wayland" }; foreach (var 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) } #if __APPLE__ data.MouseCanReportHoveredViewport = false; #else data.MouseCanReportHoveredViewport = data.MouseCanUseGlobalState; #endif ImGuiPlatformIOPtr platformIo = ImGui.GetPlatformIO(); platformIo.Platform_GetClipboardTextFn = Marshal.GetFunctionPointerForDelegate(GetClipboardDelegate); platformIo.Platform_SetClipboardTextFn = Marshal.GetFunctionPointerForDelegate(SetClipboardDelegate); platformIo.Platform_SetImeDataFn = Marshal.GetFunctionPointerForDelegate(SetImeDataDelegate); platformIo.Platform_OpenInShellFn = Marshal.GetFunctionPointerForDelegate(OpenInShellDelegate); UpdateMonitors(); data.GamepadMode = GamepadMode.AutoFirst; data.WantUpdateGamepadsList = true; data.MouseCursors[(int)ImGuiMouseCursor.Arrow] = SDL.CreateSystemCursor(SDL.SystemCursor.Default); data.MouseCursors[(int)ImGuiMouseCursor.TextInput] = SDL.CreateSystemCursor(SDL.SystemCursor.Text); data.MouseCursors[(int)ImGuiMouseCursor.ResizeAll] = SDL.CreateSystemCursor(SDL.SystemCursor.Move); data.MouseCursors[(int)ImGuiMouseCursor.ResizeNS] = SDL.CreateSystemCursor(SDL.SystemCursor.NSResize); data.MouseCursors[(int)ImGuiMouseCursor.ResizeEW] = SDL.CreateSystemCursor(SDL.SystemCursor.EWResize); data.MouseCursors[(int)ImGuiMouseCursor.ResizeNESW] = SDL.CreateSystemCursor(SDL.SystemCursor.NESWResize); data.MouseCursors[(int)ImGuiMouseCursor.ResizeNWSE] = SDL.CreateSystemCursor(SDL.SystemCursor.NWSEResize); data.MouseCursors[(int)ImGuiMouseCursor.Hand] = SDL.CreateSystemCursor(SDL.SystemCursor.Pointer); // data.MouseCursors[(int)ImGuiMouseCursor.Wait] = SDL.CreateSystemCursor(SDL.SystemCursor.Wait); // data.MouseCursors[(int)ImGuiMouseCursor.Progress] = SDL.CreateSystemCursor(SDL.SystemCursor.Progress); data.MouseCursors[(int)ImGuiMouseCursor.NotAllowed] = SDL.CreateSystemCursor(SDL.SystemCursor.NotAllowed); SetupPlatformHandles(ImGui.GetMainViewport(), window); SDL.SetHint(SDL.Hints.MouseFocusClickthrough, "1"); SDL.SetHint(SDL.Hints.MouseAutoCapture, "0"); SDL.SetHint("SDL_BORDERLESS_WINDOWED_STYLE", "0"); if ((io.BackendFlags & ImGuiBackendFlags.PlatformHasViewports) != 0) InitMultiViewportSupport(window); return true; } private static void GetWindowSizeAndFramebufferScale(nint window, out Vector2 out_size, out Vector2 out_framebuffer_scale) { SDL.GetWindowSize(window, out int w, out int h); if ((SDL.GetWindowFlags(window) & SDL.WindowFlags.Minimized) != 0) w = h = 0; 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; out_size = new Vector2(w, h); out_framebuffer_scale = new Vector2(fbScaleX, fbScaleY); } public static void NewFrame() { ImGuiIOPtr io = ImGui.GetIO(); PlatformData data = Data; GetWindowSizeAndFramebufferScale(data.Window, out Vector2 size, out Vector2 framebufferScale); if (data.WantUpdateMonitors) { UpdateMonitors(); } ulong frequency = SDL.GetPerformanceFrequency(); ulong currentTime = SDL.GetPerformanceCounter(); if (currentTime <= data.Time) currentTime = data.Time + 1; 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) { data.MouseWindowID = 0; data.MouseLastLeaveFrame = 0; io.AddMousePosEvent(-float.MaxValue, -float.MaxValue); } if (data.MouseCanReportHoveredViewport && ImGui.GetDragDropPayload().Equals(nint.Zero)) { io.BackendFlags |= ImGuiBackendFlags.HasMouseHoveredViewport; } else { io.BackendFlags &= ~ImGuiBackendFlags.HasMouseHoveredViewport; } UpdateMouseData(data); UpdateMouseCursor(data); UpdateIme(data); UpdateGamepads(data); } public static bool ProcessEvent(SDL.Event e) { ImGuiIOPtr io = ImGui.GetIO(); PlatformData data = Data; switch ((SDL.EventType)e.Type) { case SDL.EventType.MouseMotion: if (GetViewportForWindowId(e.Motion.WindowID) == null) return false; io.AddMouseSourceEvent(e.Motion.Which == SDL.TouchMouseID ? ImGuiMouseSource.TouchScreen : ImGuiMouseSource.Mouse); io.AddMousePosEvent(e.Motion.X, e.Motion.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; int mouseButton = e.Button.Button switch { SDL.ButtonLeft => 0, SDL.ButtonRight => 1, SDL.ButtonMiddle => 2, SDL.ButtonX1 => 3, SDL.ButtonX2 => 4, _ => -1 }; if (mouseButton == -1) break; io.AddMouseSourceEvent(e.Button.Which == SDL.TouchMouseID ? ImGuiMouseSource.TouchScreen : ImGuiMouseSource.Mouse); 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; ImGuiNative.ImGuiIO_AddInputCharactersUTF8(io, (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.WindowMouseEnter: if (GetViewportForWindowId(e.Window.WindowID) == null) return false; data.MouseWindowID = e.Window.WindowID; data.MouseLastLeaveFrame = 0; return true; case SDL.EventType.WindowMouseLeave: if (GetViewportForWindowId(e.Window.WindowID) == null) return false; data.MouseLastLeaveFrame = 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; } return false; } private static void UpdateMouseData(PlatformData data) { if (SDL.GetKeyboardFocus() == data.Window && ImGui.GetIO().WantSetMousePos) SDL.WarpMouseInWindow(data.Window, (int)ImGui.GetIO().MousePos.X, (int)ImGui.GetIO().MousePos.Y); } private static void UpdateMouseCursor(PlatformData data) { ImGuiIOPtr io = ImGui.GetIO(); if ((io.ConfigFlags & ImGuiConfigFlags.NoMouseCursorChange) != 0) return; ImGuiMouseCursor cursor = ImGui.GetMouseCursor(); if (io.MouseDrawCursor || cursor == ImGuiMouseCursor.None) { SDL.HideCursor(); return; } nint expected = data.MouseCursors[(int)cursor]; if (data.MouseLastCursor != expected) { SDL.SetCursor(expected); data.MouseLastCursor = expected; } SDL.ShowCursor(); } private static void CloseGamepads() { PlatformData bd = Data; if (bd.GamepadMode != GamepadMode.Manual) { for (int i = 0; i < bd.GamepadCount; i++) { SDL.CloseGamepad(bd.Gamepads[i]); bd.Gamepads[i] = nint.Zero; } } bd.GamepadCount = 0; } private static void SetGamepadMode(GamepadMode mode, nint[] manual_gamepads_array, int manual_gamepads_count) { PlatformData bd = Data; CloseGamepads(); if (mode == GamepadMode.Manual) { int count = manual_gamepads_count; if (count > bd.Gamepads.Length) count = bd.Gamepads.Length; for (int n = 0; n < count; n++) bd.Gamepads[n] = manual_gamepads_array[n]; bd.GamepadCount = count; } else { bd.WantUpdateGamepadsList = true; } bd.GamepadMode = mode; } 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); } 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 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)); if (mergedValue < vn) mergedValue = vn; } io.AddKeyAnalogEvent(key, mergedValue > 0.1f, mergedValue); } private static void UpdateGamepads(PlatformData data) { ImGuiIOPtr io = ImGui.GetIO(); if (data.WantUpdateGamepadsList && data.GamepadMode != GamepadMode.Manual) { 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; } data.WantUpdateGamepadsList = false; } io.BackendFlags &= ~ImGuiBackendFlags.HasGamepad; if (data.GamepadCount == 0) return; io.BackendFlags |= ImGuiBackendFlags.HasGamepad; const int thumbDeadZone = 8000; 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.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.GamepadLStickUp, SDL.GamepadAxis.LeftY, -thumbDeadZone, -32768); 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.GamepadRStickUp, SDL.GamepadAxis.RightY, -thumbDeadZone, -32768); UpdateGamepadAnalog(data, io, ImGuiKey.GamepadRStickDown, SDL.GamepadAxis.RightY, thumbDeadZone, 32767); } public static unsafe void UpdateMonitors() { PlatformData bd = Data; ImGuiPlatformIOPtr platformio = ImGui.GetPlatformIO(); if (platformio.NativePtr == null) return; var displays = SDL.GetDisplays(out int displayCount); if (displays == null || displayCount <= 0) return; if (platformio.Monitors.Data == IntPtr.Zero) return; bd.WantUpdateMonitors = false; ImGuiPlatformMonitor* dst = (ImGuiPlatformMonitor*)platformio.Monitors.Data; for (int i = 0; i < displayCount; i++) { var displayID = displays[i]; ImGuiPlatformMonitor monitor = new ImGuiPlatformMonitor(); SDL.GetDisplayBounds(displayID, out SDL.Rect r); monitor.MainPos = monitor.WorkPos = new Vector2(r.X, r.Y); monitor.MainSize = monitor.WorkSize = new Vector2(r.W, r.H); if (SDL.GetDisplayUsableBounds(displayID, out SDL.Rect r2) && r2.W > 0 && r2.H > 0) { monitor.WorkPos = new Vector2(r2.X, r2.Y); monitor.WorkSize = new Vector2(r2.W, r2.H); } monitor.DpiScale = SDL.GetDisplayContentScale(displayID); if (monitor.DpiScale <= 0.0f) continue; monitor.PlatformHandle = (void*)i; dst[i] = monitor; } } private static ImGuiKey KeyEventToImGui(SDL.Keycode keycode, SDL.Scancode scancode) { return scancode switch { 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._0, SDL.Keycode.Alpha1 => ImGuiKey._1, SDL.Keycode.Alpha2 => ImGuiKey._2, SDL.Keycode.Alpha3 => ImGuiKey._3, SDL.Keycode.Alpha4 => ImGuiKey._4, SDL.Keycode.Alpha5 => ImGuiKey._5, SDL.Keycode.Alpha6 => ImGuiKey._6, SDL.Keycode.Alpha7 => ImGuiKey._7, SDL.Keycode.Alpha8 => ImGuiKey._8, SDL.Keycode.Alpha9 => ImGuiKey._9, 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 } }; } public static nint GetClipboardText(nint ctx) { PlatformData data = Data; string text = SDL.GetClipboardText(); data.ClipboardTextData = Marshal.StringToHGlobalAnsi(text ?? string.Empty); return data.ClipboardTextData; } public static void SetClipboardText(nint ctx, nint text) { string? managedText = Marshal.PtrToStringAnsi(text); if (managedText != null) { SDL.SetClipboardText(managedText); } } private static void SetImeData(nint ctx, nint userData, nint imeData) { PlatformData data = Data; data.ImeData = Marshal.PtrToStructure(imeData); data.ImeDirty = true; } public static void UpdateIme(PlatformData data) { ImGuiPlatformImeData imeData = data.ImeData; nint window = SDL.GetKeyboardFocus(); // Stop previous input if ((imeData.WantVisible != 1 || data.ImeWindow != window) && data.ImeWindow != nint.Zero) { SDL.StopTextInput(data.ImeWindow); data.ImeWindow = nint.Zero; } if (!data.ImeDirty && data.ImeWindow == window || window == nint.Zero) return; // Start/update current input data.ImeDirty = false; if (imeData.WantVisible == 1) { SDL.Rect r = new SDL.Rect { X = (int)imeData.InputPos.X, Y = (int)imeData.InputPos.Y, W = 1, H = (int)imeData.InputLineHeight }; SDL.SetTextInputArea(window, r, 0); data.ImeWindow = window; } if (!SDL.TextInputActive(window) && imeData.WantVisible == 1) SDL.StartTextInput(window); } private static bool OpenInShell(nint ctx, nint url) { string managedUrl = Marshal.PtrToStringUTF8(url) ?? string.Empty; return SDL.OpenURL(managedUrl); } // !! This is sorta fucked // public static void UpdateKeyModifiers(SDL.Keymod keymods) // { // ImGuiIOPtr io = ImGui.GetIO(); // io.KeyCtrl = keymods.HasFlag(SDL.Keymod.LCtrl) || keymods.HasFlag(SDL.Keymod.RCtrl); // io.KeyShift = keymods.HasFlag(SDL.Keymod.LShift) || keymods.HasFlag(SDL.Keymod.RShift); // io.KeyAlt = keymods.HasFlag(SDL.Keymod.LAlt) || keymods.HasFlag(SDL.Keymod.RAlt); // io.KeySuper = keymods.HasFlag(SDL.Keymod.LGUI) || keymods.HasFlag(SDL.Keymod.RGUI); // } private static void UpdateKeyModifiers(SDL.Keymod mod) { ImGuiIOPtr io = ImGui.GetIO(); io.AddKeyEvent(ImGuiKey.ModCtrl, (mod & SDL.Keymod.Ctrl) != 0); io.AddKeyEvent(ImGuiKey.ModShift, (mod & SDL.Keymod.Shift) != 0); io.AddKeyEvent(ImGuiKey.ModAlt, (mod & SDL.Keymod.Alt) != 0); io.AddKeyEvent(ImGuiKey.ModSuper, (mod & SDL.Keymod.GUI) != 0); } private static void CreateWindow(ImGuiViewportPtr viewport) { PlatformData bd = Data; ViewPortData vd = new ViewPortData(); viewport.PlatformUserData = ImGuiUserData.Store(vd); SDL.WindowFlags flags = SDL.WindowFlags.Hidden; flags |= SDL.GetWindowFlags(bd.Window) & SDL.WindowFlags.HighPixelDensity; string? videoDriver = SDL.GetCurrentVideoDriver(); bool isWayland = string.Equals(videoDriver, "wayland", StringComparison.OrdinalIgnoreCase); if ((viewport.Flags & ImGuiViewportFlags.NoDecoration) != 0 && !isWayland) { flags |= SDL.WindowFlags.Borderless; } else { flags |= SDL.WindowFlags.Resizable; } if ((viewport.Flags & ImGuiViewportFlags.NoTaskBarIcon) != 0) { flags |= SDL.WindowFlags.Utility; } if ((viewport.Flags & ImGuiViewportFlags.TopMost) != 0) { flags |= SDL.WindowFlags.AlwaysOnTop; } vd.Window = SDL.CreateWindow("Untitled", (int)viewport.Size.X, (int)viewport.Size.Y, flags); ImGuiViewportPtr? parentViewport = viewport.ParentViewportId != 0 ? ImGui.FindViewportByID(viewport.ParentViewportId) : null; if (parentViewport != null) { vd.ParentWindow = GetSDLWindowFromViewport(parentViewport.Value); if (vd.ParentWindow != nint.Zero) SDL.SetWindowParent(vd.Window, vd.ParentWindow); } SDL.SetWindowPosition(vd.Window, (int)viewport.Pos.X, (int)viewport.Pos.Y); vd.WindowOwned = true; SetupPlatformHandles(viewport, vd.Window); Program.Logger.Log($"SDL Window: {vd.Window} {vd.WindowID} {viewport.Pos.X} {viewport.Pos.Y} {viewport.Size.X} {viewport.Size.Y}"); #if WINDOWS viewport.PlatformHandleRaw = SDL.GetPointerProperty( SDL.GetWindowProperties(vd.Window), SDL.Properties.WindowWin32HWNDPointer, nint.Zero ).ToPointer(); #endif } private static void DestroyWindow(ImGuiViewportPtr viewport) { ViewPortData? vd = ImGuiUserData.Get(viewport.PlatformUserData); if (vd != null) { if (vd.Window != nint.Zero && vd.WindowOwned) { SDL.DestroyWindow(vd.Window); } } ImGuiUserData.Free(viewport.PlatformUserData); viewport.PlatformUserData = nint.Zero; viewport.PlatformHandle = nint.Zero; viewport.PlatformHandleRaw = nint.Zero; } private static void ShowWindow(ImGuiViewportPtr viewport) { ViewPortData vd = ImGuiUserData.Get(viewport.PlatformUserData)!; string? oldHint = SDL.GetHint(SDL.Hints.WindowActivateWhenShown); SDL.SetHint(SDL.Hints.WindowActivateWhenShown, ((viewport.Flags & ImGuiViewportFlags.NoFocusOnAppearing) != 0) ? "0" : "1"); SDL.ShowWindow(vd.Window); SDL.SetHint(SDL.Hints.WindowActivateWhenShown, oldHint); } 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); } } private static Vector2 GetWindowPos(ImGuiViewportPtr viewport) { ViewPortData vd = ImGuiUserData.Get(viewport.PlatformUserData)!; SDL.GetWindowPosition(vd.Window, out int x, out int y); // Program.Logger.Log(null); return new Vector2(x, y); } private static void SetWindowPos(ImGuiViewportPtr viewport, Vector2 pos) { ViewPortData vd = ImGuiUserData.Get(viewport.PlatformUserData)!; SDL.SetWindowPosition(vd.Window, (int)pos.X, (int)pos.Y); } private static Vector2 GetWindowSize(ImGuiViewportPtr viewport) { ViewPortData vd = ImGuiUserData.Get(viewport.PlatformUserData)!; SDL.GetWindowSize(vd.Window, out int w, out int h); // Program.Logger.Log(null); return new Vector2(w, h); } private static void SetWindowSize(ImGuiViewportPtr viewport, Vector2 size) { ViewPortData vd = ImGuiUserData.Get(viewport.PlatformUserData)!; SDL.SetWindowSize(vd.Window, (int)size.X, (int)size.Y); } private static Vector2 GetWindowFramebufferScale(ImGuiViewportPtr viewport) { ViewPortData vd = ImGuiUserData.Get(viewport.PlatformUserData)!; GetWindowSizeAndFramebufferScale(vd.Window, out Vector2 framebufferSize, out Vector2 framebufferScale); // Program.Logger.Log(null); return framebufferScale; } private static void SetWindowTitle(ImGuiViewportPtr viewport, string title) { ViewPortData vd = ImGuiUserData.Get(viewport.PlatformUserData)!; SDL.SetWindowTitle(vd.Window, title); } private static void SetWindowAlpha(ImGuiViewportPtr viewport, float alpha) { ViewPortData vd = ImGuiUserData.Get(viewport.PlatformUserData)!; SDL.SetWindowOpacity(vd.Window, alpha); } private static void SetWindowFocus(ImGuiViewportPtr viewport) { ViewPortData vd = ImGuiUserData.Get(viewport.PlatformUserData)!; SDL.RaiseWindow(vd.Window); } private static bool GetWindowFocus(ImGuiViewportPtr viewport) { ViewPortData vd = ImGuiUserData.Get(viewport.PlatformUserData)!; // Program.Logger.Log(null); return (SDL.GetWindowFlags(vd.Window) & SDL.WindowFlags.InputFocus) != 0; } private static bool GetWindowMinimized(ImGuiViewportPtr viewport) { ViewPortData vd = ImGuiUserData.Get(viewport.PlatformUserData)!; // Program.Logger.Log(null); return (SDL.GetWindowFlags(vd.Window) & SDL.WindowFlags.Minimized) != 0; } private static void RenderWindow(ImGuiViewportPtr viewport) { // Renderer-backed viewport rendering is handled by ImGuiSDL3Renderer. } private static void SwapBuffers(ImGuiViewportPtr viewport) { // Renderer-backed viewport presentation is handled by ImGuiSDL3Renderer. } [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void CreateWindowDelegatelFn(ImGuiViewportPtr ctx); private static readonly CreateWindowDelegatelFn CreateWindowDelegate = CreateWindow; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void DestroyWindowDelegatelFn(ImGuiViewportPtr ctx); private static readonly DestroyWindowDelegatelFn DestroyWindowDelegate = DestroyWindow; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void ShowWindowDelegatelFn(ImGuiViewportPtr ctx); private static readonly ShowWindowDelegatelFn ShowWindowDelegate = ShowWindow; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void UpdateWindowDelegatelFn(ImGuiViewportPtr ctx); private static readonly UpdateWindowDelegatelFn UpdateWindowDelegate = UpdateWindow; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void SetWindowPosDelegatelFn(ImGuiViewportPtr ctx, Vector2 pos); private static readonly SetWindowPosDelegatelFn SetWindowPosDelegate = SetWindowPos; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate Vector2 GetWindowPosDelegatelFn(ImGuiViewportPtr ctx); private static readonly GetWindowPosDelegatelFn GetWindowPosDelegate = GetWindowPos; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void SetWindowSizeDelegatelFn(ImGuiViewportPtr ctx, Vector2 size); private static readonly SetWindowSizeDelegatelFn SetWindowSizeDelegate = SetWindowSize; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate Vector2 GetWindowSizeDelegateFn(ImGuiViewportPtr ctx); private static readonly GetWindowSizeDelegateFn GetWindowSizeDelegate = GetWindowSize; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate Vector2 GetWindowFramebufferScaleDelegatelFn(ImGuiViewportPtr ctx); private static readonly GetWindowFramebufferScaleDelegatelFn GetWindowFramebufferScaleDelegate = GetWindowFramebufferScale; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void SetWindowFocusDelegatelFn(ImGuiViewportPtr ctx); private static readonly SetWindowFocusDelegatelFn SetWindowFocusDelegate = SetWindowFocus; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate bool GetWindowFocusDelegatelFn(ImGuiViewportPtr ctx); private static readonly GetWindowFocusDelegatelFn GetWindowFocusDelegate = GetWindowFocus; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate bool GetWindowMinimizedDelegatelFn(ImGuiViewportPtr ctx); private static readonly GetWindowMinimizedDelegatelFn GetWindowMinimizedDelegate = GetWindowMinimized; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void SetWindowTitleDelegatelFn(ImGuiViewportPtr ctx, string title); private static readonly SetWindowTitleDelegatelFn SetWindowTitleDelegate = SetWindowTitle; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void RenderWindowDelegatelFn(ImGuiViewportPtr ctx); private static readonly RenderWindowDelegatelFn RenderWindowDelegate = RenderWindow; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void SwapBuffersDelegatelFn(ImGuiViewportPtr ctx); private static readonly SwapBuffersDelegatelFn SwapBuffersDelegate = SwapBuffers; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void SetWindowAlphaDelegatelFn(ImGuiViewportPtr ctx, float alpha); private static readonly SetWindowAlphaDelegatelFn SetWindowAlphaDelegate = SetWindowAlpha; private static void InitMultiViewportSupport(nint window) { ImGuiPlatformIOPtr platformIO = ImGui.GetPlatformIO(); platformIO.Platform_CreateWindow = Marshal.GetFunctionPointerForDelegate(CreateWindowDelegate); platformIO.Platform_DestroyWindow = Marshal.GetFunctionPointerForDelegate(DestroyWindowDelegate); platformIO.Platform_ShowWindow = Marshal.GetFunctionPointerForDelegate(ShowWindowDelegate); platformIO.Platform_SetWindowPos = Marshal.GetFunctionPointerForDelegate(SetWindowPosDelegate); platformIO.Platform_GetWindowPos = Marshal.GetFunctionPointerForDelegate(GetWindowPosDelegate); platformIO.Platform_SetWindowSize = Marshal.GetFunctionPointerForDelegate(SetWindowSizeDelegate); platformIO.Platform_GetWindowSize = Marshal.GetFunctionPointerForDelegate(GetWindowSizeDelegate); platformIO.Platform_SetWindowFocus = Marshal.GetFunctionPointerForDelegate(SetWindowFocusDelegate); platformIO.Platform_GetWindowFocus = Marshal.GetFunctionPointerForDelegate(GetWindowFocusDelegate); platformIO.Platform_GetWindowMinimized = Marshal.GetFunctionPointerForDelegate(GetWindowMinimizedDelegate); platformIO.Platform_SetWindowTitle = Marshal.GetFunctionPointerForDelegate(SetWindowTitleDelegate); platformIO.Platform_RenderWindow = Marshal.GetFunctionPointerForDelegate(RenderWindowDelegate); platformIO.Platform_SwapBuffers = Marshal.GetFunctionPointerForDelegate(SwapBuffersDelegate); platformIO.Platform_SetWindowAlpha = Marshal.GetFunctionPointerForDelegate(SetWindowAlphaDelegate); platformIO.Platform_UpdateWindow = Marshal.GetFunctionPointerForDelegate(UpdateWindowDelegate); // platformIO.Platform_GetWindowFramebufferScale = Marshal.GetFunctionPointerForDelegate(GetWindowFramebufferScaleDelegate); ImGuiViewportPtr mainViewport = ImGui.GetMainViewport(); ViewPortData vd = new ViewPortData { Window = window, Renderer = Data.Renderer, WindowID = SDL.GetWindowID(window), WindowOwned = false, RendererOwned = false }; mainViewport.PlatformUserData = ImGuiUserData.Store(vd); mainViewport.PlatformHandle = (nint)SDL.GetWindowID(window); #if WINDOWS mainViewport.PlatformHandleRaw = SDL.GetPointerProperty( SDL.GetWindowProperties(window), SDL.Properties.WindowWin32HWNDPointer, nint.Zero ).ToPointer(); #endif } public static ImGuiViewportPtr? GetViewportForWindowId(uint id) { return ImGui.FindViewportByPlatformHandle((nint)id); } private static nint GetSDLWindowFromViewport(ImGuiViewportPtr viewport) { return SDL.GetWindowFromID((uint)viewport.PlatformHandle); } private static void SetupPlatformHandles(ImGuiViewportPtr viewport, nint window) { viewport.PlatformHandle = (nint)SDL.GetWindowID(window); viewport.PlatformHandleRaw = nint.Zero; #if _WIN32 && !__WINTR__ SDL.GetPointerProperty(SDL.GetWindowProperties(window), SDL.Props.WindowWin32HWNDPointer, 0); #elif __APPLE__ && SDL_VIDEO_DRIVER_COCOA SDL.GetPointerProperty(SDL.GetWindowProperties(window), SDL.Props.WindowCocoaWindow, 0); #endif } public static void Dispose() { PlatformData? data = Data; if (data != null) { foreach (nint cursor in data.MouseCursors) { if (cursor != nint.Zero) SDL.DestroyCursor(cursor); } } ImGuiIOPtr io = ImGui.GetIO(); ImGuiPlatformIOPtr platformIo = ImGui.GetPlatformIO(); platformIo.Platform_GetClipboardTextFn = nint.Zero; platformIo.Platform_SetClipboardTextFn = nint.Zero; platformIo.Platform_SetImeDataFn = nint.Zero; platformIo.Platform_OpenInShellFn = nint.Zero; ImGuiUserData.Free(io.BackendPlatformUserData); io.BackendPlatformUserData = nint.Zero; } } public enum GamepadMode { AutoFirst, AutoAll, Manual } public enum MouseCaptureMode { Enabled, EnabledAfterDrag, Disabled } public static class ImGuiUserData where T : class { public static nint Store(T value) { if (value == null) return nint.Zero; GCHandle handle = GCHandle.Alloc(value, GCHandleType.Normal); return GCHandle.ToIntPtr(handle); } public static T Get(nint ptr) { if (ptr == nint.Zero) return null!; GCHandle handle = GCHandle.FromIntPtr(ptr); return (T)handle.Target!; } public static void Free(nint ptr) { if (ptr == nint.Zero) return; GCHandle handle = GCHandle.FromIntPtr(ptr); if (handle.IsAllocated) handle.Free(); } }