1205 lines
56 KiB
C#
1205 lines
56 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// Implementation of SDL3 platform backend for ImGui.
|
|
/// https://github.com/ocornut/imgui/blob/master/backends/imgui_impl_sdl3.h
|
|
/// </summary>
|
|
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<PlatformData>.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<PlatformData>.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<ViewPortData>.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<PlatformData>.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<ImGuiPlatformImeData>(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<ViewPortData>.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<ViewPortData>.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<ViewPortData>.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<ViewPortData>.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<ViewPortData>.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<ViewPortData>.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<ViewPortData>.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<ViewPortData>.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<ViewPortData>.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<ViewPortData>.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<ViewPortData>.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<ViewPortData>.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<ViewPortData>.Get(viewport.PlatformUserData)!;
|
|
SDL.RaiseWindow(vd.Window);
|
|
}
|
|
|
|
internal static readonly ViewportCallbackBool GetWindowFocusDelegate = GetWindowFocus;
|
|
private static bool GetWindowFocus(ImGuiViewportPtr viewport)
|
|
{
|
|
ViewPortData vd = ImGuiUserData<ViewPortData>.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<ViewPortData>.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<ViewPortData>.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<ViewPortData>.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
|
|
} |