ui/modules/SDL3/example/imgui_impl_sdl3.jai

690 lines
34 KiB
Plaintext

// dear imgui: Platform Backend for SDL3 (*EXPERIMENTAL*)
// This needs to be used along with a Renderer (e.g. DirectX11, OpenGL3, Vulkan..)
// (Info: SDL3 is a cross-platform general purpose library for handling windows, inputs, graphics context creation, etc.)
// (**IMPORTANT: SDL 3.0.0 is NOT YET RELEASED AND CURRENTLY HAS A FAST CHANGING API. THIS CODE BREAKS OFTEN**)
// Implemented features:
// [X] Platform: Clipboard support.
// [X] Platform: Mouse support. Can discriminate Mouse/TouchScreen.
// [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy SDL_SCANCODE_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set]
// [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
// [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
// Learn about Dear ImGui:
// - FAQ https://dearimgui.com/faq
// - Getting Started https://dearimgui.com/getting-started
// - Documentation https://dearimgui.com/docs (same as your local docs/ folder).
// - Introduction, links and more at the top of imgui.cpp
// CHANGELOG
// (minor and older changes stripped away, please see git history for details)
// 2024-07-22: Update for SDL3 api changes: SDL_GetGamepads() memory ownership change. (#7807)
// 2024-07-18: Update for SDL3 api changes: SDL_GetClipboardText() memory ownership change. (#7801)
// 2024-07-15: Update for SDL3 api changes: SDL_GetProperty() change to SDL_GetPointerProperty(). (#7794)
// 2024-07-02: Update for SDL3 api changes: SDLK_x renames and SDLK_KP_x removals (#7761, #7762).
// 2024-07-01: Update for SDL3 api changes: SDL_SetTextInputRect() changed to SDL_SetTextInputArea().
// 2024-06-26: Update for SDL3 api changes: SDL_StartTextInput()/SDL_StopTextInput()/SDL_SetTextInputRect() functions signatures.
// 2024-06-24: Update for SDL3 api changes: SDL_EVENT_KEY_DOWN/SDL_EVENT_KEY_UP contents.
// 2024-06-03; Update for SDL3 api changes: SDL_SYSTEM_CURSOR_ renames.
// 2024-05-15: Update for SDL3 api changes: SDLK_ renames.
// 2024-04-15: Inputs: Re-enable calling SDL_StartTextInput()/SDL_StopTextInput() as SDL3 no longer enables it by default and should play nicer with IME.
// 2024-02-13: Inputs: Fixed gamepad support. Handle gamepad disconnection. Added ImGui_ImplSDL3_SetGamepadMode().
// 2023-11-13: Updated for recent SDL3 API changes.
// 2023-10-05: Inputs: Added support for extra ImGuiKey values: F13 to F24 function keys, app back/forward keys.
// 2023-05-04: Fixed build on Emscripten/iOS/Android. (#6391)
// 2023-04-06: Inputs: Avoid calling SDL_StartTextInput()/SDL_StopTextInput() as they don't only pertain to IME. It's unclear exactly what their relation is to IME. (#6306)
// 2023-04-04: Inputs: Added support for io.AddMouseSourceEvent() to discriminate ImGuiMouseSource_Mouse/ImGuiMouseSource_TouchScreen. (#2702)
// 2023-02-23: Accept SDL_GetPerformanceCounter() not returning a monotonically increasing value. (#6189, #6114, #3644)
// 2023-02-07: Forked "imgui_impl_sdl2" into "imgui_impl_sdl3". Removed version checks for old feature. Refer to imgui_impl_sdl2.cpp for older changelog.
// SDL
// Let Jai's SDL3 module handle this
//#include <SDL3/SDL.h>
#if OS == .MACOS {
//#include <TargetConditionals.h>
//#assert false "Please include the platform header TargetConditionals.h somehow.";
}
#if OS == .WINDOWS {
// Let Jai's Windows module handle this
// #if !exists(WIN32_LEAN_AND_MEAN) {
// //WIN32_LEAN_AND_MEAN :: 1;
// }
// //#include <windows.h>
// // you should have included this in your main file
}
#if !#exists(__EMSCRIPTEN__) && !#exists(__ANDROID__) && !(#exists(__APPLE__) && #exists(TARGET_OS_IOS)) &&
!#exists(__amigaos4__) {
SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE :: true;
} else {
SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE :: false;
}
ImGui_ImplSDL3_GamepadMode :: enum {
AutoFirst;
AutoAll;
Manual;
}
// SDL Data
ImGui_ImplSDL3_Data :: struct {
Window : *SDL_Window;
Renderer : *SDL_Renderer;
Time : Uint64;
ClipboardTextData : *u8;
// IME handling
ImeWindow : *SDL_Window;
// Mouse handling
MouseWindowID : Uint32;
MouseButtonsDown : s32;
MouseCursors : [ImGui.MouseCursor.ImGuiMouseCursor_COUNT]*SDL_Cursor;
MouseLastCursor : *SDL_Cursor;
MousePendingLeaveFrame : s32;
MouseCanUseGlobalState : bool;
// Gamepad handling
Gamepads : [..]*SDL_Gamepad;
GamepadMode : ImGui_ImplSDL3_GamepadMode;
WantUpdateGamepadsList : bool;
}
// Backend data stored in io.BackendPlatformUserData to allow support for multiple Dear ImGui contexts
// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts.
// FIXME: multi-context support is not well tested and probably dysfunctional in this backend.
// FIXME: some shared resources (mouse cursor shape, gamepad) are mishandled when using multi-context.
ImGui_ImplSDL3_GetBackendData :: () -> *ImGui_ImplSDL3_Data #c_call {
return ifx ImGui.GetCurrentContext() then cast(*ImGui_ImplSDL3_Data) ImGui.GetIO().BackendPlatformUserData else null;
}
// Functions
ImGui_ImplSDL3_GetClipboardText :: (user_data: *void) -> *u8 #c_call {
bd : *ImGui_ImplSDL3_Data = ImGui_ImplSDL3_GetBackendData();
if bd.ClipboardTextData
SDL_free(bd.ClipboardTextData);
sdl_clipboard_text : *u8 = SDL_GetClipboardText();
bd.ClipboardTextData = ifx sdl_clipboard_text then SDL_strdup(sdl_clipboard_text) else null;
return bd.ClipboardTextData;
}
ImGui_ImplSDL3_SetClipboardText :: (user_data: *void, text: *u8) #c_call {
SDL_SetClipboardText(text);
}
ImGui_ImplSDL3_PlatformSetImeData :: (ctx: *ImGui.ImGuiContext, viewport: *ImGui.Viewport, data: *ImGui.PlatformImeData) #c_call {
bd : *ImGui_ImplSDL3_Data = ImGui_ImplSDL3_GetBackendData();
window : *SDL_Window = cast(*SDL_Window) viewport.PlatformHandle;
if ((data.WantVisible == false || bd.ImeWindow != window) && bd.ImeWindow != null) {
SDL_StopTextInput(bd.ImeWindow);
bd.ImeWindow = null;
}
if (data.WantVisible) {
r : SDL_Rect;
r.x = cast(s32) data.InputPos.x;
r.y = cast(s32) data.InputPos.y;
r.w = 1;
r.h = cast(s32) data.InputLineHeight;
SDL_SetTextInputArea(window, *r, 0);
SDL_StartTextInput(window);
bd.ImeWindow = window;
}
}
ImGui_ImplSDL3_KeyEventToImGuiKey :: (keycode: SDL_Keycode, scancode: SDL_Scancode) -> ImGui.Key {
// Keypad doesn't have individual key values in SDL3
if scancode == {
case SDL_Scancode.SDL_SCANCODE_KP_0; return ImGui.Key.Keypad0;
case SDL_Scancode.SDL_SCANCODE_KP_1; return ImGui.Key.Keypad1;
case SDL_Scancode.SDL_SCANCODE_KP_2; return ImGui.Key.Keypad2;
case SDL_Scancode.SDL_SCANCODE_KP_3; return ImGui.Key.Keypad3;
case SDL_Scancode.SDL_SCANCODE_KP_4; return ImGui.Key.Keypad4;
case SDL_Scancode.SDL_SCANCODE_KP_5; return ImGui.Key.Keypad5;
case SDL_Scancode.SDL_SCANCODE_KP_6; return ImGui.Key.Keypad6;
case SDL_Scancode.SDL_SCANCODE_KP_7; return ImGui.Key.Keypad7;
case SDL_Scancode.SDL_SCANCODE_KP_8; return ImGui.Key.Keypad8;
case SDL_Scancode.SDL_SCANCODE_KP_9; return ImGui.Key.Keypad9;
case SDL_Scancode.SDL_SCANCODE_KP_PERIOD; return ImGui.Key.KeypadDecimal;
case SDL_Scancode.SDL_SCANCODE_KP_DIVIDE; return ImGui.Key.KeypadDivide;
case SDL_Scancode.SDL_SCANCODE_KP_MULTIPLY; return ImGui.Key.KeypadMultiply;
case SDL_Scancode.SDL_SCANCODE_KP_MINUS; return ImGui.Key.KeypadSubtract;
case SDL_Scancode.SDL_SCANCODE_KP_PLUS; return ImGui.Key.KeypadAdd;
case SDL_Scancode.SDL_SCANCODE_KP_ENTER; return ImGui.Key.KeypadEnter;
case SDL_Scancode.SDL_SCANCODE_KP_EQUALS; return ImGui.Key.KeypadEqual;
}
if keycode == {
case SDLK_TAB; return ImGui.Key.Tab;
case SDLK_LEFT; return ImGui.Key.LeftArrow;
case SDLK_RIGHT; return ImGui.Key.RightArrow;
case SDLK_UP; return ImGui.Key.UpArrow;
case SDLK_DOWN; return ImGui.Key.DownArrow;
case SDLK_PAGEUP; return ImGui.Key.PageUp;
case SDLK_PAGEDOWN; return ImGui.Key.PageDown;
case SDLK_HOME; return ImGui.Key.Home;
case SDLK_END; return ImGui.Key.End;
case SDLK_INSERT; return ImGui.Key.Insert;
case SDLK_DELETE; return ImGui.Key.Delete;
case SDLK_BACKSPACE; return ImGui.Key.Backspace;
case SDLK_SPACE; return ImGui.Key.Space;
case SDLK_RETURN; return ImGui.Key.Enter;
case SDLK_ESCAPE; return ImGui.Key.Escape;
case SDLK_APOSTROPHE; return ImGui.Key.Apostrophe;
case SDLK_COMMA; return ImGui.Key.Comma;
case SDLK_MINUS; return ImGui.Key.Minus;
case SDLK_PERIOD; return ImGui.Key.Period;
case SDLK_SLASH; return ImGui.Key.Slash;
case SDLK_SEMICOLON; return ImGui.Key.Semicolon;
case SDLK_EQUALS; return ImGui.Key.Equal;
case SDLK_LEFTBRACKET; return ImGui.Key.LeftBracket;
case SDLK_BACKSLASH; return ImGui.Key.Backslash;
case SDLK_RIGHTBRACKET; return ImGui.Key.RightBracket;
case SDLK_GRAVE; return ImGui.Key.GraveAccent;
case SDLK_CAPSLOCK; return ImGui.Key.CapsLock;
case SDLK_SCROLLLOCK; return ImGui.Key.ScrollLock;
case SDLK_NUMLOCKCLEAR; return ImGui.Key.NumLock;
case SDLK_PRINTSCREEN; return ImGui.Key.PrintScreen;
case SDLK_PAUSE; return ImGui.Key.Pause;
case SDLK_LCTRL; return ImGui.Key.LeftCtrl;
case SDLK_LSHIFT; return ImGui.Key.LeftShift;
case SDLK_LALT; return ImGui.Key.LeftAlt;
case SDLK_LGUI; return ImGui.Key.LeftSuper;
case SDLK_RCTRL; return ImGui.Key.RightCtrl;
case SDLK_RSHIFT; return ImGui.Key.RightShift;
case SDLK_RALT; return ImGui.Key.RightAlt;
case SDLK_RGUI; return ImGui.Key.RightSuper;
case SDLK_APPLICATION; return ImGui.Key.Menu;
case SDLK_0; return ImGui.Key._0;
case SDLK_1; return ImGui.Key._1;
case SDLK_2; return ImGui.Key._2;
case SDLK_3; return ImGui.Key._3;
case SDLK_4; return ImGui.Key._4;
case SDLK_5; return ImGui.Key._5;
case SDLK_6; return ImGui.Key._6;
case SDLK_7; return ImGui.Key._7;
case SDLK_8; return ImGui.Key._8;
case SDLK_9; return ImGui.Key._9;
case SDLK_A; return ImGui.Key.A;
case SDLK_B; return ImGui.Key.B;
case SDLK_C; return ImGui.Key.C;
case SDLK_D; return ImGui.Key.D;
case SDLK_E; return ImGui.Key.E;
case SDLK_F; return ImGui.Key.F;
case SDLK_G; return ImGui.Key.G;
case SDLK_H; return ImGui.Key.H;
case SDLK_I; return ImGui.Key.I;
case SDLK_J; return ImGui.Key.J;
case SDLK_K; return ImGui.Key.K;
case SDLK_L; return ImGui.Key.L;
case SDLK_M; return ImGui.Key.M;
case SDLK_N; return ImGui.Key.N;
case SDLK_O; return ImGui.Key.O;
case SDLK_P; return ImGui.Key.P;
case SDLK_Q; return ImGui.Key.Q;
case SDLK_R; return ImGui.Key.R;
case SDLK_S; return ImGui.Key.S;
case SDLK_T; return ImGui.Key.T;
case SDLK_U; return ImGui.Key.U;
case SDLK_V; return ImGui.Key.V;
case SDLK_W; return ImGui.Key.W;
case SDLK_X; return ImGui.Key.X;
case SDLK_Y; return ImGui.Key.Y;
case SDLK_Z; return ImGui.Key.Z;
case SDLK_F1; return ImGui.Key.F1;
case SDLK_F2; return ImGui.Key.F2;
case SDLK_F3; return ImGui.Key.F3;
case SDLK_F4; return ImGui.Key.F4;
case SDLK_F5; return ImGui.Key.F5;
case SDLK_F6; return ImGui.Key.F6;
case SDLK_F7; return ImGui.Key.F7;
case SDLK_F8; return ImGui.Key.F8;
case SDLK_F9; return ImGui.Key.F9;
case SDLK_F10; return ImGui.Key.F10;
case SDLK_F11; return ImGui.Key.F11;
case SDLK_F12; return ImGui.Key.F12;
case SDLK_F13; return ImGui.Key.F13;
case SDLK_F14; return ImGui.Key.F14;
case SDLK_F15; return ImGui.Key.F15;
case SDLK_F16; return ImGui.Key.F16;
case SDLK_F17; return ImGui.Key.F17;
case SDLK_F18; return ImGui.Key.F18;
case SDLK_F19; return ImGui.Key.F19;
case SDLK_F20; return ImGui.Key.F20;
case SDLK_F21; return ImGui.Key.F21;
case SDLK_F22; return ImGui.Key.F22;
case SDLK_F23; return ImGui.Key.F23;
case SDLK_F24; return ImGui.Key.F24;
case SDLK_AC_BACK; return ImGui.Key.AppBack;
case SDLK_AC_FORWARD; return ImGui.Key.AppForward;
}
return ImGui.Key.None;
}
ImGui_ImplSDL3_UpdateKeyModifiers :: (sdl_key_mods: SDL_Keymod) {
io : *ImGui.IO = ImGui.GetIO();
io.AddKeyEvent(io, ImGui.Key.Mod_Ctrl, (sdl_key_mods & SDL_KMOD_CTRL) != 0);
io.AddKeyEvent(io, ImGui.Key.Mod_Shift, (sdl_key_mods & SDL_KMOD_SHIFT) != 0);
io.AddKeyEvent(io, ImGui.Key.Mod_Alt, (sdl_key_mods & SDL_KMOD_ALT) != 0);
io.AddKeyEvent(io, ImGui.Key.Mod_Super, (sdl_key_mods & SDL_KMOD_GUI) != 0);
}
// You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs.
// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data.
// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data.
// Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags.
// If you have multiple SDL events and some of them are not meant to be used by dear imgui, you may need to filter events based on their windowID field.
ImGui_ImplSDL3_ProcessEvent :: (event: *SDL_Event) -> bool {
bd : *ImGui_ImplSDL3_Data = ImGui_ImplSDL3_GetBackendData();
assert(bd != null && "Context or backend not initialized! Did you call ImGui_ImplSDL3_Init()?");
io : *ImGui.IO = ImGui.GetIO();
if event.type == {
case xx SDL_EVENT_MOUSE_MOTION;
mouse_pos : ImGui.ImVec2 = .{cast(float) event.motion.x, cast(float) event.motion.y};
io.AddMouseSourceEvent(io, ifx event.motion.which == SDL_TOUCH_MOUSEID then ImGui.MouseSource.TouchScreen else ImGui.MouseSource.Mouse);
io.AddMousePosEvent(io, mouse_pos.x, mouse_pos.y);
return true;
case xx SDL_EVENT_MOUSE_WHEEL;
//IMGUI_DEBUG_LOG("wheel %.2f %.2f, precise %.2f %.2f\n", (float)event.wheel.x, (float)event.wheel.y, event.wheel.preciseX, event.wheel.preciseY);
wheel_x : float = -event.wheel.x;
wheel_y : float = event.wheel.y;
#if #exists (__EMSCRIPTEN__) {
wheel_x /= 100.0;
}
io.AddMouseSourceEvent(io, ifx event.wheel.which == SDL_TOUCH_MOUSEID then ImGui.MouseSource.TouchScreen else ImGui.MouseSource.Mouse);
io.AddMouseWheelEvent(io, wheel_x, wheel_y);
return true;
case xx SDL_EVENT_MOUSE_BUTTON_DOWN;
#through;
case xx SDL_EVENT_MOUSE_BUTTON_UP;
mouse_button : s32 = -1;
if (event.button.button == SDL_BUTTON_LEFT) { mouse_button = 0; }
if (event.button.button == SDL_BUTTON_RIGHT) { mouse_button = 1; }
if (event.button.button == SDL_BUTTON_MIDDLE) { mouse_button = 2; }
if (event.button.button == SDL_BUTTON_X1) { mouse_button = 3; }
if (event.button.button == SDL_BUTTON_X2) { mouse_button = 4; }
io.AddMouseSourceEvent(io, ifx event.button.which == SDL_TOUCH_MOUSEID then ImGui.MouseSource.TouchScreen else ImGui.MouseSource.Mouse);
io.AddMouseButtonEvent(io, mouse_button, (event.type == xx SDL_EVENT_MOUSE_BUTTON_DOWN));
bd.MouseButtonsDown = xx ifx (event.type == xx SDL_EVENT_MOUSE_BUTTON_DOWN) then (bd.MouseButtonsDown | (1 << mouse_button)) else (bd.MouseButtonsDown & ~(1 << mouse_button));
return true;
case xx SDL_EVENT_TEXT_INPUT;
io.AddInputCharactersUTF8(io, event.text.text);
return true;
case xx SDL_EVENT_KEY_DOWN;
#through;
case xx SDL_EVENT_KEY_UP;
//IMGUI_DEBUG_LOG("SDL_EVENT_KEY_%d: key=%d, scancode=%d, mod=%X\n", (event.type == SDL_EVENT_KEY_DOWN) ? "DOWN" : "UP", event.key.key, event.key.scancode, event.key.mod);
ImGui_ImplSDL3_UpdateKeyModifiers(cast(SDL_Keymod) event.key.mod);
key : ImGui.Key = ImGui_ImplSDL3_KeyEventToImGuiKey(event.key.key, event.key.scancode);
io.AddKeyEvent(io, key, (event.type == xx SDL_EVENT_KEY_DOWN));
io.SetKeyEventNativeData(io, key, xx event.key.key, xx event.key.scancode, xx event.key.scancode); // To support legacy indexing (<1.87 user code). Legacy backend uses SDLK_*** as indices to IsKeyXXX() functions.
return true;
case xx SDL_EVENT_WINDOW_MOUSE_ENTER;
bd.MouseWindowID = event.window.windowID;
bd.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 clear mouse position. This is why
// we delay process the SDL_WINDOWEVENT_LEAVE events by one frame. See issue #5012 for details.
// FIXME: Unconfirmed whether this is still needed with SDL3.
case xx SDL_EVENT_WINDOW_MOUSE_LEAVE;
bd.MousePendingLeaveFrame = ImGui.GetFrameCount() + 1;
return true;
case xx SDL_EVENT_WINDOW_FOCUS_GAINED;
io.AddFocusEvent(io, true);
return true;
case xx SDL_EVENT_WINDOW_FOCUS_LOST;
io.AddFocusEvent(io, false);
return true;
case xx SDL_EVENT_GAMEPAD_ADDED;
#through;
case xx SDL_EVENT_GAMEPAD_REMOVED;
bd.WantUpdateGamepadsList = true;
return true;
}
return false;
}
ImGui_ImplSDL3_SetupPlatformHandles :: (viewport: *ImGui.Viewport, window: *SDL_Window) {
viewport.PlatformHandle = window;
viewport.PlatformHandleRaw = null;
#if #exists(_WIN32) && !#exists(__WINRT__) {
viewport.PlatformHandleRaw = cast(HWND) SDL_GetPointerProperty(SDL_GetWindowProperties(window),
SDL_PROP_WINDOW_WIN32_HWND_POINTER, null);
} else #if #exists (__APPLE__) && #exists (SDL_VIDEO_DRIVER_COCOA) {
viewport.PlatformHandleRaw = SDL_GetPointerProperty(SDL_GetWindowProperties(window),
SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, null);
}
}
ImGui_ImplSDL3_Init :: (window: *SDL_Window, renderer: *SDL_Renderer, sdl_gl_context: *void) -> bool {
io : *ImGui.IO = ImGui.GetIO();
ImGui.IMGUI_CHECKVERSION();
assert(io.BackendPlatformUserData == null && "Already initialized a platform backend!");
// Check and store if we are on a SDL backend that supports global mouse position
// ("wayland" and "rpi" don't support it, but we chose to use a white-list instead of a black-list)
mouse_can_use_global_state : bool = false;
#if SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE {
//sdl_backend : *u8 = SDL_GetCurrentVideoDriver();
//global_mouse_whitelist : []*u8 = .[ "windows", "cocoa", "x11", "DIVE", "VMAN" ];
//for n : 0..global_mouse_whitelist.count {
// //if (strncmp(sdl_backend, global_mouse_whitelist[n], strlen(global_mouse_whitelist[n])) == 0)
// // mouse_can_use_global_state = true;
//}
}
// Setup backend capabilities flags
bd : *ImGui_ImplSDL3_Data = New(ImGui_ImplSDL3_Data);
io.BackendPlatformUserData = cast(*void) bd;
io.BackendPlatformName = "imgui_impl_sdl3";
io.BackendFlags_ |= ImGui.BackendFlags.HasMouseCursors; // We can honor GetMouseCursor() values (optional)
io.BackendFlags_ |= ImGui.BackendFlags.HasSetMousePos; // We can honor io.WantSetMousePos requests (optional, rarely used)
bd.Window = window;
bd.Renderer = renderer;
bd.MouseCanUseGlobalState = mouse_can_use_global_state;
io.SetClipboardTextFn = ImGui_ImplSDL3_SetClipboardText;
io.GetClipboardTextFn = ImGui_ImplSDL3_GetClipboardText;
io.ClipboardUserData = null;
io.PlatformSetImeDataFn = ImGui_ImplSDL3_PlatformSetImeData;
// Gamepad handling
bd.GamepadMode = ImGui_ImplSDL3_GamepadMode.AutoFirst;
bd.WantUpdateGamepadsList = true;
// Load mouse cursors
bd.MouseCursors[ImGui.MouseCursor.Arrow] = SDL_CreateSystemCursor(.SDL_SYSTEM_CURSOR_DEFAULT);
bd.MouseCursors[ImGui.MouseCursor.TextInput] = SDL_CreateSystemCursor(.SDL_SYSTEM_CURSOR_TEXT);
bd.MouseCursors[ImGui.MouseCursor.ResizeAll] = SDL_CreateSystemCursor(.SDL_SYSTEM_CURSOR_MOVE);
bd.MouseCursors[ImGui.MouseCursor.ResizeNS] = SDL_CreateSystemCursor(.SDL_SYSTEM_CURSOR_NS_RESIZE);
bd.MouseCursors[ImGui.MouseCursor.ResizeEW] = SDL_CreateSystemCursor(.SDL_SYSTEM_CURSOR_EW_RESIZE);
bd.MouseCursors[ImGui.MouseCursor.ResizeNESW] = SDL_CreateSystemCursor(.SDL_SYSTEM_CURSOR_NESW_RESIZE);
bd.MouseCursors[ImGui.MouseCursor.ResizeNWSE] = SDL_CreateSystemCursor(.SDL_SYSTEM_CURSOR_NWSE_RESIZE);
bd.MouseCursors[ImGui.MouseCursor.Hand] = SDL_CreateSystemCursor(.SDL_SYSTEM_CURSOR_POINTER);
bd.MouseCursors[ImGui.MouseCursor.NotAllowed] = SDL_CreateSystemCursor(.SDL_SYSTEM_CURSOR_NOT_ALLOWED);
// Set platform dependent data in viewport
// Our mouse update function expect PlatformHandle to be filled for the main viewport
main_viewport : *ImGui.Viewport = ImGui.GetMainViewport();
ImGui_ImplSDL3_SetupPlatformHandles(main_viewport, window);
// From 2.0.5: Set SDL hint to receive mouse click events on window focus, otherwise SDL doesn't emit the event.
// Without this, when clicking to gain focus, our widgets wouldn't activate even though they showed as hovered.
// (This is unfortunately a global SDL setting, so enabling it might have a side-effect on your application.
// It is unlikely to make a difference, but if your app absolutely needs to ignore the initial on-focus click:
// you can ignore SDL_EVENT_MOUSE_BUTTON_DOWN events coming right after a SDL_WINDOWEVENT_FOCUS_GAINED)
#if SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH {
SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
}
// From 2.0.22: Disable auto-capture, this is preventing drag and drop across multiple windows (see #5710)
#if SDL_HINT_MOUSE_AUTO_CAPTURE {
SDL_SetHint(SDL_HINT_MOUSE_AUTO_CAPTURE, "0");
}
return true;
}
ImGui_ImplSDL3_InitForOpenGL :: (window: *SDL_Window, sdl_gl_context: *void) -> bool {
return ImGui_ImplSDL3_Init(window, null, sdl_gl_context);
}
ImGui_ImplSDL3_InitForVulkan :: (window: *SDL_Window) -> bool {
return ImGui_ImplSDL3_Init(window, null, null);
}
ImGui_ImplSDL3_InitForD3D :: (window: *SDL_Window) -> bool {
#if #exists (_WIN32) {
assert(0 && "Unsupported");
}
return ImGui_ImplSDL3_Init(window, null, null);
}
ImGui_ImplSDL3_InitForMetal :: (window: *SDL_Window) -> bool {
return ImGui_ImplSDL3_Init(window, null, null);
}
ImGui_ImplSDL3_InitForSDLRenderer :: (window: *SDL_Window, renderer: *SDL_Renderer) -> bool {
return ImGui_ImplSDL3_Init(window, renderer, null);
}
ImGui_ImplSDL3_InitForOther :: (window: *SDL_Window) -> bool {
return ImGui_ImplSDL3_Init(window, null, null);
}
ImGui_ImplSDL3_Shutdown :: () {
bd : *ImGui_ImplSDL3_Data = ImGui_ImplSDL3_GetBackendData();
assert(bd != null && "No platform backend to shutdown, or already shutdown?");
io : *ImGui.IO = ImGui.GetIO();
if (bd.ClipboardTextData)
SDL_free(bd.ClipboardTextData);
for cursor_n : 0..ImGui.MouseCursor.ImGuiMouseCursor_COUNT - 1
SDL_DestroyCursor(bd.MouseCursors[cursor_n]);
ImGui_ImplSDL3_CloseGamepads();
io.BackendPlatformName = null;
io.BackendPlatformUserData = null;
io.BackendFlags_ &= ~(ImGui.BackendFlags.HasMouseCursors | ImGui.BackendFlags.HasSetMousePos | ImGui.BackendFlags.HasGamepad);
free(bd);
}
ImGui_ImplSDL3_UpdateMouseData :: () {
bd : *ImGui_ImplSDL3_Data = ImGui_ImplSDL3_GetBackendData();
io : *ImGui.IO = ImGui.GetIO();
// We forward mouse input when hovered or captured (via SDL_EVENT_MOUSE_MOTION) or when focused (below)
#if SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE {
// SDL_CaptureMouse() let the OS know e.g. that our imgui drag outside the SDL window boundaries
// shouldn't e.g. trigger other operations outside
SDL_CaptureMouse(xx ifx (bd.MouseButtonsDown != 0) then SDL_TRUE else SDL_FALSE);
focused_window : *SDL_Window = SDL_GetKeyboardFocus();
is_app_focused : bool = (bd.Window == focused_window);
} else {
focused_window : *SDL_Window = bd.Window;
is_app_focused : bool = (SDL_GetWindowFlags(bd.Window) & SDL_WINDOW_INPUT_FOCUS) != 0;
// SDL 2.0.3 and non-windowed systems: single-viewport only
}
if (is_app_focused)
{
// (Optional) Set OS mouse position from Dear ImGui if requested (rarely used, only when ImGuiConfigFlags_NavEnableSetMousePos is enabled by user)
if (io.WantSetMousePos)
SDL_WarpMouseInWindow(bd.Window, io.MousePos.x, io.MousePos.y);
// (Optional) Fallback to provide mouse position when focused (SDL_EVENT_MOUSE_MOTION already provides this when hovered or captured)
if (bd.MouseCanUseGlobalState && bd.MouseButtonsDown == 0)
{
// 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)
mouse_x_global, mouse_y_global : float;
window_x, window_y : s32;
SDL_GetGlobalMouseState(*mouse_x_global, *mouse_y_global);
SDL_GetWindowPosition(focused_window, *window_x, *window_y);
io.AddMousePosEvent(io, mouse_x_global - window_x, mouse_y_global - window_y);
}
}
}
ImGui_ImplSDL3_UpdateMouseCursor :: () {
io : *ImGui.IO = ImGui.GetIO();
if (io.ConfigFlags_ & ImGui.ConfigFlags.NoMouseCursorChange)
return;
bd : *ImGui_ImplSDL3_Data = ImGui_ImplSDL3_GetBackendData();
imgui_cursor : ImGui.MouseCursor = ImGui.GetMouseCursor();
if (io.MouseDrawCursor || imgui_cursor == ImGui.MouseCursor.None)
{
// Hide OS mouse cursor if imgui is drawing it or if it wants no cursor
SDL_HideCursor();
}
else
{
// Show OS mouse cursor
expected_cursor : *SDL_Cursor = ifx bd.MouseCursors[imgui_cursor] then bd.MouseCursors[imgui_cursor] else bd.MouseCursors[ImGui.MouseCursor.Arrow];
if (bd.MouseLastCursor != expected_cursor)
{
SDL_SetCursor(expected_cursor); // SDL function doesn't have an early out (see #6113)
bd.MouseLastCursor = expected_cursor;
}
SDL_ShowCursor();
}
}
ImGui_ImplSDL3_CloseGamepads :: () {
bd : *ImGui_ImplSDL3_Data = ImGui_ImplSDL3_GetBackendData();
if (bd.GamepadMode != ImGui_ImplSDL3_GamepadMode.Manual)
for gamepad : bd.Gamepads
SDL_CloseGamepad(gamepad);
array_reset(*bd.Gamepads);
}
ImGui_ImplSDL3_SetGamepadMode :: (mode: ImGui_ImplSDL3_GamepadMode, manual_gamepads_array: **SDL_Gamepad, manual_gamepads_count: s32) {
bd : *ImGui_ImplSDL3_Data = ImGui_ImplSDL3_GetBackendData();
ImGui_ImplSDL3_CloseGamepads();
if (mode == ImGui_ImplSDL3_GamepadMode.Manual) {
assert(manual_gamepads_array != null && manual_gamepads_count > 0);
for n : 0..manual_gamepads_count - 1
array_add(*bd.Gamepads, manual_gamepads_array[n]);
} else {
assert(manual_gamepads_array == null && manual_gamepads_count <= 0);
bd.WantUpdateGamepadsList = true;
}
bd.GamepadMode = mode;
}
ImGui_ImplSDL3_UpdateGamepadButton :: (bd : *ImGui_ImplSDL3_Data, io : *ImGui.IO, key:ImGui.Key, button_no: SDL_GamepadButton) {
merged_value : bool = false;
for gamepad : bd.Gamepads
merged_value |= SDL_GetGamepadButton(gamepad, button_no) != 0;
io.AddKeyEvent(io, key, merged_value);
}
Saturate :: (v: float) -> float {
return ifx v < 0.0 then 0.0 else ifx v > 1.0 then 1.0 else v;
}
ImGui_ImplSDL3_UpdateGamepadAnalog :: (bd : *ImGui_ImplSDL3_Data, io : *ImGui.IO, key: ImGui.Key, axis_no: SDL_GamepadAxis, v0: float, v1: float) {
merged_value : float = 0.0;
for gamepad : bd.Gamepads{
vn : float = Saturate(cast(float) (SDL_GetGamepadAxis(gamepad, axis_no) - v0) / cast(float)(v1 - v0));
if (merged_value < vn)
merged_value = vn;
}
io.AddKeyAnalogEvent(io, key, merged_value > 0.1, merged_value);
}
ImGui_ImplSDL3_UpdateGamepads :: () {
io : *ImGui.IO = ImGui.GetIO();
bd : *ImGui_ImplSDL3_Data = ImGui_ImplSDL3_GetBackendData();
// Update list of gamepads to use
if (bd.WantUpdateGamepadsList && bd.GamepadMode != ImGui_ImplSDL3_GamepadMode.Manual) {
ImGui_ImplSDL3_CloseGamepads();
sdl_gamepads_count : s32 = 0;
sdl_gamepads : *SDL_JoystickID = SDL_GetGamepads(*sdl_gamepads_count);
for n : 0..sdl_gamepads_count - 1 {
gamepad : *SDL_Gamepad = SDL_OpenGamepad(sdl_gamepads[n]);
if gamepad {
array_add(*bd.Gamepads, gamepad);
if (bd.GamepadMode == ImGui_ImplSDL3_GamepadMode.AutoFirst)
break;
}
}
bd.WantUpdateGamepadsList = false;
}
// FIXME: Technically feeding gamepad shouldn't depend on this now that they are regular inputs.
if ((io.ConfigFlags_ & ImGui.ConfigFlags.NavEnableGamepad) == 0)
return;
io.BackendFlags_ &= ~ImGui.BackendFlags.HasGamepad;
if (bd.Gamepads.count == 0)
return;
io.BackendFlags_ |= ImGui.BackendFlags.HasGamepad;
// Update gamepad inputs
thumb_dead_zone : s32 = 8000; // SDL_gamepad.h suggests using this value.
ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGui.Key.GamepadStart, SDL_GAMEPAD_BUTTON_START);
ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGui.Key.GamepadBack, SDL_GAMEPAD_BUTTON_BACK);
ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGui.Key.GamepadFaceLeft, SDL_GAMEPAD_BUTTON_WEST); // Xbox X, PS Square
ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGui.Key.GamepadFaceRight, SDL_GAMEPAD_BUTTON_EAST); // Xbox B, PS Circle
ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGui.Key.GamepadFaceUp, SDL_GAMEPAD_BUTTON_NORTH); // Xbox Y, PS Triangle
ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGui.Key.GamepadFaceDown, SDL_GAMEPAD_BUTTON_SOUTH); // Xbox A, PS Cross
ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGui.Key.GamepadDpadLeft, SDL_GAMEPAD_BUTTON_DPAD_LEFT);
ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGui.Key.GamepadDpadRight, SDL_GAMEPAD_BUTTON_DPAD_RIGHT);
ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGui.Key.GamepadDpadUp, SDL_GAMEPAD_BUTTON_DPAD_UP);
ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGui.Key.GamepadDpadDown, SDL_GAMEPAD_BUTTON_DPAD_DOWN);
ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGui.Key.GamepadL1, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER);
ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGui.Key.GamepadR1, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER);
ImGui_ImplSDL3_UpdateGamepadAnalog(bd, io, ImGui.Key.GamepadL2, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, 0.0, 32767);
ImGui_ImplSDL3_UpdateGamepadAnalog(bd, io, ImGui.Key.GamepadR2, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, 0.0, 32767);
ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGui.Key.GamepadL3, SDL_GAMEPAD_BUTTON_LEFT_STICK);
ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGui.Key.GamepadR3, SDL_GAMEPAD_BUTTON_RIGHT_STICK);
ImGui_ImplSDL3_UpdateGamepadAnalog(bd, io, ImGui.Key.GamepadLStickLeft, SDL_GAMEPAD_AXIS_LEFTX, xx -thumb_dead_zone, -32768);
ImGui_ImplSDL3_UpdateGamepadAnalog(bd, io, ImGui.Key.GamepadLStickRight, SDL_GAMEPAD_AXIS_LEFTX, xx +thumb_dead_zone, +32767);
ImGui_ImplSDL3_UpdateGamepadAnalog(bd, io, ImGui.Key.GamepadLStickUp, SDL_GAMEPAD_AXIS_LEFTY, xx -thumb_dead_zone, -32768);
ImGui_ImplSDL3_UpdateGamepadAnalog(bd, io, ImGui.Key.GamepadLStickDown, SDL_GAMEPAD_AXIS_LEFTY, xx +thumb_dead_zone, +32767);
ImGui_ImplSDL3_UpdateGamepadAnalog(bd, io, ImGui.Key.GamepadRStickLeft, SDL_GAMEPAD_AXIS_RIGHTX, xx -thumb_dead_zone, -32768);
ImGui_ImplSDL3_UpdateGamepadAnalog(bd, io, ImGui.Key.GamepadRStickRight, SDL_GAMEPAD_AXIS_RIGHTX, xx +thumb_dead_zone, +32767);
ImGui_ImplSDL3_UpdateGamepadAnalog(bd, io, ImGui.Key.GamepadRStickUp, SDL_GAMEPAD_AXIS_RIGHTY, xx -thumb_dead_zone, -32768);
ImGui_ImplSDL3_UpdateGamepadAnalog(bd, io, ImGui.Key.GamepadRStickDown, SDL_GAMEPAD_AXIS_RIGHTY, xx +thumb_dead_zone, +32767);
}
ImGui_ImplSDL3_NewFrame :: () {
bd : *ImGui_ImplSDL3_Data = ImGui_ImplSDL3_GetBackendData();
assert(bd != null && "Context or backend not initialized! Did you call ImGui_ImplSDL3_Init()?");
io : *ImGui.IO = ImGui.GetIO();
// Setup display size (every frame to accommodate for window resizing)
w, h : s32;
display_w, display_h : s32;
SDL_GetWindowSize(bd.Window, *w, *h);
if (SDL_GetWindowFlags(bd.Window) & SDL_WINDOW_MINIMIZED) {
w = 0;
h = 0;
}
SDL_GetWindowSizeInPixels(bd.Window, *display_w, *display_h);
io.DisplaySize = ImGui.ImVec2.{cast(float)w, cast(float)h};
if (w > 0 && h > 0)
io.DisplayFramebufferScale = ImGui.ImVec2.{cast(float) display_w / w, cast(float) display_h / h};
// Setup time step (we don't use SDL_GetTicks() because it is using millisecond resolution)
// (Accept SDL_GetPerformanceCounter() not returning a monotonically increasing value. Happens in VMs and Emscripten, see #6189, #6114, #3644)
frequency : Uint64 = SDL_GetPerformanceFrequency();
current_time : Uint64 = SDL_GetPerformanceCounter();
if (current_time <= bd.Time)
current_time = bd.Time + 1;
io.DeltaTime = ifx bd.Time > 0 then cast(float)(cast(float64)(current_time - bd.Time) / frequency) else cast(float)(1.0 / 60.0);
bd.Time = current_time;
if (bd.MousePendingLeaveFrame && bd.MousePendingLeaveFrame >= ImGui.GetFrameCount() && bd.MouseButtonsDown == 0) {
bd.MouseWindowID = 0;
bd.MousePendingLeaveFrame = 0;
io.AddMousePosEvent(io, FLOAT32_MIN, FLOAT32_MAX);
}
ImGui_ImplSDL3_UpdateMouseData();
ImGui_ImplSDL3_UpdateMouseCursor();
// Update game controllers (if enabled and available)
ImGui_ImplSDL3_UpdateGamepads();
}