using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Hexa.NET.ImGui;
using Hexa.NET.OpenGL;
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 GL Gl = null!;
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 MousePendingLeaveFrame;
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 static PlatformData GetPlatformData() => ImGui.GetCurrentContext().Handle != null ? ImGuiUserData.Get(ImGui.GetIO().BackendPlatformUserData)! : null!;
private static nint _glContext;
public static bool Init(GL gl, nint window, nint ctx)
{
_glContext = ctx;
ImGuiIOPtr io = ImGui.GetIO();
PlatformData data = new PlatformData
{
Window = window,
WindowID = SDL.GetWindowID(window),
Gl = gl
};
io.BackendPlatformUserData = ImGuiUserData.Store(data);
io.BackendFlags |= ImGuiBackendFlags.HasMouseCursors;
io.BackendFlags |= ImGuiBackendFlags.HasSetMousePos;
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 = { "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;
// 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
data.MouseCanReportHoveredViewport = data.MouseCanUseGlobalState;
#endif
ImGuiPlatformIOPtr platformIo = ImGui.GetPlatformIO();
platformIo.PlatformGetClipboardTextFn = DelegateStorage.GetClipboardDelegate.GetPtrForDelegate();
platformIo.PlatformSetClipboardTextFn = DelegateStorage.SetClipboardDelegate.GetPtrForDelegate();
platformIo.PlatformSetImeDataFn = DelegateStorage.SetImeDataDelegate.GetPtrForDelegate();
platformIo.PlatformOpenInShellFn = DelegateStorage.OpenInShellDelegate.GetPtrForDelegate();
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(ctx, window);
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();
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;
if (data.MousePendingLeaveFrame != 0 && data.MousePendingLeaveFrame >= ImGui.GetFrameCount() && data.MouseButtonsDown == 0)
{
data.MouseWindowID = 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();
UpdateIme();
UpdateGamepads();
}
public static void UpdateMonitors()
{
PlatformData bd = GetPlatformData();
ImGuiPlatformIOPtr platformio = ImGui.GetPlatformIO();
platformio.Monitors.Resize(0);
bd.WantUpdateMonitors = false;
uint[] 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.
uint 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 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).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);
}
public static bool ProcessEvent(SDL.Event e)
{
ImGuiIOPtr io = ImGui.GetIO();
PlatformData data = GetPlatformData();
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);
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;
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;
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.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.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;
}
// This code is rather messy because some of the functions we need for full viewport support are not available in SDL < 2.0.4.
internal static void UpdateMouseData()
{
PlatformData bd = GetPlatformData();
ImGuiIOPtr io = ImGui.GetIO();
// 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);
}
else if (bd.MouseCaptureMode == MouseCaptureMode.EnabledAfterDrag)
{
bool wantCapture = false;
for (int buttonN = 0; buttonN < (int)ImGuiMouseButton.Count && !wantCapture; buttonN++)
{
if (ImGui.IsMouseDragging((ImGuiMouseButton)buttonN, 1.0f))
wantCapture = true;
}
SDL.CaptureMouse(wantCapture);
}
nint focusedWindow = SDL.GetKeyboardFocus();
bool isAppFocused = focusedWindow != nint.Zero && (bd.Window == focusedWindow || GetViewportForWindowId(SDL.GetWindowID(focusedWindow)) != null);
if (isAppFocused)
{
// (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(io.MousePos.X, io.MousePos.Y);
else
SDL.WarpMouseInWindow(bd.Window, io.MousePos.X, io.MousePos.Y);
}
// (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 = 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)
SDL.GetGlobalMouseState(out float mouseX, out float mouseY);
if (!io.ConfigFlags.HasFlag(ImGuiConfigFlags.ViewportsEnable))
{
SDL.GetWindowPosition(focusedWindow, out int windowX, out int windowY);
mouseX -= windowX;
mouseY -= windowY;
}
io.AddMousePosEvent(mouseX, mouseY);
}
}
// (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 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).
if (io.BackendFlags.HasFlag(ImGuiBackendFlags.HasMouseHoveredViewport))
{
uint mouseViewportID = 0;
ImGuiViewportPtr? mouseViewport = GetViewportForWindowId(bd.MouseWindowID);
if (mouseViewport != null && !mouseViewport.Value.IsNull)
mouseViewportID = mouseViewport.Value.ID;
io.AddMouseViewportEvent(mouseViewportID);
}
}
internal static void UpdateMouseCursor()
{
ImGuiIOPtr io = ImGui.GetIO();
if (io.ConfigFlags.HasFlag(ImGuiConfigFlags.NoMouseCursorChange))
return;
PlatformData bd = GetPlatformData();
ImGuiMouseCursor imguiCursor = ImGui.GetMouseCursor();
if (io.MouseDrawCursor || imguiCursor == ImGuiMouseCursor.None)
{
// Hide OS mouse cursor if imgui is drawing it or if it wants no cursor
SDL.HideCursor();
}
else
{
// Show OS mouse cursor
nint expectedCursor = bd.MouseCursors[(int)imguiCursor] != nint.Zero ? bd.MouseCursors[(int)imguiCursor] : bd.MouseCursors[(int)ImGuiMouseCursor.Arrow];
if (bd.MouseLastCursor != expectedCursor)
{
SDL.SetCursor(expectedCursor); // SDL function doesn't have an early out (see #6113)
bd.MouseLastCursor = expectedCursor;
}
SDL.ShowCursor();
}
}
internal static void UpdateGamepads()
{
PlatformData data = GetPlatformData();
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;
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;
// 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); // 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.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);
}
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++)
mergedValue |= SDL.GetGamepadButton(bd.Gamepads[i], button_no);
io.AddKeyEvent(key, mergedValue);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
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++)
{
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 = 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
{
bd.WantUpdateGamepadsList = true;
}
bd.GamepadMode = mode;
}
internal static void CloseGamepads()
{
PlatformData bd = GetPlatformData();
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;
}
// 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)
{
// Keypad doesn't have individual key values in SDL3 — always use scancode for these.
switch (scancode)
{
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();
ImGuiPlatformImeData imeData = data.ImeData;
nint window = SDL.GetKeyboardFocus();
// Stop previous input
if ((!(imeData.WantVisible == 1 || imeData.WantTextInput == 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)
{
// 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 - 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 || imeData.WantTextInput == 1))
SDL.StartTextInput(window);
}
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);
}
public class ViewPortData
{
public nint Window;
public nint ParentWindow;
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 glContext, nint window)
{
ImGuiPlatformIOPtr platformIO = ImGui.GetPlatformIO();
platformIO.PlatformCreateWindow = DelegateStorage.CreateWindowDelegate.GetPtrForDelegate();
platformIO.PlatformDestroyWindow = DelegateStorage.DestroyWindowDelegate.GetPtrForDelegate();
platformIO.PlatformShowWindow = DelegateStorage.ShowWindowDelegate.GetPtrForDelegate();
platformIO.PlatformSetWindowPos = DelegateStorage.SetWindowPosDelegate.GetPtrForDelegate();
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();
platformIO.PlatformSetWindowTitle = DelegateStorage.SetWindowTitleDelegate.GetPtrForDelegate();
platformIO.PlatformSetWindowAlpha = DelegateStorage.SetWindowAlphaDelegate.GetPtrForDelegate();
platformIO.PlatformUpdateWindow = DelegateStorage.UpdateWindowDelegate.GetPtrForDelegate();
platformIO.PlatformRenderWindow = DelegateStorage.RenderWindowDelegate.GetPtrForDelegate();
platformIO.PlatformSwapBuffers = DelegateStorage.SwapBuffersDelegate.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 = glContext
};
mainViewport.PlatformUserData = ImGuiUserData.Store(vd);
mainViewport.PlatformHandle = (void*)(nint)vd.WindowID;
#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__
viewport.PlatformHandleRaw = SDL.GetPointerProperty(SDL.GetWindowProperties(window), SDL.Props.WindowWin32HWNDPointer, 0).ToPointer();
#elif __APPLE__ && SDL_VIDEO_DRIVER_COCOA
viewport.PlatformHandleRaw = SDL.GetPointerProperty(SDL.GetWindowProperties(window), SDL.Props.WindowCocoaWindow, 0).ToPointer();
#endif
}
public static void Dispose()
{
PlatformData? data = GetPlatformData();
if (data != null)
{
if (data.ClipboardTextData != nint.Zero)
{
Marshal.FreeHGlobal(data.ClipboardTextData);
data.ClipboardTextData = nint.Zero;
}
foreach (nint cursor in data.MouseCursors)
{
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;
}
// @formatter:off — disable formatter after this line
public static class DelegateStorage
{
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate nint PlatformGetClipboardTextFn(nint ctx);
internal static readonly PlatformGetClipboardTextFn GetClipboardDelegate = GetClipboardText;
private static nint GetClipboardText(nint ctx)
{
PlatformData data = GetPlatformData();
if (data.ClipboardTextData != nint.Zero)
{
Marshal.FreeHGlobal(data.ClipboardTextData);
data.ClipboardTextData = nint.Zero;
}
// 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;
private static void SetClipboardText(nint ctx, nint text)
{
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.ImeDirty = true;
UpdateIme();
}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate bool PlatformOpenInShellFn(nint ctx, nint url);
internal static readonly PlatformOpenInShellFn OpenInShellDelegate = OpenInShell;
private static bool OpenInShell(nint ctx, nint url)
{
string managedUrl = Marshal.PtrToStringUTF8(url) ?? string.Empty;
return SDL.OpenURL(managedUrl);
}
// // // // // // // // // // // // // //
// Below is for Multi-Viewport Support //
// //
// // // // // // // // // // // // // //
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void ViewportCallback(ImGuiViewportPtr ctx);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void ViewportCallbackSetVec2(ImGuiViewportPtr ctx, Vector2 vec);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate Vector2 ViewportCallbackGetVec2(ImGuiViewportPtr ctx);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate bool ViewportCallbackBool(ImGuiViewportPtr ctx);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
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
flags |= SDL.WindowFlags.Resizable;
if (viewport.Flags.HasFlag(ImGuiViewportFlags.NoTaskBarIcon))
flags |= SDL.WindowFlags.Utility;
if (viewport.Flags.HasFlag(ImGuiViewportFlags.TopMost))
flags |= SDL.WindowFlags.AlwaysOnTop;
vd.Window = SDL.CreateWindow("No Title Yet", (int)viewport.Size.X, (int)viewport.Size.Y, flags);
vd.WindowID = SDL.GetWindowID(vd.Window);
#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
viewport.PlatformHandleRaw = SDL.GetPointerProperty(
SDL.GetWindowProperties(vd.Window),
SDL.Properties.WindowWin32HWNDPointer,
nint.Zero
).ToPointer();
#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;
}
ImGuiUserData.Free(viewport.PlatformUserData);
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)!;
#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);
}
internal static readonly ViewportCallback UpdateWindowDelegate = UpdateWindow;
private static void UpdateWindow(ImGuiViewportPtr viewport)
{
// 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)
{
ViewPortData vd = ImGuiUserData.Get(viewport.PlatformUserData)!;
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 _, 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);
}
}
// @formatter:on — enable formatter after this line
}
public enum GamepadMode
{
AutoFirst,
AutoAll,
Manual
}
public enum MouseCaptureMode
{
Enabled,
EnabledAfterDrag,
Disabled
}