using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Hexa.NET.ImGui;
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().Handle != null ? 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.PlatformGetClipboardTextFn = (void*)Marshal.GetFunctionPointerForDelegate(GetClipboardDelegate);
platformIo.PlatformSetClipboardTextFn = (void*)Marshal.GetFunctionPointerForDelegate(SetClipboardDelegate);
platformIo.PlatformSetImeDataFn = (void*)Marshal.GetFunctionPointerForDelegate(SetImeDataDelegate);
platformIo.PlatformOpenInShellFn = (void*)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;
ImGui.GetIO().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.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 void UpdateMonitors()
{
PlatformData bd = Data;
ImGuiPlatformIOPtr platformio = ImGui.GetPlatformIO();
platformio.Monitors.Resize(0);
bd.WantUpdateMonitors = false;
var displays = SDL.GetDisplays(out int displayCount);
for (int n = 0; n < displayCount; n++)
{
// Warning: the validity of monitor DPI information on Windows depends on the application DPI awareness settings, which generally needs to be set in the manifest or at runtime.
var displayID = displays[n];
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); // See https://wiki.libsdl.org/SDL3/README-highdpi for details.
monitor.PlatformHandle = (void*)n;
if (monitor.DpiScale <= 0.0f)
continue; // Some accessibility applications are declaring virtual monitors with a DPI of 0, see #7902.
platformio.Monitors.PushBack(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.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
}
};
}
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)
{
Program.Logger.Log(null);
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 && parentViewport.Value.Handle != 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)
{
Program.Logger.Log(null);
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 = null;
viewport.PlatformHandle = null;
viewport.PlatformHandleRaw = null;
Program.Logger.Log(null);
}
private static void ShowWindow(ImGuiViewportPtr viewport)
{
Program.Logger.Log(null);
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);
Program.Logger.Log(null);
}
private static void UpdateWindow(ImGuiViewportPtr viewport)
{
Program.Logger.Log(null);
ViewPortData vd = ImGuiUserData.Get(viewport.PlatformUserData)!;
if ((viewport.Flags & ImGuiViewportFlags.TopMost) != 0)
{
SDL.SetWindowAlwaysOnTop(vd.Window, true);
}
else
{
SDL.SetWindowAlwaysOnTop(vd.Window, false);
}
Program.Logger.Log(null);
}
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);
Program.Logger.Log(null);
}
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);
Program.Logger.Log(null);
}
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);
Program.Logger.Log(null);
}
private static void SetWindowAlpha(ImGuiViewportPtr viewport, float alpha)
{
ViewPortData vd = ImGuiUserData.Get(viewport.PlatformUserData)!;
SDL.SetWindowOpacity(vd.Window, alpha);
Program.Logger.Log(null);
}
private static void SetWindowFocus(ImGuiViewportPtr viewport)
{
ViewPortData vd = ImGuiUserData.Get(viewport.PlatformUserData)!;
SDL.RaiseWindow(vd.Window);
Program.Logger.Log(null);
}
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.
Program.Logger.Log(null);
}
private static void SwapBuffers(ImGuiViewportPtr viewport)
{
// Renderer-backed viewport presentation is handled by ImGuiSDL3Renderer.
Program.Logger.Log(null);
}
[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)
{
Program.Logger.Log(null);
ImGuiPlatformIOPtr platformIO = ImGui.GetPlatformIO();
platformIO.PlatformCreateWindow = (void*)Marshal.GetFunctionPointerForDelegate(CreateWindowDelegate);
platformIO.PlatformDestroyWindow = (void*)Marshal.GetFunctionPointerForDelegate(DestroyWindowDelegate);
platformIO.PlatformShowWindow = (void*)Marshal.GetFunctionPointerForDelegate(ShowWindowDelegate);
platformIO.PlatformSetWindowPos = (void*)Marshal.GetFunctionPointerForDelegate(SetWindowPosDelegate);
platformIO.PlatformGetWindowPos = (void*)Marshal.GetFunctionPointerForDelegate(GetWindowPosDelegate);
platformIO.PlatformSetWindowSize = (void*)Marshal.GetFunctionPointerForDelegate(SetWindowSizeDelegate);
platformIO.PlatformGetWindowSize = (void*)Marshal.GetFunctionPointerForDelegate(GetWindowSizeDelegate);
platformIO.PlatformSetWindowFocus = (void*)Marshal.GetFunctionPointerForDelegate(SetWindowFocusDelegate);
platformIO.PlatformGetWindowFocus = (void*)Marshal.GetFunctionPointerForDelegate(GetWindowFocusDelegate);
platformIO.PlatformGetWindowMinimized = (void*)Marshal.GetFunctionPointerForDelegate(GetWindowMinimizedDelegate);
platformIO.PlatformSetWindowTitle = (void*)Marshal.GetFunctionPointerForDelegate(SetWindowTitleDelegate);
platformIO.PlatformSetWindowAlpha = (void*)Marshal.GetFunctionPointerForDelegate(SetWindowAlphaDelegate);
platformIO.PlatformUpdateWindow = (void*)Marshal.GetFunctionPointerForDelegate(UpdateWindowDelegate);
platformIO.PlatformGetWindowFramebufferScale = (void*)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 = (void*)(nint)SDL.GetWindowID(window);
Program.Logger.Log(null);
#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((void*)(nint)id);
}
private static nint GetSDLWindowFromViewport(ImGuiViewportPtr viewport)
{
return SDL.GetWindowFromID((uint)(nint)viewport.PlatformHandle);
}
private static void SetupPlatformHandles(ImGuiViewportPtr viewport, nint window)
{
viewport.PlatformHandle = (void*)(nint)SDL.GetWindowID(window);
viewport.PlatformHandleRaw = (void*)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.PlatformGetClipboardTextFn = null;
platformIo.PlatformSetClipboardTextFn = null;
platformIo.PlatformSetImeDataFn = null;
platformIo.PlatformOpenInShellFn = null;
ImGuiUserData.Free(io.BackendPlatformUserData);
io.BackendPlatformUserData = null;
}
}
public enum GamepadMode
{
AutoFirst,
AutoAll,
Manual
}
public enum MouseCaptureMode
{
Enabled,
EnabledAfterDrag,
Disabled
}
public unsafe static class ImGuiUserData where T : class
{
public static void* Store(T value)
{
if (value == null)
return null;
GCHandle handle = GCHandle.Alloc(value, GCHandleType.Normal);
return (void*)GCHandle.ToIntPtr(handle);
}
public static T Get(void* ptr)
{
if (ptr == null)
return null;
GCHandle handle = GCHandle.FromIntPtr((nint)ptr);
return (T)handle.Target;
}
public static void Free(void* ptr)
{
if (ptr == null)
return;
GCHandle handle = GCHandle.FromIntPtr((nint)ptr);
if (handle.IsAllocated)
handle.Free();
}
}