// 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 #if OS == .MACOS { //#include //#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 // // 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(); }