From 39bd33a192cd312400a5d027c75f819a54e88be4 Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Fri, 22 Nov 2024 18:01:47 +0000 Subject: [PATCH 01/72] add imgui_impl_emscripten platform backend --- backends/imgui_impl_emscripten.cpp | 543 +++++++++++++++++++++++++++++ backends/imgui_impl_emscripten.h | 52 +++ 2 files changed, 595 insertions(+) create mode 100644 backends/imgui_impl_emscripten.cpp create mode 100644 backends/imgui_impl_emscripten.h diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp new file mode 100644 index 000000000..72421becc --- /dev/null +++ b/backends/imgui_impl_emscripten.cpp @@ -0,0 +1,543 @@ +// dear imgui: Platform Backend for Emscripten HTML5 +// +// See documentation in imgui_impl_emscripten.h. +// +// Copyright 2024 Eugene Hopkinson + +#include "imgui.h" +#ifndef IMGUI_DISABLE + +#include "imgui_impl_emscripten.h" +#include +#include +#include + +namespace { + +/// A map of HTML5 key names to imgui keys +static const std::unordered_map key_translate_lookup{ + // main character keys + {"Backquote", ImGuiKey_GraveAccent}, + {"Backslash", ImGuiKey_Backslash}, + {"BracketLeft", ImGuiKey_LeftBracket}, + {"BracketRight", ImGuiKey_RightBracket}, + {"Comma", ImGuiKey_Comma}, + {"Digit0", ImGuiKey_0}, + {"Digit1", ImGuiKey_1}, + {"Digit2", ImGuiKey_2}, + {"Digit3", ImGuiKey_3}, + {"Digit4", ImGuiKey_4}, + {"Digit5", ImGuiKey_5}, + {"Digit6", ImGuiKey_6}, + {"Digit7", ImGuiKey_7}, + {"Digit8", ImGuiKey_8}, + {"Digit9", ImGuiKey_9}, + {"Equal", ImGuiKey_Equal}, + {"IntlBackslash", ImGuiKey_Backslash}, // Mapping to generic backslash + {"IntlRo", ImGuiKey_Slash}, // Closest match for non-standard layouts + {"IntlYen", ImGuiKey_Backslash}, // Closest match for non-standard layouts + {"KeyA", ImGuiKey_A}, + {"KeyB", ImGuiKey_B}, + {"KeyC", ImGuiKey_C}, + {"KeyD", ImGuiKey_D}, + {"KeyE", ImGuiKey_E}, + {"KeyF", ImGuiKey_F}, + {"KeyG", ImGuiKey_G}, + {"KeyH", ImGuiKey_H}, + {"KeyI", ImGuiKey_I}, + {"KeyJ", ImGuiKey_J}, + {"KeyK", ImGuiKey_K}, + {"KeyL", ImGuiKey_L}, + {"KeyM", ImGuiKey_M}, + {"KeyN", ImGuiKey_N}, + {"KeyO", ImGuiKey_O}, + {"KeyP", ImGuiKey_P}, + {"KeyQ", ImGuiKey_Q}, + {"KeyR", ImGuiKey_R}, + {"KeyS", ImGuiKey_S}, + {"KeyT", ImGuiKey_T}, + {"KeyU", ImGuiKey_U}, + {"KeyV", ImGuiKey_V}, + {"KeyW", ImGuiKey_W}, + {"KeyX", ImGuiKey_X}, + {"KeyY", ImGuiKey_Y}, + {"KeyZ", ImGuiKey_Z}, + {"Minus", ImGuiKey_Minus}, + {"Period", ImGuiKey_Period}, + {"Quote", ImGuiKey_Apostrophe}, + {"Semicolon", ImGuiKey_Semicolon}, + {"Slash", ImGuiKey_Slash}, + + // control keys + {"AltLeft", ImGuiKey_LeftAlt}, + {"AltRight", ImGuiKey_RightAlt}, + {"Backspace", ImGuiKey_Backspace}, + {"CapsLock", ImGuiKey_CapsLock}, + {"ContextMenu", ImGuiKey_Menu}, + {"ControlLeft", ImGuiKey_LeftCtrl}, + {"ControlRight", ImGuiKey_RightCtrl}, + {"Enter", ImGuiKey_Enter}, + {"MetaLeft", ImGuiKey_LeftSuper}, + {"MetaRight", ImGuiKey_RightSuper}, + {"ShiftLeft", ImGuiKey_LeftShift}, + {"ShiftRight", ImGuiKey_RightShift}, + {"Space", ImGuiKey_Space}, + {"Tab", ImGuiKey_Tab}, + + // navigation key group + {"Delete", ImGuiKey_Delete}, + {"End", ImGuiKey_End}, + //{"Help", ImGuiKey_PrintScreen}, // Best approximation + {"Home", ImGuiKey_Home}, + {"Insert", ImGuiKey_Insert}, + {"PageDown", ImGuiKey_PageDown}, + {"PageUp", ImGuiKey_PageUp}, + + // arrow key group + {"ArrowDown", ImGuiKey_DownArrow}, + {"ArrowLeft", ImGuiKey_LeftArrow}, + {"ArrowRight", ImGuiKey_RightArrow}, + {"ArrowUp", ImGuiKey_UpArrow}, + + // number pad group + {"NumLock", ImGuiKey_NumLock}, + {"Numpad0", ImGuiKey_Keypad0}, + {"Numpad1", ImGuiKey_Keypad1}, + {"Numpad2", ImGuiKey_Keypad2}, + {"Numpad3", ImGuiKey_Keypad3}, + {"Numpad4", ImGuiKey_Keypad4}, + {"Numpad5", ImGuiKey_Keypad5}, + {"Numpad6", ImGuiKey_Keypad6}, + {"Numpad7", ImGuiKey_Keypad7}, + {"Numpad8", ImGuiKey_Keypad8}, + {"Numpad9", ImGuiKey_Keypad9}, + {"NumpadAdd", ImGuiKey_KeypadAdd}, + {"NumpadBackspace", ImGuiKey_Backspace}, // No direct mapping; backspace functionality + //{"NumpadClear", ImGuiKey_KeypadClear}, // Custom-defined if needed + //{"NumpadClearEntry", ImGuiKey_KeypadClear}, // Custom-defined if needed + {"NumpadComma", ImGuiKey_KeypadDecimal}, // Closest match + {"NumpadDecimal", ImGuiKey_KeypadDecimal}, + {"NumpadDivide", ImGuiKey_KeypadDivide}, + {"NumpadEnter", ImGuiKey_KeypadEnter}, + {"NumpadEqual", ImGuiKey_KeypadEqual}, + {"NumpadHash", ImGuiKey_Backslash}, // Mapped to # on UK keyboard + //{"NumpadMemoryAdd", ImGuiKey_None}, // No defined mapping + //{"NumpadMemoryClear", ImGuiKey_None}, // No defined mapping + //{"NumpadMemoryRecall", ImGuiKey_None}, // No defined mapping + //{"NumpadMemoryStore", ImGuiKey_None}, // No defined mapping + //{"NumpadMemorySubtract", ImGuiKey_None}, // No defined mapping + {"NumpadMultiply", ImGuiKey_KeypadMultiply}, + {"NumpadParenLeft", ImGuiKey_LeftBracket}, // Closest available + {"NumpadParenRight", ImGuiKey_RightBracket}, // Closest available + {"NumpadStar", ImGuiKey_KeypadMultiply}, // Same as multiply + {"NumpadSubtract", ImGuiKey_KeypadSubtract}, + + // top row key groups + {"Escape", ImGuiKey_Escape}, + {"F1", ImGuiKey_F1}, + {"F2", ImGuiKey_F2}, + {"F3", ImGuiKey_F3}, + {"F4", ImGuiKey_F4}, + {"F5", ImGuiKey_F5}, + {"F6", ImGuiKey_F6}, + {"F6", ImGuiKey_F6}, + {"F7", ImGuiKey_F7}, + {"F8", ImGuiKey_F8}, + {"F9", ImGuiKey_F9}, + {"F10", ImGuiKey_F10}, + {"F11", ImGuiKey_F11}, + {"F12", ImGuiKey_F12}, + //{"Fn", ImGuiKey_None}, // No direct mapping + //{"FnLock", ImGuiKey_None}, // No direct mapping + {"PrintScreen", ImGuiKey_PrintScreen}, + {"ScrollLock", ImGuiKey_ScrollLock}, + {"Pause", ImGuiKey_Pause}, +}; + +ImGuiKey translate_key(char const* emscripten_key) __attribute__((__const__)); +ImGuiKey translate_key(char const* emscripten_key) { + /// Translate an emscripten-provided browser string describing a keycode to an imgui key code + if(auto it{key_translate_lookup.find(emscripten_key)}; it != key_translate_lookup.end()) { + return it->second; + } + return ImGuiKey_None; +} + +constexpr ImGuiMouseButton translate_mousebutton(unsigned short emscripten_button) __attribute__((__const__)); +constexpr ImGuiMouseButton translate_mousebutton(unsigned short emscripten_button) { + /// Translate an emscripten-provided integer describing a mouse button to an imgui mouse button + if(emscripten_button == 1) return ImGuiMouseButton_Middle; // 1 = middle mouse button + if(emscripten_button == 2) return ImGuiMouseButton_Right; // 2 = right mouse button + if(emscripten_button > ImGuiMouseButton_COUNT) return ImGuiMouseButton_Middle; // treat any weird clicks on unexpected buttons (button 6 upwards) as middle mouse + return emscripten_button; // any other button translates 1:1 +} + +} // anonymous namespace + +namespace emscripten_browser_cursor_internal { + +////////////////////////////////// Interface /////////////////////////////////// + +enum class cursor { + // General + cursor_auto, // The UA will determine the cursor to display based on the current context. E.g., equivalent to text when hovering text. + cursor_default, // The platform-dependent default cursor. Typically an arrow. + none, // No cursor is rendered. + + // Links & status + context_menu, // cursor slightly obscuring a menu icon - A context menu is available. + help, // cursor next to a question mark - Help information is available. + pointer, // right hand with an index finger pointing up - The cursor is a pointer that indicates a link. Typically an image of a pointing hand. + progress, // cursor and hour glass - The program is busy in the background, but the user can still interact with the interface (in contrast to wait). + wait, // hour glass - The program is busy, and the user can't interact with the interface (in contrast to progress). Sometimes an image of an hourglass or a watch. + + // Selection + cell, // plus symbol - The table cell or set of cells can be selected. + crosshair, // crosshair - Cross cursor, often used to indicate selection in a bitmap. + text, // vertical i-beam - The text can be selected. Typically the shape of an I-beam. + vertical_text, // horizontal i-beam - The vertical text can be selected. Typically the shape of a sideways I-beam. + + // Drag & drop + alias, // cursor next to a folder icon with a curved arrow pointing up and to the right - An alias or shortcut is to be created. + copy, // cursor next to a smaller folder icon with a plus sign - Something is to be copied. + move, // plus sign made of two thin lines, with small arrows facing out - Something is to be moved. + no_drop, // cursor next to circle with a line through it - An item may not be dropped at the current location. + not_allowed, // Circle with a line through it - The requested action will not be carried out. + grab, // fully opened hand - Something can be grabbed (dragged to be moved). + grabbing, // closed hand - Something is being grabbed (dragged to be moved). + + // Resizing & scrolling + all_scroll, // dot with four triangles around it - Something can be scrolled in any direction (panned). + col_resize, // The item/column can be resized horizontally. Often rendered as arrows pointing left and right with a vertical bar separating them. + row_resize, // The item/row can be resized vertically. Often rendered as arrows pointing up and down with a horizontal bar separating them. + n_resize, // arrow pointing up - Some edge is to be moved. For example, the se-resize cursor is used when the movement starts from the south-east corner of the box. + e_resize, // arrow pointing right + s_resize, // arrow pointing down + w_resize, // arrow pointing left + ne_resize, // arrow pointing top-right + nw_resize, // arrow pointing top-left + se_resize, // arrow pointing bottom-right + sw_resize, // arrow pointing bottom-left + ew_resize, // arrow pointing left and right - Bidirectional resize cursor. + ns_resize, // arrow pointing up and down + nesw_resize, // arrow pointing both to the top-right and bottom-left + nwse_resize, // arrow pointing both to the top-left and bottom-right + + // Zooming + zoom_in, // magnifying glass with a plus sign - Something can be zoomed (magnified) in or out. + zoom_out, + + // Special invalid value + invalid = std::numeric_limits::max() +}; + +void set(cursor new_cursor); // set a new cursor from a cursor enum +void unset(); // clear the current cursor setting + +//////////////////////////////// Implementation //////////////////////////////// + +bool is_set() { + /// Returns whether the cursor is currently set + return EM_ASM_INT( + return !(!document.body.style.cursor || document.body.style.cursor.length === 0 ); + ); +} + +std::string get_string() { + /// Return the current cursor setting as a string + auto cursor_str_ptr{reinterpret_cast(EM_ASM_PTR( + return stringToNewUTF8(document.body.style.cursor); + ))}; + std::string const cursor_str{cursor_str_ptr}; + free(cursor_str_ptr); + return cursor_str; +} + +void set(cursor new_cursor) { + /// Set the cursor according to the given enum + // Note, implementations omitted for cursors not used by imgui. For full implementation, use https://github.com/Armchair-Software/emscripten-browser-cursor + switch(new_cursor) { + case cursor::cursor_default: + default: + EM_ASM(document.body.style.cursor = 'default';); + break; + case cursor::pointer: + EM_ASM(document.body.style.cursor = 'pointer';); + break; + case cursor::text: + EM_ASM(document.body.style.cursor = 'text';); + break; + case cursor::move: + EM_ASM(document.body.style.cursor = 'move';); + break; + case cursor::not_allowed: + EM_ASM(document.body.style.cursor = 'not-allowed';); + break; + case cursor::ew_resize: + EM_ASM(document.body.style.cursor = 'ew-resize';); + break; + case cursor::ns_resize: + EM_ASM(document.body.style.cursor = 'ns-resize';); + break; + case cursor::nesw_resize: + EM_ASM(document.body.style.cursor = 'nesw-resize';); + break; + case cursor::nwse_resize: + EM_ASM(document.body.style.cursor = 'nwse-resize';); + break; + } +} + +void set(std::string const &new_cursor) { + /// Set the cursor from an arbitrary string + EM_ASM({ + document.body.style.cursor = UTF8ToString($0); + }, new_cursor.c_str()); +} + +} // namespace emscripten_browser_cursor + +namespace { + +void update_cursor() { + /// Sync any cursor changes due to ImGUI to the browser's cursor + static emscripten_browser_cursor_internal::cursor current_cursor{emscripten_browser_cursor_internal::cursor::invalid}; + static std::optional cursor_to_restore; + + auto set_cursor_if_necessary{[&](emscripten_browser_cursor_internal::cursor new_cursor){ + if(new_cursor == current_cursor) return; // don't do anything if the current cursor is already set + current_cursor = new_cursor; + emscripten_browser_cursor_internal::set(new_cursor); + }}; + + if(ImGui::GetIO().WantCaptureMouse) { // mouse is hovering over the gui + if(!cursor_to_restore && emscripten_browser_cursor_internal::is_set()) { + cursor_to_restore = emscripten_browser_cursor_internal::get_string(); // back up the existing cursor when entering the imgui capture space + } + + switch(ImGui::GetMouseCursor()) { + case ImGuiMouseCursor_Arrow: + set_cursor_if_necessary(emscripten_browser_cursor_internal::cursor::cursor_default); + break; + case ImGuiMouseCursor_TextInput: // When hovering over InputText, etc. + set_cursor_if_necessary(emscripten_browser_cursor_internal::cursor::text); + break; + case ImGuiMouseCursor_ResizeAll: // (Unused by Dear ImGui functions) + set_cursor_if_necessary(emscripten_browser_cursor_internal::cursor::move); + break; + case ImGuiMouseCursor_ResizeNS: // When hovering over a horizontal border + set_cursor_if_necessary(emscripten_browser_cursor_internal::cursor::ns_resize); + break; + case ImGuiMouseCursor_ResizeEW: // When hovering over a vertical border or a column + set_cursor_if_necessary(emscripten_browser_cursor_internal::cursor::ew_resize); + break; + case ImGuiMouseCursor_ResizeNESW: // When hovering over the bottom-left corner of a window + set_cursor_if_necessary(emscripten_browser_cursor_internal::cursor::nesw_resize); + break; + case ImGuiMouseCursor_ResizeNWSE: // When hovering over the bottom-right corner of a window + set_cursor_if_necessary(emscripten_browser_cursor_internal::cursor::nwse_resize); + break; + case ImGuiMouseCursor_Hand: // (Unused by Dear ImGui functions. Use for e.g. hyperlinks) + set_cursor_if_necessary(emscripten_browser_cursor_internal::cursor::pointer); + break; + case ImGuiMouseCursor_NotAllowed: // When hovering something with disallowed interaction. Usually a crossed circle. + set_cursor_if_necessary(emscripten_browser_cursor_internal::cursor::not_allowed); + break; + } + } else { // mouse is away from the gui, hovering over some other part of the viewport + if(cursor_to_restore) { + emscripten_browser_cursor_internal::set(*cursor_to_restore); // restore the previous cursor when leaving the imgui capture space + cursor_to_restore = std::nullopt; + current_cursor = emscripten_browser_cursor_internal::cursor::invalid; // select an unused value for current cursor to force a set next time set_cursor_if_necessary is called + } + } +} + +} // anonymous namespace + +void ImGui_ImplEmscripten_Init() { + /// Initialise the Emscripten backend, setting input callbacks + auto &imgui_io{ImGui::GetIO()}; + imgui_io.BackendPlatformName = "imgui_impl_emscripten"; + imgui_io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; + + // set up initial display size values + imgui_io.DisplaySize.x = emscripten::val::global("window")["innerWidth"].as(); + imgui_io.DisplaySize.y = emscripten::val::global("window")["innerHeight"].as(); + { + auto const device_pixel_ratio{emscripten::val::global("window")["devicePixelRatio"].as()}; + imgui_io.DisplayFramebufferScale.x = device_pixel_ratio; + imgui_io.DisplayFramebufferScale.y = device_pixel_ratio; + } + + emscripten_set_mousemove_callback( + EMSCRIPTEN_EVENT_TARGET_WINDOW, // target + nullptr, // userData + false, // useCapture + [](int /*event_type*/, EmscriptenMouseEvent const *mouse_event, void */*data*/){ // callback, event_type == EMSCRIPTEN_EVENT_MOUSEMOVE + ImGui::GetIO().AddMousePosEvent( + static_cast(mouse_event->clientX), + static_cast(mouse_event->clientY) + ); + return true; // the event was consumed + } + ); + emscripten_set_mousedown_callback( + EMSCRIPTEN_EVENT_TARGET_WINDOW, // target + nullptr, // userData + false, // useCapture + [](int /*event_type*/, EmscriptenMouseEvent const *mouse_event, void */*data*/){ // callback, event_type == EMSCRIPTEN_EVENT_MOUSEDOWN + ImGui::GetIO().AddMouseButtonEvent(translate_mousebutton(mouse_event->button), true); // translated button, down + return true; // the event was consumed + } + ); + emscripten_set_mouseup_callback( + EMSCRIPTEN_EVENT_TARGET_WINDOW, // target + nullptr, // userData + false, // useCapture + [](int /*event_type*/, EmscriptenMouseEvent const *mouse_event, void */*data*/){ // callback, event_type == EMSCRIPTEN_EVENT_MOUSEUP + ImGui::GetIO().AddMouseButtonEvent(translate_mousebutton(mouse_event->button), false); // translated button, up + return true; // the event was consumed + } + ); + emscripten_set_mouseenter_callback( + EMSCRIPTEN_EVENT_TARGET_DOCUMENT, // target - WINDOW doesn't produce mouseenter events + nullptr, // userData + false, // useCapture + [](int /*event_type*/, EmscriptenMouseEvent const *mouse_event, void */*data*/){ // callback, event_type == EMSCRIPTEN_EVENT_MOUSEENTER + ImGui::GetIO().AddMousePosEvent( + static_cast(mouse_event->clientX), + static_cast(mouse_event->clientY) + ); + return true; // the event was consumed + } + ); + emscripten_set_mouseleave_callback( + EMSCRIPTEN_EVENT_TARGET_DOCUMENT, // target - WINDOW doesn't produce mouseenter events + nullptr, // userData + false, // useCapture + [](int /*event_type*/, EmscriptenMouseEvent const */*mouse_event*/, void */*data*/){ // callback, event_type == EMSCRIPTEN_EVENT_MOUSELEAVE + auto &imgui_io{ImGui::GetIO()}; + imgui_io.AddMousePosEvent(-FLT_MAX, -FLT_MAX); // cursor is not in the window + imgui_io.ClearInputKeys(); // clear pending input keys on mouse exit + return true; // the event was consumed + } + ); + emscripten_set_wheel_callback( + EMSCRIPTEN_EVENT_TARGET_WINDOW, // target + nullptr, // userData + false, // useCapture + [](int /*event_type*/, EmscriptenWheelEvent const *wheel_event, void */*data*/){ // callback, event_type == EMSCRIPTEN_EVENT_WHEEL + float scale{1.0f}; + switch(wheel_event->deltaMode) { + case DOM_DELTA_PIXEL: // scrolling in pixels + scale = 1.0f / 100.0f; + break; + case DOM_DELTA_LINE: // scrolling by lines + scale = 1.0f / 3.0f; + break; + case DOM_DELTA_PAGE: // scrolling by pages + scale = 80.0f; + break; + } + // TODO: make scrolling speeds configurable + ImGui::GetIO().AddMouseWheelEvent( + -static_cast(wheel_event->deltaX) * scale, + -static_cast(wheel_event->deltaY) * scale + ); + return false; // the event was not consumed + } + ); + emscripten_set_keydown_callback( + EMSCRIPTEN_EVENT_TARGET_WINDOW, // target + nullptr, // userData + false, // useCapture + [](int /*event_type*/, EmscriptenKeyboardEvent const *key_event, void */*data*/){ // callback, event_type == EMSCRIPTEN_EVENT_KEYDOWN + ImGui::GetIO().AddKeyEvent(translate_key(key_event->code), true); + return false; // the event was not consumed + } + ); + emscripten_set_keyup_callback( + EMSCRIPTEN_EVENT_TARGET_WINDOW, // target + nullptr, // userData + false, // useCapture + [](int /*event_type*/, EmscriptenKeyboardEvent const *key_event, void */*data*/){ // callback, event_type == EMSCRIPTEN_EVENT_KEYUP + ImGui::GetIO().AddKeyEvent(translate_key(key_event->code), false); + return false; // the event was not consumed + } + ); + emscripten_set_keypress_callback( + EMSCRIPTEN_EVENT_TARGET_WINDOW, // target + nullptr, // userData + false, // useCapture + [](int /*event_type*/, EmscriptenKeyboardEvent const *key_event, void */*data*/){ // callback, event_type == EMSCRIPTEN_EVENT_KEYPRESS + auto &imgui_io{ImGui::GetIO()}; + ImGui::GetIO().AddInputCharactersUTF8(key_event->key); + return true; // the event was consumed + } + ); + emscripten_set_resize_callback( + EMSCRIPTEN_EVENT_TARGET_WINDOW, // target + nullptr, // userData + false, // useCapture + [](int /*event_type*/, EmscriptenUiEvent const *event, void */*data*/) { // event_type == EMSCRIPTEN_EVENT_RESIZE + auto &imgui_io{ImGui::GetIO()}; + imgui_io.DisplaySize.x = static_cast(event->windowInnerWidth); + imgui_io.DisplaySize.y = static_cast(event->windowInnerHeight); + return true; // the event was consumed + } + ); + emscripten_set_focusin_callback( + EMSCRIPTEN_EVENT_TARGET_WINDOW, // target + nullptr, // userData + false, // useCapture + [](int /*event_type*/, EmscriptenFocusEvent const */*event*/, void */*data*/) { // event_type == EMSCRIPTEN_EVENT_FOCUSIN + auto &imgui_io{ImGui::GetIO()}; + imgui_io.AddFocusEvent(true); + imgui_io.ClearInputKeys(); // clear pending input keys on focus gain + return true; // the event was consumed + } + ); + emscripten_set_focusout_callback( + EMSCRIPTEN_EVENT_TARGET_WINDOW, // target + nullptr, // userData + false, // useCapture + [](int /*event_type*/, EmscriptenFocusEvent const */*event*/, void */*data*/) { // event_type == EMSCRIPTEN_EVENT_FOCUSOUT + auto &imgui_io{ImGui::GetIO()}; + imgui_io.AddFocusEvent(false); + imgui_io.ClearInputKeys(); // clear pending input keys on focus loss - for example if you press tab to cycle to another part of the UI + return true; // the event was consumed + } + ); + + // TODO: touch events +} + +void ImGui_ImplEmscripten_Shutdown() { + /// Unset any callbacks set by Init + emscripten_set_mousemove_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr); + emscripten_set_mousedown_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr); + emscripten_set_mouseup_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr); + emscripten_set_mouseenter_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, nullptr, false, nullptr); + emscripten_set_mouseleave_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, nullptr, false, nullptr); + emscripten_set_wheel_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr); + emscripten_set_keydown_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr); + emscripten_set_keyup_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr); + emscripten_set_keypress_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr); + emscripten_set_resize_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr); + emscripten_set_focusin_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr); + emscripten_set_focusout_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr); + // TODO: touch events + + auto &imgui_io{ImGui::GetIO()}; + imgui_io.BackendPlatformName = nullptr; + imgui_io.BackendPlatformUserData = nullptr; + imgui_io.BackendFlags &= ~ImGuiBackendFlags_HasMouseCursors; +} + +void ImGui_ImplEmscripten_NewFrame() { + /// Update any state that needs to be polled + update_cursor(); +} + +#endif // IMGUI_DISABLE diff --git a/backends/imgui_impl_emscripten.h b/backends/imgui_impl_emscripten.h new file mode 100644 index 000000000..f961f0590 --- /dev/null +++ b/backends/imgui_impl_emscripten.h @@ -0,0 +1,52 @@ +// dear imgui: Platform Backend for Emscripten HTML5 +// +// This is a platform back-end, similar to and offering an alternative to imgui_impl_glfw. +// The intended use-case is for applications built with Emscripten, running in the browser, but *not* using GLFW. +// It uses Emscripten's HTML5 interface to tie callbacks to imgui input, handling window resizing, +// focus, cursor, keyboard input, touch, gamepad devices etc. It does not attempt to handle rendering. +// +// A note about GLFW on Emscripten: Emscripten includes its own GLFW implementation, which wraps browser HTML5 callbacks to provide the standard GLFW input interface. So there are two levels of indirection. +// This backend removes the middleman for input, providing a more efficient direct interface between Emscripten's functionality and imgui input. +// +// This is a useful accompaniment for WebGPU rendering (i.e. with imgui_impl_wgpu), where GLFW is not needed for rendering. +// In that case, this backend replaces all non-rendering-related functionality from GLFW, making it possible to avoid depending on GLFW altogether. +// +// For native cursor rendering, this includes a cut-down implementation of the Emscripten Browser Cursor library: https://github.com/Armchair-Software/emscripten-browser-cursor +// +// Copyright 2024 Eugene Hopkinson + +// Supported features: +// - Keyboard input +// - Window resizing +// - Cursor position +// - Cursor enters and leaves the window +// - Application focus +// - Browser cursors + +// TODO: +// - Touch events + +// A note on gamepad input: This back-end does not attempt to handle gamepad events, for the simple +// reason that any time you intend to provide gamepad input to imgui, you will inevitably want to +// also use gamepad input in your own game logic, so duplicating this processing can add a lot of +// inefficiency. For an example of how to handle Emscripten HTML5 gamepad events efficiently, and +// pass the relevant events to imgui, see https://github.com/Armchair-Software/webgpu-demo2 +// Don't forget to set io.BackendFlags |= ImGuiBackendFlags_HasGamepad when a gamepad is connected. + +#pragma once + +#ifndef __EMSCRIPTEN__ + #error The imgui_impl_emscripten backend reqiuires Emscripten. +#endif + +/// Initialise the Emscripten backend, setting input callbacks. This should be called after ImGui::CreateContext(); +void ImGui_ImplEmscripten_Init(); + +/// Shut down the Emscripten backend. This unsets all Emscripten input callbacks set by Init. +/// Note it'll also unset any Emscripten input callbacks set elsewhere in the program! +/// Note also there is no obligation to ever call this, as there is not necessarily any such concept as "shutting down" when running in the browser, and we have no resources to release. The user can just close the tab. +void ImGui_ImplEmscripten_Shutdown(); + +/// Call every frame to update polled input events, i.e. gamepads, and update imgui's cursors. +/// If you aren't using gamepad input to control imgui, and you're not using browser native cursor rendering (i.e. if imgui is rendering cursors internally), you don't need to call this. +void ImGui_ImplEmscripten_NewFrame(); From 60984d1a345a2b50136489dcea79e0bf5649f439 Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Fri, 6 Dec 2024 17:22:28 +0000 Subject: [PATCH 02/72] don't use devicePixelRatio as it's not needed with WebGPU --- backends/imgui_impl_emscripten.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index 72421becc..01a54c544 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -364,11 +364,6 @@ void ImGui_ImplEmscripten_Init() { // set up initial display size values imgui_io.DisplaySize.x = emscripten::val::global("window")["innerWidth"].as(); imgui_io.DisplaySize.y = emscripten::val::global("window")["innerHeight"].as(); - { - auto const device_pixel_ratio{emscripten::val::global("window")["devicePixelRatio"].as()}; - imgui_io.DisplayFramebufferScale.x = device_pixel_ratio; - imgui_io.DisplayFramebufferScale.y = device_pixel_ratio; - } emscripten_set_mousemove_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, // target From 9fd9bf9f507fb17b9b038a925eb3941fedbf4359 Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Fri, 6 Dec 2024 17:22:46 +0000 Subject: [PATCH 03/72] handle blur and focus events correctly, focusin and focusout aren't enough --- backends/imgui_impl_emscripten.cpp | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index 01a54c544..127027a26 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -482,6 +482,28 @@ void ImGui_ImplEmscripten_Init() { return true; // the event was consumed } ); + emscripten_set_blur_callback( + EMSCRIPTEN_EVENT_TARGET_WINDOW, // target + nullptr, // userData + false, // useCapture + [](int /*event_type*/, EmscriptenFocusEvent const */*event*/, void */*data*/) { // event_type == EMSCRIPTEN_EVENT_BLUR + auto &imgui_io{ImGui::GetIO()}; + imgui_io.AddFocusEvent(false); + imgui_io.ClearInputKeys(); // clear pending input keys on focus gain + return true; // the event was consumed + } + ); + emscripten_set_focus_callback( + EMSCRIPTEN_EVENT_TARGET_WINDOW, // target + nullptr, // userData + false, // useCapture + [](int /*event_type*/, EmscriptenFocusEvent const */*event*/, void */*data*/) { // event_type == EMSCRIPTEN_EVENT_FOCUS + auto &imgui_io{ImGui::GetIO()}; + imgui_io.AddFocusEvent(true); + imgui_io.ClearInputKeys(); // clear pending input keys on focus loss - for example if you press tab to cycle to another part of the UI + return true; // the event was consumed + } + ); emscripten_set_focusin_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, // target nullptr, // userData From a1decdc274c1f29ae3385d4ce8c2164c8d3817b4 Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Fri, 6 Dec 2024 17:23:22 +0000 Subject: [PATCH 04/72] special handling for event capture for tab and enter --- backends/imgui_impl_emscripten.cpp | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index 127027a26..e1d199e6d 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -448,8 +448,18 @@ void ImGui_ImplEmscripten_Init() { nullptr, // userData false, // useCapture [](int /*event_type*/, EmscriptenKeyboardEvent const *key_event, void */*data*/){ // callback, event_type == EMSCRIPTEN_EVENT_KEYDOWN - ImGui::GetIO().AddKeyEvent(translate_key(key_event->code), true); - return false; // the event was not consumed + auto const key{translate_key(key_event->code)}; + auto &imgui_io{ImGui::GetIO()}; + imgui_io.AddKeyEvent(key, true); + + switch(key) { // special cases for certain key events that need to be consumed to prevent unwanted behaviour + case ImGuiKey_Tab: // consuming tab prevents the user tabbing to other parts of the browser interface outside the window content + return imgui_io.WantCaptureKeyboard; // the event was consumed only if imgui wants to capture the keyboard + case ImGuiKey_Enter: // consuming enter prevents the word "Enter" appearing in text input via the keypress callback + return imgui_io.WantTextInput; // the event was consumed only if we're currently accepting text input + default: + return false; // the event was not consumed + } } ); emscripten_set_keyup_callback( @@ -467,15 +477,15 @@ void ImGui_ImplEmscripten_Init() { false, // useCapture [](int /*event_type*/, EmscriptenKeyboardEvent const *key_event, void */*data*/){ // callback, event_type == EMSCRIPTEN_EVENT_KEYPRESS auto &imgui_io{ImGui::GetIO()}; - ImGui::GetIO().AddInputCharactersUTF8(key_event->key); - return true; // the event was consumed + imgui_io.AddInputCharactersUTF8(key_event->key); + return imgui_io.WantCaptureKeyboard; // the event was consumed only if imgui wants to capture the keyboard } ); emscripten_set_resize_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, // target nullptr, // userData false, // useCapture - [](int /*event_type*/, EmscriptenUiEvent const *event, void */*data*/) { // event_type == EMSCRIPTEN_EVENT_RESIZE + [](int /*event_type*/, EmscriptenUiEvent const *event, void */*data*/) { // event_type == EMSCRIPTEN_EVENT_RESIZE auto &imgui_io{ImGui::GetIO()}; imgui_io.DisplaySize.x = static_cast(event->windowInnerWidth); imgui_io.DisplaySize.y = static_cast(event->windowInnerHeight); From bee91678d61f2ca905e5fd70ae466a986dcf1b19 Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Sun, 8 Dec 2024 15:57:41 +0000 Subject: [PATCH 05/72] prevent "Delete" getting printed in text input --- backends/imgui_impl_emscripten.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index e1d199e6d..05b6de3d8 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -456,6 +456,7 @@ void ImGui_ImplEmscripten_Init() { case ImGuiKey_Tab: // consuming tab prevents the user tabbing to other parts of the browser interface outside the window content return imgui_io.WantCaptureKeyboard; // the event was consumed only if imgui wants to capture the keyboard case ImGuiKey_Enter: // consuming enter prevents the word "Enter" appearing in text input via the keypress callback + case ImGuiKey_Delete: // consuming enter prevents the word "Delete" appearing in text input via the keypress callback return imgui_io.WantTextInput; // the event was consumed only if we're currently accepting text input default: return false; // the event was not consumed From 8ecea4d569a5a27792c0ad07de561a202f9598a1 Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Mon, 9 Dec 2024 17:40:49 +0000 Subject: [PATCH 06/72] add special handling for modifier keys to also generate modifier key events --- backends/imgui_impl_emscripten.cpp | 47 +++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index 05b6de3d8..4aad0717b 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -451,16 +451,33 @@ void ImGui_ImplEmscripten_Init() { auto const key{translate_key(key_event->code)}; auto &imgui_io{ImGui::GetIO()}; imgui_io.AddKeyEvent(key, true); - - switch(key) { // special cases for certain key events that need to be consumed to prevent unwanted behaviour + switch(key) { // special cases for certain key events + case ImGuiKey_LeftCtrl: // additional events for modifier keys + case ImGuiKey_RightCtrl: + imgui_io.AddKeyEvent(ImGuiMod_Ctrl, true); + break; + case ImGuiKey_LeftShift: + case ImGuiKey_RightShift: + imgui_io.AddKeyEvent(ImGuiMod_Shift, true); + break; + case ImGuiKey_LeftAlt: + case ImGuiKey_RightAlt: + imgui_io.AddKeyEvent(ImGuiMod_Alt, true); + break; + case ImGuiKey_LeftSuper: + case ImGuiKey_RightSuper: + imgui_io.AddKeyEvent(ImGuiMod_Super, true); + break; + // TODO: case ImGuiKey_Menu: do we want to do anything with this? case ImGuiKey_Tab: // consuming tab prevents the user tabbing to other parts of the browser interface outside the window content return imgui_io.WantCaptureKeyboard; // the event was consumed only if imgui wants to capture the keyboard case ImGuiKey_Enter: // consuming enter prevents the word "Enter" appearing in text input via the keypress callback case ImGuiKey_Delete: // consuming enter prevents the word "Delete" appearing in text input via the keypress callback return imgui_io.WantTextInput; // the event was consumed only if we're currently accepting text input default: - return false; // the event was not consumed + break; } + return false; // if no special handling, the event was not consumed } ); emscripten_set_keyup_callback( @@ -468,7 +485,29 @@ void ImGui_ImplEmscripten_Init() { nullptr, // userData false, // useCapture [](int /*event_type*/, EmscriptenKeyboardEvent const *key_event, void */*data*/){ // callback, event_type == EMSCRIPTEN_EVENT_KEYUP - ImGui::GetIO().AddKeyEvent(translate_key(key_event->code), false); + auto const key{translate_key(key_event->code)}; + auto &imgui_io{ImGui::GetIO()}; + imgui_io.AddKeyEvent(key, false); + switch(key) { // special cases for certain key events + case ImGuiKey_LeftCtrl: // additional events for modifier keys + case ImGuiKey_RightCtrl: + imgui_io.AddKeyEvent(ImGuiMod_Ctrl, false); + break; + case ImGuiKey_LeftShift: + case ImGuiKey_RightShift: + imgui_io.AddKeyEvent(ImGuiMod_Shift, false); + break; + case ImGuiKey_LeftAlt: + case ImGuiKey_RightAlt: + imgui_io.AddKeyEvent(ImGuiMod_Alt, false); + break; + case ImGuiKey_LeftSuper: + case ImGuiKey_RightSuper: + imgui_io.AddKeyEvent(ImGuiMod_Super, false); + break; + default: + break; + } return false; // the event was not consumed } ); From 5ab277a62d6fcd6a9ded38b07f526d2e6014003a Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Tue, 31 Mar 2026 14:55:41 +0100 Subject: [PATCH 07/72] adjust comment style to match imgui standard --- backends/imgui_impl_emscripten.cpp | 26 +++++++++++++------------- backends/imgui_impl_emscripten.h | 12 ++++++------ 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index 4aad0717b..228997f13 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -14,7 +14,7 @@ namespace { -/// A map of HTML5 key names to imgui keys +// A map of HTML5 key names to imgui keys static const std::unordered_map key_translate_lookup{ // main character keys {"Backquote", ImGuiKey_GraveAccent}, @@ -156,7 +156,7 @@ static const std::unordered_map key_translate_lookup{ ImGuiKey translate_key(char const* emscripten_key) __attribute__((__const__)); ImGuiKey translate_key(char const* emscripten_key) { - /// Translate an emscripten-provided browser string describing a keycode to an imgui key code + // Translate an emscripten-provided browser string describing a keycode to an imgui key code if(auto it{key_translate_lookup.find(emscripten_key)}; it != key_translate_lookup.end()) { return it->second; } @@ -165,7 +165,7 @@ ImGuiKey translate_key(char const* emscripten_key) { constexpr ImGuiMouseButton translate_mousebutton(unsigned short emscripten_button) __attribute__((__const__)); constexpr ImGuiMouseButton translate_mousebutton(unsigned short emscripten_button) { - /// Translate an emscripten-provided integer describing a mouse button to an imgui mouse button + // Translate an emscripten-provided integer describing a mouse button to an imgui mouse button if(emscripten_button == 1) return ImGuiMouseButton_Middle; // 1 = middle mouse button if(emscripten_button == 2) return ImGuiMouseButton_Right; // 2 = right mouse button if(emscripten_button > ImGuiMouseButton_COUNT) return ImGuiMouseButton_Middle; // treat any weird clicks on unexpected buttons (button 6 upwards) as middle mouse @@ -176,7 +176,7 @@ constexpr ImGuiMouseButton translate_mousebutton(unsigned short emscripten_butto namespace emscripten_browser_cursor_internal { -////////////////////////////////// Interface /////////////////////////////////// +// Browser cursor helpers enum class cursor { // General @@ -234,17 +234,17 @@ enum class cursor { void set(cursor new_cursor); // set a new cursor from a cursor enum void unset(); // clear the current cursor setting -//////////////////////////////// Implementation //////////////////////////////// +// Browser cursor helper implementation bool is_set() { - /// Returns whether the cursor is currently set + // Returns whether the cursor is currently set return EM_ASM_INT( return !(!document.body.style.cursor || document.body.style.cursor.length === 0 ); ); } std::string get_string() { - /// Return the current cursor setting as a string + // Return the current cursor setting as a string auto cursor_str_ptr{reinterpret_cast(EM_ASM_PTR( return stringToNewUTF8(document.body.style.cursor); ))}; @@ -254,7 +254,7 @@ std::string get_string() { } void set(cursor new_cursor) { - /// Set the cursor according to the given enum + // Set the cursor according to the given enum // Note, implementations omitted for cursors not used by imgui. For full implementation, use https://github.com/Armchair-Software/emscripten-browser-cursor switch(new_cursor) { case cursor::cursor_default: @@ -289,7 +289,7 @@ void set(cursor new_cursor) { } void set(std::string const &new_cursor) { - /// Set the cursor from an arbitrary string + // Set the cursor from an arbitrary string EM_ASM({ document.body.style.cursor = UTF8ToString($0); }, new_cursor.c_str()); @@ -300,7 +300,7 @@ void set(std::string const &new_cursor) { namespace { void update_cursor() { - /// Sync any cursor changes due to ImGUI to the browser's cursor + // Sync any cursor changes due to ImGui to the browser's cursor static emscripten_browser_cursor_internal::cursor current_cursor{emscripten_browser_cursor_internal::cursor::invalid}; static std::optional cursor_to_restore; @@ -356,7 +356,7 @@ void update_cursor() { } // anonymous namespace void ImGui_ImplEmscripten_Init() { - /// Initialise the Emscripten backend, setting input callbacks + // Initialise the Emscripten backend, setting input callbacks auto &imgui_io{ImGui::GetIO()}; imgui_io.BackendPlatformName = "imgui_impl_emscripten"; imgui_io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; @@ -581,7 +581,7 @@ void ImGui_ImplEmscripten_Init() { } void ImGui_ImplEmscripten_Shutdown() { - /// Unset any callbacks set by Init + // Unset any callbacks set by Init emscripten_set_mousemove_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr); emscripten_set_mousedown_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr); emscripten_set_mouseup_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr); @@ -603,7 +603,7 @@ void ImGui_ImplEmscripten_Shutdown() { } void ImGui_ImplEmscripten_NewFrame() { - /// Update any state that needs to be polled + // Update any state that needs to be polled update_cursor(); } diff --git a/backends/imgui_impl_emscripten.h b/backends/imgui_impl_emscripten.h index f961f0590..fb15af075 100644 --- a/backends/imgui_impl_emscripten.h +++ b/backends/imgui_impl_emscripten.h @@ -39,14 +39,14 @@ #error The imgui_impl_emscripten backend reqiuires Emscripten. #endif -/// Initialise the Emscripten backend, setting input callbacks. This should be called after ImGui::CreateContext(); +// Initialise the Emscripten backend, setting input callbacks. This should be called after ImGui::CreateContext(); void ImGui_ImplEmscripten_Init(); -/// Shut down the Emscripten backend. This unsets all Emscripten input callbacks set by Init. -/// Note it'll also unset any Emscripten input callbacks set elsewhere in the program! -/// Note also there is no obligation to ever call this, as there is not necessarily any such concept as "shutting down" when running in the browser, and we have no resources to release. The user can just close the tab. +// Shut down the Emscripten backend. This unsets all Emscripten input callbacks set by Init. +// Note it'll also unset any Emscripten input callbacks set elsewhere in the program! +// Note also there is no obligation to ever call this, as there is not necessarily any such concept as "shutting down" when running in the browser, and we have no resources to release. The user can just close the tab. void ImGui_ImplEmscripten_Shutdown(); -/// Call every frame to update polled input events, i.e. gamepads, and update imgui's cursors. -/// If you aren't using gamepad input to control imgui, and you're not using browser native cursor rendering (i.e. if imgui is rendering cursors internally), you don't need to call this. +// Call every frame to update polled input events, i.e. gamepads, and update imgui's cursors. +// If you aren't using gamepad input to control imgui, and you're not using browser native cursor rendering (i.e. if imgui is rendering cursors internally), you don't need to call this. void ImGui_ImplEmscripten_NewFrame(); From 2ce0a48c506b8bd057b519858c204412ccfe3ea3 Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Tue, 31 Mar 2026 14:55:56 +0100 Subject: [PATCH 08/72] add changelog --- backends/imgui_impl_emscripten.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index 228997f13..8b7dee021 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -3,6 +3,15 @@ // See documentation in imgui_impl_emscripten.h. // // Copyright 2024 Eugene Hopkinson +// +// CHANGELOG +// (minor and older changes stripped away, please see git history for details) +// 2024-12-09: Inputs: Added special handling for modifier keys to also generate modifier key events. +// 2024-12-08: Inputs: Prevent "Delete" key from getting printed in text input. +// 2024-12-06: Inputs: Added special handling for Tab and Enter event capture. +// 2024-12-06: Inputs: Handle blur and focus events correctly, focusin and focusout aren't enough. +// 2024-12-06: Emscripten: Don't use devicePixelRatio as it's not needed with WebGPU. +// 2024-11-22: Initial version. #include "imgui.h" #ifndef IMGUI_DISABLE From 739e9bbee152676220f2ead3a0482f2887c1e271 Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Tue, 31 Mar 2026 15:04:02 +0100 Subject: [PATCH 09/72] adjust indentation to match imgui standard --- backends/imgui_impl_emscripten.cpp | 1036 ++++++++++++++-------------- backends/imgui_impl_emscripten.h | 2 +- 2 files changed, 519 insertions(+), 519 deletions(-) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index 8b7dee021..46414efe9 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -25,160 +25,160 @@ namespace { // A map of HTML5 key names to imgui keys static const std::unordered_map key_translate_lookup{ - // main character keys - {"Backquote", ImGuiKey_GraveAccent}, - {"Backslash", ImGuiKey_Backslash}, - {"BracketLeft", ImGuiKey_LeftBracket}, - {"BracketRight", ImGuiKey_RightBracket}, - {"Comma", ImGuiKey_Comma}, - {"Digit0", ImGuiKey_0}, - {"Digit1", ImGuiKey_1}, - {"Digit2", ImGuiKey_2}, - {"Digit3", ImGuiKey_3}, - {"Digit4", ImGuiKey_4}, - {"Digit5", ImGuiKey_5}, - {"Digit6", ImGuiKey_6}, - {"Digit7", ImGuiKey_7}, - {"Digit8", ImGuiKey_8}, - {"Digit9", ImGuiKey_9}, - {"Equal", ImGuiKey_Equal}, - {"IntlBackslash", ImGuiKey_Backslash}, // Mapping to generic backslash - {"IntlRo", ImGuiKey_Slash}, // Closest match for non-standard layouts - {"IntlYen", ImGuiKey_Backslash}, // Closest match for non-standard layouts - {"KeyA", ImGuiKey_A}, - {"KeyB", ImGuiKey_B}, - {"KeyC", ImGuiKey_C}, - {"KeyD", ImGuiKey_D}, - {"KeyE", ImGuiKey_E}, - {"KeyF", ImGuiKey_F}, - {"KeyG", ImGuiKey_G}, - {"KeyH", ImGuiKey_H}, - {"KeyI", ImGuiKey_I}, - {"KeyJ", ImGuiKey_J}, - {"KeyK", ImGuiKey_K}, - {"KeyL", ImGuiKey_L}, - {"KeyM", ImGuiKey_M}, - {"KeyN", ImGuiKey_N}, - {"KeyO", ImGuiKey_O}, - {"KeyP", ImGuiKey_P}, - {"KeyQ", ImGuiKey_Q}, - {"KeyR", ImGuiKey_R}, - {"KeyS", ImGuiKey_S}, - {"KeyT", ImGuiKey_T}, - {"KeyU", ImGuiKey_U}, - {"KeyV", ImGuiKey_V}, - {"KeyW", ImGuiKey_W}, - {"KeyX", ImGuiKey_X}, - {"KeyY", ImGuiKey_Y}, - {"KeyZ", ImGuiKey_Z}, - {"Minus", ImGuiKey_Minus}, - {"Period", ImGuiKey_Period}, - {"Quote", ImGuiKey_Apostrophe}, - {"Semicolon", ImGuiKey_Semicolon}, - {"Slash", ImGuiKey_Slash}, + // main character keys + {"Backquote", ImGuiKey_GraveAccent}, + {"Backslash", ImGuiKey_Backslash}, + {"BracketLeft", ImGuiKey_LeftBracket}, + {"BracketRight", ImGuiKey_RightBracket}, + {"Comma", ImGuiKey_Comma}, + {"Digit0", ImGuiKey_0}, + {"Digit1", ImGuiKey_1}, + {"Digit2", ImGuiKey_2}, + {"Digit3", ImGuiKey_3}, + {"Digit4", ImGuiKey_4}, + {"Digit5", ImGuiKey_5}, + {"Digit6", ImGuiKey_6}, + {"Digit7", ImGuiKey_7}, + {"Digit8", ImGuiKey_8}, + {"Digit9", ImGuiKey_9}, + {"Equal", ImGuiKey_Equal}, + {"IntlBackslash", ImGuiKey_Backslash}, // Mapping to generic backslash + {"IntlRo", ImGuiKey_Slash}, // Closest match for non-standard layouts + {"IntlYen", ImGuiKey_Backslash}, // Closest match for non-standard layouts + {"KeyA", ImGuiKey_A}, + {"KeyB", ImGuiKey_B}, + {"KeyC", ImGuiKey_C}, + {"KeyD", ImGuiKey_D}, + {"KeyE", ImGuiKey_E}, + {"KeyF", ImGuiKey_F}, + {"KeyG", ImGuiKey_G}, + {"KeyH", ImGuiKey_H}, + {"KeyI", ImGuiKey_I}, + {"KeyJ", ImGuiKey_J}, + {"KeyK", ImGuiKey_K}, + {"KeyL", ImGuiKey_L}, + {"KeyM", ImGuiKey_M}, + {"KeyN", ImGuiKey_N}, + {"KeyO", ImGuiKey_O}, + {"KeyP", ImGuiKey_P}, + {"KeyQ", ImGuiKey_Q}, + {"KeyR", ImGuiKey_R}, + {"KeyS", ImGuiKey_S}, + {"KeyT", ImGuiKey_T}, + {"KeyU", ImGuiKey_U}, + {"KeyV", ImGuiKey_V}, + {"KeyW", ImGuiKey_W}, + {"KeyX", ImGuiKey_X}, + {"KeyY", ImGuiKey_Y}, + {"KeyZ", ImGuiKey_Z}, + {"Minus", ImGuiKey_Minus}, + {"Period", ImGuiKey_Period}, + {"Quote", ImGuiKey_Apostrophe}, + {"Semicolon", ImGuiKey_Semicolon}, + {"Slash", ImGuiKey_Slash}, - // control keys - {"AltLeft", ImGuiKey_LeftAlt}, - {"AltRight", ImGuiKey_RightAlt}, - {"Backspace", ImGuiKey_Backspace}, - {"CapsLock", ImGuiKey_CapsLock}, - {"ContextMenu", ImGuiKey_Menu}, - {"ControlLeft", ImGuiKey_LeftCtrl}, - {"ControlRight", ImGuiKey_RightCtrl}, - {"Enter", ImGuiKey_Enter}, - {"MetaLeft", ImGuiKey_LeftSuper}, - {"MetaRight", ImGuiKey_RightSuper}, - {"ShiftLeft", ImGuiKey_LeftShift}, - {"ShiftRight", ImGuiKey_RightShift}, - {"Space", ImGuiKey_Space}, - {"Tab", ImGuiKey_Tab}, + // control keys + {"AltLeft", ImGuiKey_LeftAlt}, + {"AltRight", ImGuiKey_RightAlt}, + {"Backspace", ImGuiKey_Backspace}, + {"CapsLock", ImGuiKey_CapsLock}, + {"ContextMenu", ImGuiKey_Menu}, + {"ControlLeft", ImGuiKey_LeftCtrl}, + {"ControlRight", ImGuiKey_RightCtrl}, + {"Enter", ImGuiKey_Enter}, + {"MetaLeft", ImGuiKey_LeftSuper}, + {"MetaRight", ImGuiKey_RightSuper}, + {"ShiftLeft", ImGuiKey_LeftShift}, + {"ShiftRight", ImGuiKey_RightShift}, + {"Space", ImGuiKey_Space}, + {"Tab", ImGuiKey_Tab}, - // navigation key group - {"Delete", ImGuiKey_Delete}, - {"End", ImGuiKey_End}, - //{"Help", ImGuiKey_PrintScreen}, // Best approximation - {"Home", ImGuiKey_Home}, - {"Insert", ImGuiKey_Insert}, - {"PageDown", ImGuiKey_PageDown}, - {"PageUp", ImGuiKey_PageUp}, + // navigation key group + {"Delete", ImGuiKey_Delete}, + {"End", ImGuiKey_End}, + //{"Help", ImGuiKey_PrintScreen}, // Best approximation + {"Home", ImGuiKey_Home}, + {"Insert", ImGuiKey_Insert}, + {"PageDown", ImGuiKey_PageDown}, + {"PageUp", ImGuiKey_PageUp}, - // arrow key group - {"ArrowDown", ImGuiKey_DownArrow}, - {"ArrowLeft", ImGuiKey_LeftArrow}, - {"ArrowRight", ImGuiKey_RightArrow}, - {"ArrowUp", ImGuiKey_UpArrow}, + // arrow key group + {"ArrowDown", ImGuiKey_DownArrow}, + {"ArrowLeft", ImGuiKey_LeftArrow}, + {"ArrowRight", ImGuiKey_RightArrow}, + {"ArrowUp", ImGuiKey_UpArrow}, - // number pad group - {"NumLock", ImGuiKey_NumLock}, - {"Numpad0", ImGuiKey_Keypad0}, - {"Numpad1", ImGuiKey_Keypad1}, - {"Numpad2", ImGuiKey_Keypad2}, - {"Numpad3", ImGuiKey_Keypad3}, - {"Numpad4", ImGuiKey_Keypad4}, - {"Numpad5", ImGuiKey_Keypad5}, - {"Numpad6", ImGuiKey_Keypad6}, - {"Numpad7", ImGuiKey_Keypad7}, - {"Numpad8", ImGuiKey_Keypad8}, - {"Numpad9", ImGuiKey_Keypad9}, - {"NumpadAdd", ImGuiKey_KeypadAdd}, - {"NumpadBackspace", ImGuiKey_Backspace}, // No direct mapping; backspace functionality - //{"NumpadClear", ImGuiKey_KeypadClear}, // Custom-defined if needed - //{"NumpadClearEntry", ImGuiKey_KeypadClear}, // Custom-defined if needed - {"NumpadComma", ImGuiKey_KeypadDecimal}, // Closest match - {"NumpadDecimal", ImGuiKey_KeypadDecimal}, - {"NumpadDivide", ImGuiKey_KeypadDivide}, - {"NumpadEnter", ImGuiKey_KeypadEnter}, - {"NumpadEqual", ImGuiKey_KeypadEqual}, - {"NumpadHash", ImGuiKey_Backslash}, // Mapped to # on UK keyboard - //{"NumpadMemoryAdd", ImGuiKey_None}, // No defined mapping - //{"NumpadMemoryClear", ImGuiKey_None}, // No defined mapping - //{"NumpadMemoryRecall", ImGuiKey_None}, // No defined mapping - //{"NumpadMemoryStore", ImGuiKey_None}, // No defined mapping - //{"NumpadMemorySubtract", ImGuiKey_None}, // No defined mapping - {"NumpadMultiply", ImGuiKey_KeypadMultiply}, - {"NumpadParenLeft", ImGuiKey_LeftBracket}, // Closest available - {"NumpadParenRight", ImGuiKey_RightBracket}, // Closest available - {"NumpadStar", ImGuiKey_KeypadMultiply}, // Same as multiply - {"NumpadSubtract", ImGuiKey_KeypadSubtract}, + // number pad group + {"NumLock", ImGuiKey_NumLock}, + {"Numpad0", ImGuiKey_Keypad0}, + {"Numpad1", ImGuiKey_Keypad1}, + {"Numpad2", ImGuiKey_Keypad2}, + {"Numpad3", ImGuiKey_Keypad3}, + {"Numpad4", ImGuiKey_Keypad4}, + {"Numpad5", ImGuiKey_Keypad5}, + {"Numpad6", ImGuiKey_Keypad6}, + {"Numpad7", ImGuiKey_Keypad7}, + {"Numpad8", ImGuiKey_Keypad8}, + {"Numpad9", ImGuiKey_Keypad9}, + {"NumpadAdd", ImGuiKey_KeypadAdd}, + {"NumpadBackspace", ImGuiKey_Backspace}, // No direct mapping; backspace functionality + //{"NumpadClear", ImGuiKey_KeypadClear}, // Custom-defined if needed + //{"NumpadClearEntry", ImGuiKey_KeypadClear}, // Custom-defined if needed + {"NumpadComma", ImGuiKey_KeypadDecimal}, // Closest match + {"NumpadDecimal", ImGuiKey_KeypadDecimal}, + {"NumpadDivide", ImGuiKey_KeypadDivide}, + {"NumpadEnter", ImGuiKey_KeypadEnter}, + {"NumpadEqual", ImGuiKey_KeypadEqual}, + {"NumpadHash", ImGuiKey_Backslash}, // Mapped to # on UK keyboard + //{"NumpadMemoryAdd", ImGuiKey_None}, // No defined mapping + //{"NumpadMemoryClear", ImGuiKey_None}, // No defined mapping + //{"NumpadMemoryRecall", ImGuiKey_None}, // No defined mapping + //{"NumpadMemoryStore", ImGuiKey_None}, // No defined mapping + //{"NumpadMemorySubtract", ImGuiKey_None}, // No defined mapping + {"NumpadMultiply", ImGuiKey_KeypadMultiply}, + {"NumpadParenLeft", ImGuiKey_LeftBracket}, // Closest available + {"NumpadParenRight", ImGuiKey_RightBracket}, // Closest available + {"NumpadStar", ImGuiKey_KeypadMultiply}, // Same as multiply + {"NumpadSubtract", ImGuiKey_KeypadSubtract}, - // top row key groups - {"Escape", ImGuiKey_Escape}, - {"F1", ImGuiKey_F1}, - {"F2", ImGuiKey_F2}, - {"F3", ImGuiKey_F3}, - {"F4", ImGuiKey_F4}, - {"F5", ImGuiKey_F5}, - {"F6", ImGuiKey_F6}, - {"F6", ImGuiKey_F6}, - {"F7", ImGuiKey_F7}, - {"F8", ImGuiKey_F8}, - {"F9", ImGuiKey_F9}, - {"F10", ImGuiKey_F10}, - {"F11", ImGuiKey_F11}, - {"F12", ImGuiKey_F12}, - //{"Fn", ImGuiKey_None}, // No direct mapping - //{"FnLock", ImGuiKey_None}, // No direct mapping - {"PrintScreen", ImGuiKey_PrintScreen}, - {"ScrollLock", ImGuiKey_ScrollLock}, - {"Pause", ImGuiKey_Pause}, + // top row key groups + {"Escape", ImGuiKey_Escape}, + {"F1", ImGuiKey_F1}, + {"F2", ImGuiKey_F2}, + {"F3", ImGuiKey_F3}, + {"F4", ImGuiKey_F4}, + {"F5", ImGuiKey_F5}, + {"F6", ImGuiKey_F6}, + {"F6", ImGuiKey_F6}, + {"F7", ImGuiKey_F7}, + {"F8", ImGuiKey_F8}, + {"F9", ImGuiKey_F9}, + {"F10", ImGuiKey_F10}, + {"F11", ImGuiKey_F11}, + {"F12", ImGuiKey_F12}, + //{"Fn", ImGuiKey_None}, // No direct mapping + //{"FnLock", ImGuiKey_None}, // No direct mapping + {"PrintScreen", ImGuiKey_PrintScreen}, + {"ScrollLock", ImGuiKey_ScrollLock}, + {"Pause", ImGuiKey_Pause}, }; ImGuiKey translate_key(char const* emscripten_key) __attribute__((__const__)); ImGuiKey translate_key(char const* emscripten_key) { - // Translate an emscripten-provided browser string describing a keycode to an imgui key code - if(auto it{key_translate_lookup.find(emscripten_key)}; it != key_translate_lookup.end()) { - return it->second; - } - return ImGuiKey_None; + // Translate an emscripten-provided browser string describing a keycode to an imgui key code + if(auto it{key_translate_lookup.find(emscripten_key)}; it != key_translate_lookup.end()) { + return it->second; + } + return ImGuiKey_None; } constexpr ImGuiMouseButton translate_mousebutton(unsigned short emscripten_button) __attribute__((__const__)); constexpr ImGuiMouseButton translate_mousebutton(unsigned short emscripten_button) { - // Translate an emscripten-provided integer describing a mouse button to an imgui mouse button - if(emscripten_button == 1) return ImGuiMouseButton_Middle; // 1 = middle mouse button - if(emscripten_button == 2) return ImGuiMouseButton_Right; // 2 = right mouse button - if(emscripten_button > ImGuiMouseButton_COUNT) return ImGuiMouseButton_Middle; // treat any weird clicks on unexpected buttons (button 6 upwards) as middle mouse - return emscripten_button; // any other button translates 1:1 + // Translate an emscripten-provided integer describing a mouse button to an imgui mouse button + if(emscripten_button == 1) return ImGuiMouseButton_Middle; // 1 = middle mouse button + if(emscripten_button == 2) return ImGuiMouseButton_Right; // 2 = right mouse button + if(emscripten_button > ImGuiMouseButton_COUNT) return ImGuiMouseButton_Middle; // treat any weird clicks on unexpected buttons (button 6 upwards) as middle mouse + return emscripten_button; // any other button translates 1:1 } } // anonymous namespace @@ -188,56 +188,56 @@ namespace emscripten_browser_cursor_internal { // Browser cursor helpers enum class cursor { - // General - cursor_auto, // The UA will determine the cursor to display based on the current context. E.g., equivalent to text when hovering text. - cursor_default, // The platform-dependent default cursor. Typically an arrow. - none, // No cursor is rendered. + // General + cursor_auto, // The UA will determine the cursor to display based on the current context. E.g., equivalent to text when hovering text. + cursor_default, // The platform-dependent default cursor. Typically an arrow. + none, // No cursor is rendered. - // Links & status - context_menu, // cursor slightly obscuring a menu icon - A context menu is available. - help, // cursor next to a question mark - Help information is available. - pointer, // right hand with an index finger pointing up - The cursor is a pointer that indicates a link. Typically an image of a pointing hand. - progress, // cursor and hour glass - The program is busy in the background, but the user can still interact with the interface (in contrast to wait). - wait, // hour glass - The program is busy, and the user can't interact with the interface (in contrast to progress). Sometimes an image of an hourglass or a watch. + // Links & status + context_menu, // cursor slightly obscuring a menu icon - A context menu is available. + help, // cursor next to a question mark - Help information is available. + pointer, // right hand with an index finger pointing up - The cursor is a pointer that indicates a link. Typically an image of a pointing hand. + progress, // cursor and hour glass - The program is busy in the background, but the user can still interact with the interface (in contrast to wait). + wait, // hour glass - The program is busy, and the user can't interact with the interface (in contrast to progress). Sometimes an image of an hourglass or a watch. - // Selection - cell, // plus symbol - The table cell or set of cells can be selected. - crosshair, // crosshair - Cross cursor, often used to indicate selection in a bitmap. - text, // vertical i-beam - The text can be selected. Typically the shape of an I-beam. - vertical_text, // horizontal i-beam - The vertical text can be selected. Typically the shape of a sideways I-beam. + // Selection + cell, // plus symbol - The table cell or set of cells can be selected. + crosshair, // crosshair - Cross cursor, often used to indicate selection in a bitmap. + text, // vertical i-beam - The text can be selected. Typically the shape of an I-beam. + vertical_text, // horizontal i-beam - The vertical text can be selected. Typically the shape of a sideways I-beam. - // Drag & drop - alias, // cursor next to a folder icon with a curved arrow pointing up and to the right - An alias or shortcut is to be created. - copy, // cursor next to a smaller folder icon with a plus sign - Something is to be copied. - move, // plus sign made of two thin lines, with small arrows facing out - Something is to be moved. - no_drop, // cursor next to circle with a line through it - An item may not be dropped at the current location. - not_allowed, // Circle with a line through it - The requested action will not be carried out. - grab, // fully opened hand - Something can be grabbed (dragged to be moved). - grabbing, // closed hand - Something is being grabbed (dragged to be moved). + // Drag & drop + alias, // cursor next to a folder icon with a curved arrow pointing up and to the right - An alias or shortcut is to be created. + copy, // cursor next to a smaller folder icon with a plus sign - Something is to be copied. + move, // plus sign made of two thin lines, with small arrows facing out - Something is to be moved. + no_drop, // cursor next to circle with a line through it - An item may not be dropped at the current location. + not_allowed, // Circle with a line through it - The requested action will not be carried out. + grab, // fully opened hand - Something can be grabbed (dragged to be moved). + grabbing, // closed hand - Something is being grabbed (dragged to be moved). - // Resizing & scrolling - all_scroll, // dot with four triangles around it - Something can be scrolled in any direction (panned). - col_resize, // The item/column can be resized horizontally. Often rendered as arrows pointing left and right with a vertical bar separating them. - row_resize, // The item/row can be resized vertically. Often rendered as arrows pointing up and down with a horizontal bar separating them. - n_resize, // arrow pointing up - Some edge is to be moved. For example, the se-resize cursor is used when the movement starts from the south-east corner of the box. - e_resize, // arrow pointing right - s_resize, // arrow pointing down - w_resize, // arrow pointing left - ne_resize, // arrow pointing top-right - nw_resize, // arrow pointing top-left - se_resize, // arrow pointing bottom-right - sw_resize, // arrow pointing bottom-left - ew_resize, // arrow pointing left and right - Bidirectional resize cursor. - ns_resize, // arrow pointing up and down - nesw_resize, // arrow pointing both to the top-right and bottom-left - nwse_resize, // arrow pointing both to the top-left and bottom-right + // Resizing & scrolling + all_scroll, // dot with four triangles around it - Something can be scrolled in any direction (panned). + col_resize, // The item/column can be resized horizontally. Often rendered as arrows pointing left and right with a vertical bar separating them. + row_resize, // The item/row can be resized vertically. Often rendered as arrows pointing up and down with a horizontal bar separating them. + n_resize, // arrow pointing up - Some edge is to be moved. For example, the se-resize cursor is used when the movement starts from the south-east corner of the box. + e_resize, // arrow pointing right + s_resize, // arrow pointing down + w_resize, // arrow pointing left + ne_resize, // arrow pointing top-right + nw_resize, // arrow pointing top-left + se_resize, // arrow pointing bottom-right + sw_resize, // arrow pointing bottom-left + ew_resize, // arrow pointing left and right - Bidirectional resize cursor. + ns_resize, // arrow pointing up and down + nesw_resize, // arrow pointing both to the top-right and bottom-left + nwse_resize, // arrow pointing both to the top-left and bottom-right - // Zooming - zoom_in, // magnifying glass with a plus sign - Something can be zoomed (magnified) in or out. - zoom_out, + // Zooming + zoom_in, // magnifying glass with a plus sign - Something can be zoomed (magnified) in or out. + zoom_out, - // Special invalid value - invalid = std::numeric_limits::max() + // Special invalid value + invalid = std::numeric_limits::max() }; void set(cursor new_cursor); // set a new cursor from a cursor enum @@ -246,62 +246,62 @@ void unset(); // Browser cursor helper implementation bool is_set() { - // Returns whether the cursor is currently set - return EM_ASM_INT( - return !(!document.body.style.cursor || document.body.style.cursor.length === 0 ); - ); + // Returns whether the cursor is currently set + return EM_ASM_INT( + return !(!document.body.style.cursor || document.body.style.cursor.length === 0 ); + ); } std::string get_string() { - // Return the current cursor setting as a string - auto cursor_str_ptr{reinterpret_cast(EM_ASM_PTR( - return stringToNewUTF8(document.body.style.cursor); - ))}; - std::string const cursor_str{cursor_str_ptr}; - free(cursor_str_ptr); - return cursor_str; + // Return the current cursor setting as a string + auto cursor_str_ptr{reinterpret_cast(EM_ASM_PTR( + return stringToNewUTF8(document.body.style.cursor); + ))}; + std::string const cursor_str{cursor_str_ptr}; + free(cursor_str_ptr); + return cursor_str; } void set(cursor new_cursor) { - // Set the cursor according to the given enum - // Note, implementations omitted for cursors not used by imgui. For full implementation, use https://github.com/Armchair-Software/emscripten-browser-cursor - switch(new_cursor) { - case cursor::cursor_default: - default: - EM_ASM(document.body.style.cursor = 'default';); - break; - case cursor::pointer: - EM_ASM(document.body.style.cursor = 'pointer';); - break; - case cursor::text: - EM_ASM(document.body.style.cursor = 'text';); - break; - case cursor::move: - EM_ASM(document.body.style.cursor = 'move';); - break; - case cursor::not_allowed: - EM_ASM(document.body.style.cursor = 'not-allowed';); - break; - case cursor::ew_resize: - EM_ASM(document.body.style.cursor = 'ew-resize';); - break; - case cursor::ns_resize: - EM_ASM(document.body.style.cursor = 'ns-resize';); - break; - case cursor::nesw_resize: - EM_ASM(document.body.style.cursor = 'nesw-resize';); - break; - case cursor::nwse_resize: - EM_ASM(document.body.style.cursor = 'nwse-resize';); - break; - } + // Set the cursor according to the given enum + // Note, implementations omitted for cursors not used by imgui. For full implementation, use https://github.com/Armchair-Software/emscripten-browser-cursor + switch(new_cursor) { + case cursor::cursor_default: + default: + EM_ASM(document.body.style.cursor = 'default';); + break; + case cursor::pointer: + EM_ASM(document.body.style.cursor = 'pointer';); + break; + case cursor::text: + EM_ASM(document.body.style.cursor = 'text';); + break; + case cursor::move: + EM_ASM(document.body.style.cursor = 'move';); + break; + case cursor::not_allowed: + EM_ASM(document.body.style.cursor = 'not-allowed';); + break; + case cursor::ew_resize: + EM_ASM(document.body.style.cursor = 'ew-resize';); + break; + case cursor::ns_resize: + EM_ASM(document.body.style.cursor = 'ns-resize';); + break; + case cursor::nesw_resize: + EM_ASM(document.body.style.cursor = 'nesw-resize';); + break; + case cursor::nwse_resize: + EM_ASM(document.body.style.cursor = 'nwse-resize';); + break; + } } void set(std::string const &new_cursor) { - // Set the cursor from an arbitrary string - EM_ASM({ - document.body.style.cursor = UTF8ToString($0); - }, new_cursor.c_str()); + // Set the cursor from an arbitrary string + EM_ASM({ + document.body.style.cursor = UTF8ToString($0); + }, new_cursor.c_str()); } } // namespace emscripten_browser_cursor @@ -309,311 +309,311 @@ void set(std::string const &new_cursor) { namespace { void update_cursor() { - // Sync any cursor changes due to ImGui to the browser's cursor - static emscripten_browser_cursor_internal::cursor current_cursor{emscripten_browser_cursor_internal::cursor::invalid}; - static std::optional cursor_to_restore; + // Sync any cursor changes due to ImGui to the browser's cursor + static emscripten_browser_cursor_internal::cursor current_cursor{emscripten_browser_cursor_internal::cursor::invalid}; + static std::optional cursor_to_restore; - auto set_cursor_if_necessary{[&](emscripten_browser_cursor_internal::cursor new_cursor){ - if(new_cursor == current_cursor) return; // don't do anything if the current cursor is already set - current_cursor = new_cursor; - emscripten_browser_cursor_internal::set(new_cursor); - }}; + auto set_cursor_if_necessary{[&](emscripten_browser_cursor_internal::cursor new_cursor){ + if(new_cursor == current_cursor) return; // don't do anything if the current cursor is already set + current_cursor = new_cursor; + emscripten_browser_cursor_internal::set(new_cursor); + }}; - if(ImGui::GetIO().WantCaptureMouse) { // mouse is hovering over the gui - if(!cursor_to_restore && emscripten_browser_cursor_internal::is_set()) { - cursor_to_restore = emscripten_browser_cursor_internal::get_string(); // back up the existing cursor when entering the imgui capture space - } + if(ImGui::GetIO().WantCaptureMouse) { // mouse is hovering over the gui + if(!cursor_to_restore && emscripten_browser_cursor_internal::is_set()) { + cursor_to_restore = emscripten_browser_cursor_internal::get_string(); // back up the existing cursor when entering the imgui capture space + } - switch(ImGui::GetMouseCursor()) { - case ImGuiMouseCursor_Arrow: - set_cursor_if_necessary(emscripten_browser_cursor_internal::cursor::cursor_default); - break; - case ImGuiMouseCursor_TextInput: // When hovering over InputText, etc. - set_cursor_if_necessary(emscripten_browser_cursor_internal::cursor::text); - break; - case ImGuiMouseCursor_ResizeAll: // (Unused by Dear ImGui functions) - set_cursor_if_necessary(emscripten_browser_cursor_internal::cursor::move); - break; - case ImGuiMouseCursor_ResizeNS: // When hovering over a horizontal border - set_cursor_if_necessary(emscripten_browser_cursor_internal::cursor::ns_resize); - break; - case ImGuiMouseCursor_ResizeEW: // When hovering over a vertical border or a column - set_cursor_if_necessary(emscripten_browser_cursor_internal::cursor::ew_resize); - break; - case ImGuiMouseCursor_ResizeNESW: // When hovering over the bottom-left corner of a window - set_cursor_if_necessary(emscripten_browser_cursor_internal::cursor::nesw_resize); - break; - case ImGuiMouseCursor_ResizeNWSE: // When hovering over the bottom-right corner of a window - set_cursor_if_necessary(emscripten_browser_cursor_internal::cursor::nwse_resize); - break; - case ImGuiMouseCursor_Hand: // (Unused by Dear ImGui functions. Use for e.g. hyperlinks) - set_cursor_if_necessary(emscripten_browser_cursor_internal::cursor::pointer); - break; - case ImGuiMouseCursor_NotAllowed: // When hovering something with disallowed interaction. Usually a crossed circle. - set_cursor_if_necessary(emscripten_browser_cursor_internal::cursor::not_allowed); - break; + switch(ImGui::GetMouseCursor()) { + case ImGuiMouseCursor_Arrow: + set_cursor_if_necessary(emscripten_browser_cursor_internal::cursor::cursor_default); + break; + case ImGuiMouseCursor_TextInput: // When hovering over InputText, etc. + set_cursor_if_necessary(emscripten_browser_cursor_internal::cursor::text); + break; + case ImGuiMouseCursor_ResizeAll: // (Unused by Dear ImGui functions) + set_cursor_if_necessary(emscripten_browser_cursor_internal::cursor::move); + break; + case ImGuiMouseCursor_ResizeNS: // When hovering over a horizontal border + set_cursor_if_necessary(emscripten_browser_cursor_internal::cursor::ns_resize); + break; + case ImGuiMouseCursor_ResizeEW: // When hovering over a vertical border or a column + set_cursor_if_necessary(emscripten_browser_cursor_internal::cursor::ew_resize); + break; + case ImGuiMouseCursor_ResizeNESW: // When hovering over the bottom-left corner of a window + set_cursor_if_necessary(emscripten_browser_cursor_internal::cursor::nesw_resize); + break; + case ImGuiMouseCursor_ResizeNWSE: // When hovering over the bottom-right corner of a window + set_cursor_if_necessary(emscripten_browser_cursor_internal::cursor::nwse_resize); + break; + case ImGuiMouseCursor_Hand: // (Unused by Dear ImGui functions. Use for e.g. hyperlinks) + set_cursor_if_necessary(emscripten_browser_cursor_internal::cursor::pointer); + break; + case ImGuiMouseCursor_NotAllowed: // When hovering something with disallowed interaction. Usually a crossed circle. + set_cursor_if_necessary(emscripten_browser_cursor_internal::cursor::not_allowed); + break; + } + } else { // mouse is away from the gui, hovering over some other part of the viewport + if(cursor_to_restore) { + emscripten_browser_cursor_internal::set(*cursor_to_restore); // restore the previous cursor when leaving the imgui capture space + cursor_to_restore = std::nullopt; + current_cursor = emscripten_browser_cursor_internal::cursor::invalid; // select an unused value for current cursor to force a set next time set_cursor_if_necessary is called + } } - } else { // mouse is away from the gui, hovering over some other part of the viewport - if(cursor_to_restore) { - emscripten_browser_cursor_internal::set(*cursor_to_restore); // restore the previous cursor when leaving the imgui capture space - cursor_to_restore = std::nullopt; - current_cursor = emscripten_browser_cursor_internal::cursor::invalid; // select an unused value for current cursor to force a set next time set_cursor_if_necessary is called - } - } } } // anonymous namespace void ImGui_ImplEmscripten_Init() { - // Initialise the Emscripten backend, setting input callbacks - auto &imgui_io{ImGui::GetIO()}; - imgui_io.BackendPlatformName = "imgui_impl_emscripten"; - imgui_io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; + // Initialise the Emscripten backend, setting input callbacks + auto &imgui_io{ImGui::GetIO()}; + imgui_io.BackendPlatformName = "imgui_impl_emscripten"; + imgui_io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; - // set up initial display size values - imgui_io.DisplaySize.x = emscripten::val::global("window")["innerWidth"].as(); - imgui_io.DisplaySize.y = emscripten::val::global("window")["innerHeight"].as(); + // set up initial display size values + imgui_io.DisplaySize.x = emscripten::val::global("window")["innerWidth"].as(); + imgui_io.DisplaySize.y = emscripten::val::global("window")["innerHeight"].as(); - emscripten_set_mousemove_callback( - EMSCRIPTEN_EVENT_TARGET_WINDOW, // target - nullptr, // userData - false, // useCapture - [](int /*event_type*/, EmscriptenMouseEvent const *mouse_event, void */*data*/){ // callback, event_type == EMSCRIPTEN_EVENT_MOUSEMOVE - ImGui::GetIO().AddMousePosEvent( - static_cast(mouse_event->clientX), - static_cast(mouse_event->clientY) - ); - return true; // the event was consumed - } - ); - emscripten_set_mousedown_callback( - EMSCRIPTEN_EVENT_TARGET_WINDOW, // target - nullptr, // userData - false, // useCapture - [](int /*event_type*/, EmscriptenMouseEvent const *mouse_event, void */*data*/){ // callback, event_type == EMSCRIPTEN_EVENT_MOUSEDOWN - ImGui::GetIO().AddMouseButtonEvent(translate_mousebutton(mouse_event->button), true); // translated button, down - return true; // the event was consumed - } - ); - emscripten_set_mouseup_callback( - EMSCRIPTEN_EVENT_TARGET_WINDOW, // target - nullptr, // userData - false, // useCapture - [](int /*event_type*/, EmscriptenMouseEvent const *mouse_event, void */*data*/){ // callback, event_type == EMSCRIPTEN_EVENT_MOUSEUP - ImGui::GetIO().AddMouseButtonEvent(translate_mousebutton(mouse_event->button), false); // translated button, up - return true; // the event was consumed - } - ); - emscripten_set_mouseenter_callback( - EMSCRIPTEN_EVENT_TARGET_DOCUMENT, // target - WINDOW doesn't produce mouseenter events - nullptr, // userData - false, // useCapture - [](int /*event_type*/, EmscriptenMouseEvent const *mouse_event, void */*data*/){ // callback, event_type == EMSCRIPTEN_EVENT_MOUSEENTER - ImGui::GetIO().AddMousePosEvent( - static_cast(mouse_event->clientX), - static_cast(mouse_event->clientY) - ); - return true; // the event was consumed - } - ); - emscripten_set_mouseleave_callback( - EMSCRIPTEN_EVENT_TARGET_DOCUMENT, // target - WINDOW doesn't produce mouseenter events - nullptr, // userData - false, // useCapture - [](int /*event_type*/, EmscriptenMouseEvent const */*mouse_event*/, void */*data*/){ // callback, event_type == EMSCRIPTEN_EVENT_MOUSELEAVE - auto &imgui_io{ImGui::GetIO()}; - imgui_io.AddMousePosEvent(-FLT_MAX, -FLT_MAX); // cursor is not in the window - imgui_io.ClearInputKeys(); // clear pending input keys on mouse exit - return true; // the event was consumed - } - ); - emscripten_set_wheel_callback( - EMSCRIPTEN_EVENT_TARGET_WINDOW, // target - nullptr, // userData - false, // useCapture - [](int /*event_type*/, EmscriptenWheelEvent const *wheel_event, void */*data*/){ // callback, event_type == EMSCRIPTEN_EVENT_WHEEL - float scale{1.0f}; - switch(wheel_event->deltaMode) { - case DOM_DELTA_PIXEL: // scrolling in pixels - scale = 1.0f / 100.0f; - break; - case DOM_DELTA_LINE: // scrolling by lines - scale = 1.0f / 3.0f; - break; - case DOM_DELTA_PAGE: // scrolling by pages - scale = 80.0f; - break; - } - // TODO: make scrolling speeds configurable - ImGui::GetIO().AddMouseWheelEvent( - -static_cast(wheel_event->deltaX) * scale, - -static_cast(wheel_event->deltaY) * scale - ); - return false; // the event was not consumed - } - ); - emscripten_set_keydown_callback( - EMSCRIPTEN_EVENT_TARGET_WINDOW, // target - nullptr, // userData - false, // useCapture - [](int /*event_type*/, EmscriptenKeyboardEvent const *key_event, void */*data*/){ // callback, event_type == EMSCRIPTEN_EVENT_KEYDOWN - auto const key{translate_key(key_event->code)}; - auto &imgui_io{ImGui::GetIO()}; - imgui_io.AddKeyEvent(key, true); - switch(key) { // special cases for certain key events - case ImGuiKey_LeftCtrl: // additional events for modifier keys - case ImGuiKey_RightCtrl: - imgui_io.AddKeyEvent(ImGuiMod_Ctrl, true); - break; - case ImGuiKey_LeftShift: - case ImGuiKey_RightShift: - imgui_io.AddKeyEvent(ImGuiMod_Shift, true); - break; - case ImGuiKey_LeftAlt: - case ImGuiKey_RightAlt: - imgui_io.AddKeyEvent(ImGuiMod_Alt, true); - break; - case ImGuiKey_LeftSuper: - case ImGuiKey_RightSuper: - imgui_io.AddKeyEvent(ImGuiMod_Super, true); - break; - // TODO: case ImGuiKey_Menu: do we want to do anything with this? - case ImGuiKey_Tab: // consuming tab prevents the user tabbing to other parts of the browser interface outside the window content - return imgui_io.WantCaptureKeyboard; // the event was consumed only if imgui wants to capture the keyboard - case ImGuiKey_Enter: // consuming enter prevents the word "Enter" appearing in text input via the keypress callback - case ImGuiKey_Delete: // consuming enter prevents the word "Delete" appearing in text input via the keypress callback - return imgui_io.WantTextInput; // the event was consumed only if we're currently accepting text input - default: - break; - } - return false; // if no special handling, the event was not consumed - } - ); - emscripten_set_keyup_callback( - EMSCRIPTEN_EVENT_TARGET_WINDOW, // target - nullptr, // userData - false, // useCapture - [](int /*event_type*/, EmscriptenKeyboardEvent const *key_event, void */*data*/){ // callback, event_type == EMSCRIPTEN_EVENT_KEYUP - auto const key{translate_key(key_event->code)}; - auto &imgui_io{ImGui::GetIO()}; - imgui_io.AddKeyEvent(key, false); - switch(key) { // special cases for certain key events - case ImGuiKey_LeftCtrl: // additional events for modifier keys - case ImGuiKey_RightCtrl: - imgui_io.AddKeyEvent(ImGuiMod_Ctrl, false); - break; - case ImGuiKey_LeftShift: - case ImGuiKey_RightShift: - imgui_io.AddKeyEvent(ImGuiMod_Shift, false); - break; - case ImGuiKey_LeftAlt: - case ImGuiKey_RightAlt: - imgui_io.AddKeyEvent(ImGuiMod_Alt, false); - break; - case ImGuiKey_LeftSuper: - case ImGuiKey_RightSuper: - imgui_io.AddKeyEvent(ImGuiMod_Super, false); - break; - default: - break; - } - return false; // the event was not consumed - } - ); - emscripten_set_keypress_callback( - EMSCRIPTEN_EVENT_TARGET_WINDOW, // target - nullptr, // userData - false, // useCapture - [](int /*event_type*/, EmscriptenKeyboardEvent const *key_event, void */*data*/){ // callback, event_type == EMSCRIPTEN_EVENT_KEYPRESS - auto &imgui_io{ImGui::GetIO()}; - imgui_io.AddInputCharactersUTF8(key_event->key); - return imgui_io.WantCaptureKeyboard; // the event was consumed only if imgui wants to capture the keyboard - } - ); - emscripten_set_resize_callback( - EMSCRIPTEN_EVENT_TARGET_WINDOW, // target - nullptr, // userData - false, // useCapture - [](int /*event_type*/, EmscriptenUiEvent const *event, void */*data*/) { // event_type == EMSCRIPTEN_EVENT_RESIZE - auto &imgui_io{ImGui::GetIO()}; - imgui_io.DisplaySize.x = static_cast(event->windowInnerWidth); - imgui_io.DisplaySize.y = static_cast(event->windowInnerHeight); - return true; // the event was consumed - } - ); - emscripten_set_blur_callback( - EMSCRIPTEN_EVENT_TARGET_WINDOW, // target - nullptr, // userData - false, // useCapture - [](int /*event_type*/, EmscriptenFocusEvent const */*event*/, void */*data*/) { // event_type == EMSCRIPTEN_EVENT_BLUR - auto &imgui_io{ImGui::GetIO()}; - imgui_io.AddFocusEvent(false); - imgui_io.ClearInputKeys(); // clear pending input keys on focus gain - return true; // the event was consumed - } - ); - emscripten_set_focus_callback( - EMSCRIPTEN_EVENT_TARGET_WINDOW, // target - nullptr, // userData - false, // useCapture - [](int /*event_type*/, EmscriptenFocusEvent const */*event*/, void */*data*/) { // event_type == EMSCRIPTEN_EVENT_FOCUS - auto &imgui_io{ImGui::GetIO()}; - imgui_io.AddFocusEvent(true); - imgui_io.ClearInputKeys(); // clear pending input keys on focus loss - for example if you press tab to cycle to another part of the UI - return true; // the event was consumed - } - ); - emscripten_set_focusin_callback( - EMSCRIPTEN_EVENT_TARGET_WINDOW, // target - nullptr, // userData - false, // useCapture - [](int /*event_type*/, EmscriptenFocusEvent const */*event*/, void */*data*/) { // event_type == EMSCRIPTEN_EVENT_FOCUSIN - auto &imgui_io{ImGui::GetIO()}; - imgui_io.AddFocusEvent(true); - imgui_io.ClearInputKeys(); // clear pending input keys on focus gain - return true; // the event was consumed - } - ); - emscripten_set_focusout_callback( - EMSCRIPTEN_EVENT_TARGET_WINDOW, // target - nullptr, // userData - false, // useCapture - [](int /*event_type*/, EmscriptenFocusEvent const */*event*/, void */*data*/) { // event_type == EMSCRIPTEN_EVENT_FOCUSOUT - auto &imgui_io{ImGui::GetIO()}; - imgui_io.AddFocusEvent(false); - imgui_io.ClearInputKeys(); // clear pending input keys on focus loss - for example if you press tab to cycle to another part of the UI - return true; // the event was consumed - } - ); + emscripten_set_mousemove_callback( + EMSCRIPTEN_EVENT_TARGET_WINDOW, // target + nullptr, // userData + false, // useCapture + [](int /*event_type*/, EmscriptenMouseEvent const *mouse_event, void */*data*/){ // callback, event_type == EMSCRIPTEN_EVENT_MOUSEMOVE + ImGui::GetIO().AddMousePosEvent( + static_cast(mouse_event->clientX), + static_cast(mouse_event->clientY) + ); + return true; // the event was consumed + } + ); + emscripten_set_mousedown_callback( + EMSCRIPTEN_EVENT_TARGET_WINDOW, // target + nullptr, // userData + false, // useCapture + [](int /*event_type*/, EmscriptenMouseEvent const *mouse_event, void */*data*/){ // callback, event_type == EMSCRIPTEN_EVENT_MOUSEDOWN + ImGui::GetIO().AddMouseButtonEvent(translate_mousebutton(mouse_event->button), true); // translated button, down + return true; // the event was consumed + } + ); + emscripten_set_mouseup_callback( + EMSCRIPTEN_EVENT_TARGET_WINDOW, // target + nullptr, // userData + false, // useCapture + [](int /*event_type*/, EmscriptenMouseEvent const *mouse_event, void */*data*/){ // callback, event_type == EMSCRIPTEN_EVENT_MOUSEUP + ImGui::GetIO().AddMouseButtonEvent(translate_mousebutton(mouse_event->button), false); // translated button, up + return true; // the event was consumed + } + ); + emscripten_set_mouseenter_callback( + EMSCRIPTEN_EVENT_TARGET_DOCUMENT, // target - WINDOW doesn't produce mouseenter events + nullptr, // userData + false, // useCapture + [](int /*event_type*/, EmscriptenMouseEvent const *mouse_event, void */*data*/){ // callback, event_type == EMSCRIPTEN_EVENT_MOUSEENTER + ImGui::GetIO().AddMousePosEvent( + static_cast(mouse_event->clientX), + static_cast(mouse_event->clientY) + ); + return true; // the event was consumed + } + ); + emscripten_set_mouseleave_callback( + EMSCRIPTEN_EVENT_TARGET_DOCUMENT, // target - WINDOW doesn't produce mouseenter events + nullptr, // userData + false, // useCapture + [](int /*event_type*/, EmscriptenMouseEvent const */*mouse_event*/, void */*data*/){ // callback, event_type == EMSCRIPTEN_EVENT_MOUSELEAVE + auto &imgui_io{ImGui::GetIO()}; + imgui_io.AddMousePosEvent(-FLT_MAX, -FLT_MAX); // cursor is not in the window + imgui_io.ClearInputKeys(); // clear pending input keys on mouse exit + return true; // the event was consumed + } + ); + emscripten_set_wheel_callback( + EMSCRIPTEN_EVENT_TARGET_WINDOW, // target + nullptr, // userData + false, // useCapture + [](int /*event_type*/, EmscriptenWheelEvent const *wheel_event, void */*data*/){ // callback, event_type == EMSCRIPTEN_EVENT_WHEEL + float scale{1.0f}; + switch(wheel_event->deltaMode) { + case DOM_DELTA_PIXEL: // scrolling in pixels + scale = 1.0f / 100.0f; + break; + case DOM_DELTA_LINE: // scrolling by lines + scale = 1.0f / 3.0f; + break; + case DOM_DELTA_PAGE: // scrolling by pages + scale = 80.0f; + break; + } + // TODO: make scrolling speeds configurable + ImGui::GetIO().AddMouseWheelEvent( + -static_cast(wheel_event->deltaX) * scale, + -static_cast(wheel_event->deltaY) * scale + ); + return false; // the event was not consumed + } + ); + emscripten_set_keydown_callback( + EMSCRIPTEN_EVENT_TARGET_WINDOW, // target + nullptr, // userData + false, // useCapture + [](int /*event_type*/, EmscriptenKeyboardEvent const *key_event, void */*data*/){ // callback, event_type == EMSCRIPTEN_EVENT_KEYDOWN + auto const key{translate_key(key_event->code)}; + auto &imgui_io{ImGui::GetIO()}; + imgui_io.AddKeyEvent(key, true); + switch(key) { // special cases for certain key events + case ImGuiKey_LeftCtrl: // additional events for modifier keys + case ImGuiKey_RightCtrl: + imgui_io.AddKeyEvent(ImGuiMod_Ctrl, true); + break; + case ImGuiKey_LeftShift: + case ImGuiKey_RightShift: + imgui_io.AddKeyEvent(ImGuiMod_Shift, true); + break; + case ImGuiKey_LeftAlt: + case ImGuiKey_RightAlt: + imgui_io.AddKeyEvent(ImGuiMod_Alt, true); + break; + case ImGuiKey_LeftSuper: + case ImGuiKey_RightSuper: + imgui_io.AddKeyEvent(ImGuiMod_Super, true); + break; + // TODO: case ImGuiKey_Menu: do we want to do anything with this? + case ImGuiKey_Tab: // consuming tab prevents the user tabbing to other parts of the browser interface outside the window content + return imgui_io.WantCaptureKeyboard; // the event was consumed only if imgui wants to capture the keyboard + case ImGuiKey_Enter: // consuming enter prevents the word "Enter" appearing in text input via the keypress callback + case ImGuiKey_Delete: // consuming enter prevents the word "Delete" appearing in text input via the keypress callback + return imgui_io.WantTextInput; // the event was consumed only if we're currently accepting text input + default: + break; + } + return false; // if no special handling, the event was not consumed + } + ); + emscripten_set_keyup_callback( + EMSCRIPTEN_EVENT_TARGET_WINDOW, // target + nullptr, // userData + false, // useCapture + [](int /*event_type*/, EmscriptenKeyboardEvent const *key_event, void */*data*/){ // callback, event_type == EMSCRIPTEN_EVENT_KEYUP + auto const key{translate_key(key_event->code)}; + auto &imgui_io{ImGui::GetIO()}; + imgui_io.AddKeyEvent(key, false); + switch(key) { // special cases for certain key events + case ImGuiKey_LeftCtrl: // additional events for modifier keys + case ImGuiKey_RightCtrl: + imgui_io.AddKeyEvent(ImGuiMod_Ctrl, false); + break; + case ImGuiKey_LeftShift: + case ImGuiKey_RightShift: + imgui_io.AddKeyEvent(ImGuiMod_Shift, false); + break; + case ImGuiKey_LeftAlt: + case ImGuiKey_RightAlt: + imgui_io.AddKeyEvent(ImGuiMod_Alt, false); + break; + case ImGuiKey_LeftSuper: + case ImGuiKey_RightSuper: + imgui_io.AddKeyEvent(ImGuiMod_Super, false); + break; + default: + break; + } + return false; // the event was not consumed + } + ); + emscripten_set_keypress_callback( + EMSCRIPTEN_EVENT_TARGET_WINDOW, // target + nullptr, // userData + false, // useCapture + [](int /*event_type*/, EmscriptenKeyboardEvent const *key_event, void */*data*/){ // callback, event_type == EMSCRIPTEN_EVENT_KEYPRESS + auto &imgui_io{ImGui::GetIO()}; + imgui_io.AddInputCharactersUTF8(key_event->key); + return imgui_io.WantCaptureKeyboard; // the event was consumed only if imgui wants to capture the keyboard + } + ); + emscripten_set_resize_callback( + EMSCRIPTEN_EVENT_TARGET_WINDOW, // target + nullptr, // userData + false, // useCapture + [](int /*event_type*/, EmscriptenUiEvent const *event, void */*data*/) { // event_type == EMSCRIPTEN_EVENT_RESIZE + auto &imgui_io{ImGui::GetIO()}; + imgui_io.DisplaySize.x = static_cast(event->windowInnerWidth); + imgui_io.DisplaySize.y = static_cast(event->windowInnerHeight); + return true; // the event was consumed + } + ); + emscripten_set_blur_callback( + EMSCRIPTEN_EVENT_TARGET_WINDOW, // target + nullptr, // userData + false, // useCapture + [](int /*event_type*/, EmscriptenFocusEvent const */*event*/, void */*data*/) { // event_type == EMSCRIPTEN_EVENT_BLUR + auto &imgui_io{ImGui::GetIO()}; + imgui_io.AddFocusEvent(false); + imgui_io.ClearInputKeys(); // clear pending input keys on focus gain + return true; // the event was consumed + } + ); + emscripten_set_focus_callback( + EMSCRIPTEN_EVENT_TARGET_WINDOW, // target + nullptr, // userData + false, // useCapture + [](int /*event_type*/, EmscriptenFocusEvent const */*event*/, void */*data*/) { // event_type == EMSCRIPTEN_EVENT_FOCUS + auto &imgui_io{ImGui::GetIO()}; + imgui_io.AddFocusEvent(true); + imgui_io.ClearInputKeys(); // clear pending input keys on focus loss - for example if you press tab to cycle to another part of the UI + return true; // the event was consumed + } + ); + emscripten_set_focusin_callback( + EMSCRIPTEN_EVENT_TARGET_WINDOW, // target + nullptr, // userData + false, // useCapture + [](int /*event_type*/, EmscriptenFocusEvent const */*event*/, void */*data*/) { // event_type == EMSCRIPTEN_EVENT_FOCUSIN + auto &imgui_io{ImGui::GetIO()}; + imgui_io.AddFocusEvent(true); + imgui_io.ClearInputKeys(); // clear pending input keys on focus gain + return true; // the event was consumed + } + ); + emscripten_set_focusout_callback( + EMSCRIPTEN_EVENT_TARGET_WINDOW, // target + nullptr, // userData + false, // useCapture + [](int /*event_type*/, EmscriptenFocusEvent const */*event*/, void */*data*/) { // event_type == EMSCRIPTEN_EVENT_FOCUSOUT + auto &imgui_io{ImGui::GetIO()}; + imgui_io.AddFocusEvent(false); + imgui_io.ClearInputKeys(); // clear pending input keys on focus loss - for example if you press tab to cycle to another part of the UI + return true; // the event was consumed + } + ); - // TODO: touch events + // TODO: touch events } void ImGui_ImplEmscripten_Shutdown() { - // Unset any callbacks set by Init - emscripten_set_mousemove_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr); - emscripten_set_mousedown_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr); - emscripten_set_mouseup_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr); - emscripten_set_mouseenter_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, nullptr, false, nullptr); - emscripten_set_mouseleave_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, nullptr, false, nullptr); - emscripten_set_wheel_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr); - emscripten_set_keydown_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr); - emscripten_set_keyup_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr); - emscripten_set_keypress_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr); - emscripten_set_resize_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr); - emscripten_set_focusin_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr); - emscripten_set_focusout_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr); - // TODO: touch events + // Unset any callbacks set by Init + emscripten_set_mousemove_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr); + emscripten_set_mousedown_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr); + emscripten_set_mouseup_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr); + emscripten_set_mouseenter_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, nullptr, false, nullptr); + emscripten_set_mouseleave_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, nullptr, false, nullptr); + emscripten_set_wheel_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr); + emscripten_set_keydown_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr); + emscripten_set_keyup_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr); + emscripten_set_keypress_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr); + emscripten_set_resize_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr); + emscripten_set_focusin_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr); + emscripten_set_focusout_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr); + // TODO: touch events - auto &imgui_io{ImGui::GetIO()}; - imgui_io.BackendPlatformName = nullptr; - imgui_io.BackendPlatformUserData = nullptr; - imgui_io.BackendFlags &= ~ImGuiBackendFlags_HasMouseCursors; + auto &imgui_io{ImGui::GetIO()}; + imgui_io.BackendPlatformName = nullptr; + imgui_io.BackendPlatformUserData = nullptr; + imgui_io.BackendFlags &= ~ImGuiBackendFlags_HasMouseCursors; } void ImGui_ImplEmscripten_NewFrame() { - // Update any state that needs to be polled - update_cursor(); + // Update any state that needs to be polled + update_cursor(); } #endif // IMGUI_DISABLE diff --git a/backends/imgui_impl_emscripten.h b/backends/imgui_impl_emscripten.h index fb15af075..be2b7280d 100644 --- a/backends/imgui_impl_emscripten.h +++ b/backends/imgui_impl_emscripten.h @@ -36,7 +36,7 @@ #pragma once #ifndef __EMSCRIPTEN__ - #error The imgui_impl_emscripten backend reqiuires Emscripten. +#error The imgui_impl_emscripten backend reqiuires Emscripten. #endif // Initialise the Emscripten backend, setting input callbacks. This should be called after ImGui::CreateContext(); From 2787e6571259c54bca5f6a4cd2d8faa754544db8 Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Tue, 31 Mar 2026 15:12:36 +0100 Subject: [PATCH 10/72] remove copyright clashing with CLA --- backends/imgui_impl_emscripten.cpp | 4 +--- backends/imgui_impl_emscripten.h | 2 -- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index 46414efe9..05e127ee1 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -2,8 +2,6 @@ // // See documentation in imgui_impl_emscripten.h. // -// Copyright 2024 Eugene Hopkinson -// // CHANGELOG // (minor and older changes stripped away, please see git history for details) // 2024-12-09: Inputs: Added special handling for modifier keys to also generate modifier key events. @@ -11,7 +9,7 @@ // 2024-12-06: Inputs: Added special handling for Tab and Enter event capture. // 2024-12-06: Inputs: Handle blur and focus events correctly, focusin and focusout aren't enough. // 2024-12-06: Emscripten: Don't use devicePixelRatio as it's not needed with WebGPU. -// 2024-11-22: Initial version. +// 2024-11-22: Initial version by Eugene Hopkinson. (#8178) #include "imgui.h" #ifndef IMGUI_DISABLE diff --git a/backends/imgui_impl_emscripten.h b/backends/imgui_impl_emscripten.h index be2b7280d..923d0def2 100644 --- a/backends/imgui_impl_emscripten.h +++ b/backends/imgui_impl_emscripten.h @@ -12,8 +12,6 @@ // In that case, this backend replaces all non-rendering-related functionality from GLFW, making it possible to avoid depending on GLFW altogether. // // For native cursor rendering, this includes a cut-down implementation of the Emscripten Browser Cursor library: https://github.com/Armchair-Software/emscripten-browser-cursor -// -// Copyright 2024 Eugene Hopkinson // Supported features: // - Keyboard input From 0680e7d7a919bcd4f0ff4b11b1f3f2b81b40736a Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Tue, 31 Mar 2026 15:22:17 +0100 Subject: [PATCH 11/72] adjust brace and bracket style to match imgui standard --- backends/imgui_impl_emscripten.cpp | 95 +++++++++++++++++++----------- 1 file changed, 59 insertions(+), 36 deletions(-) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index 05e127ee1..a8ecf8e83 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -162,30 +162,35 @@ static const std::unordered_map key_translate_lookup{ }; ImGuiKey translate_key(char const* emscripten_key) __attribute__((__const__)); -ImGuiKey translate_key(char const* emscripten_key) { +ImGuiKey translate_key(char const* emscripten_key) +{ // Translate an emscripten-provided browser string describing a keycode to an imgui key code - if(auto it{key_translate_lookup.find(emscripten_key)}; it != key_translate_lookup.end()) { + if (auto it{key_translate_lookup.find(emscripten_key)}; it != key_translate_lookup.end()) + { return it->second; } return ImGuiKey_None; } constexpr ImGuiMouseButton translate_mousebutton(unsigned short emscripten_button) __attribute__((__const__)); -constexpr ImGuiMouseButton translate_mousebutton(unsigned short emscripten_button) { +constexpr ImGuiMouseButton translate_mousebutton(unsigned short emscripten_button) +{ // Translate an emscripten-provided integer describing a mouse button to an imgui mouse button - if(emscripten_button == 1) return ImGuiMouseButton_Middle; // 1 = middle mouse button - if(emscripten_button == 2) return ImGuiMouseButton_Right; // 2 = right mouse button - if(emscripten_button > ImGuiMouseButton_COUNT) return ImGuiMouseButton_Middle; // treat any weird clicks on unexpected buttons (button 6 upwards) as middle mouse + if (emscripten_button == 1) return ImGuiMouseButton_Middle; // 1 = middle mouse button + if (emscripten_button == 2) return ImGuiMouseButton_Right; // 2 = right mouse button + if (emscripten_button > ImGuiMouseButton_COUNT) return ImGuiMouseButton_Middle; // treat any weird clicks on unexpected buttons (button 6 upwards) as middle mouse return emscripten_button; // any other button translates 1:1 } } // anonymous namespace -namespace emscripten_browser_cursor_internal { +namespace emscripten_browser_cursor_internal +{ // Browser cursor helpers -enum class cursor { +enum class cursor +{ // General cursor_auto, // The UA will determine the cursor to display based on the current context. E.g., equivalent to text when hovering text. cursor_default, // The platform-dependent default cursor. Typically an arrow. @@ -243,14 +248,16 @@ void unset(); // Browser cursor helper implementation -bool is_set() { +bool is_set() +{ // Returns whether the cursor is currently set return EM_ASM_INT( return !(!document.body.style.cursor || document.body.style.cursor.length === 0 ); ); } -std::string get_string() { +std::string get_string() +{ // Return the current cursor setting as a string auto cursor_str_ptr{reinterpret_cast(EM_ASM_PTR( return stringToNewUTF8(document.body.style.cursor); @@ -260,10 +267,12 @@ std::string get_string() { return cursor_str; } -void set(cursor new_cursor) { +void set(cursor new_cursor) +{ // Set the cursor according to the given enum // Note, implementations omitted for cursors not used by imgui. For full implementation, use https://github.com/Armchair-Software/emscripten-browser-cursor - switch(new_cursor) { + switch (new_cursor) + { case cursor::cursor_default: default: EM_ASM(document.body.style.cursor = 'default';); @@ -295,7 +304,8 @@ void set(cursor new_cursor) { } } -void set(std::string const &new_cursor) { +void set(std::string const &new_cursor) +{ // Set the cursor from an arbitrary string EM_ASM({ document.body.style.cursor = UTF8ToString($0); @@ -306,23 +316,27 @@ void set(std::string const &new_cursor) { namespace { -void update_cursor() { +void update_cursor() +{ // Sync any cursor changes due to ImGui to the browser's cursor static emscripten_browser_cursor_internal::cursor current_cursor{emscripten_browser_cursor_internal::cursor::invalid}; static std::optional cursor_to_restore; - auto set_cursor_if_necessary{[&](emscripten_browser_cursor_internal::cursor new_cursor){ - if(new_cursor == current_cursor) return; // don't do anything if the current cursor is already set + auto set_cursor_if_necessary{[&](emscripten_browser_cursor_internal::cursor new_cursor) { + if (new_cursor == current_cursor) return; // don't do anything if the current cursor is already set current_cursor = new_cursor; emscripten_browser_cursor_internal::set(new_cursor); }}; - if(ImGui::GetIO().WantCaptureMouse) { // mouse is hovering over the gui - if(!cursor_to_restore && emscripten_browser_cursor_internal::is_set()) { + if (ImGui::GetIO().WantCaptureMouse) // mouse is hovering over the gui + { + if (!cursor_to_restore && emscripten_browser_cursor_internal::is_set()) + { cursor_to_restore = emscripten_browser_cursor_internal::get_string(); // back up the existing cursor when entering the imgui capture space } - switch(ImGui::GetMouseCursor()) { + switch (ImGui::GetMouseCursor()) + { case ImGuiMouseCursor_Arrow: set_cursor_if_necessary(emscripten_browser_cursor_internal::cursor::cursor_default); break; @@ -351,8 +365,11 @@ void update_cursor() { set_cursor_if_necessary(emscripten_browser_cursor_internal::cursor::not_allowed); break; } - } else { // mouse is away from the gui, hovering over some other part of the viewport - if(cursor_to_restore) { + } + else // mouse is away from the gui, hovering over some other part of the viewport + { + if (cursor_to_restore) + { emscripten_browser_cursor_internal::set(*cursor_to_restore); // restore the previous cursor when leaving the imgui capture space cursor_to_restore = std::nullopt; current_cursor = emscripten_browser_cursor_internal::cursor::invalid; // select an unused value for current cursor to force a set next time set_cursor_if_necessary is called @@ -362,7 +379,8 @@ void update_cursor() { } // anonymous namespace -void ImGui_ImplEmscripten_Init() { +void ImGui_ImplEmscripten_Init() +{ // Initialise the Emscripten backend, setting input callbacks auto &imgui_io{ImGui::GetIO()}; imgui_io.BackendPlatformName = "imgui_impl_emscripten"; @@ -376,7 +394,7 @@ void ImGui_ImplEmscripten_Init() { EMSCRIPTEN_EVENT_TARGET_WINDOW, // target nullptr, // userData false, // useCapture - [](int /*event_type*/, EmscriptenMouseEvent const *mouse_event, void */*data*/){ // callback, event_type == EMSCRIPTEN_EVENT_MOUSEMOVE + [](int /*event_type*/, EmscriptenMouseEvent const *mouse_event, void */*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_MOUSEMOVE ImGui::GetIO().AddMousePosEvent( static_cast(mouse_event->clientX), static_cast(mouse_event->clientY) @@ -388,7 +406,7 @@ void ImGui_ImplEmscripten_Init() { EMSCRIPTEN_EVENT_TARGET_WINDOW, // target nullptr, // userData false, // useCapture - [](int /*event_type*/, EmscriptenMouseEvent const *mouse_event, void */*data*/){ // callback, event_type == EMSCRIPTEN_EVENT_MOUSEDOWN + [](int /*event_type*/, EmscriptenMouseEvent const *mouse_event, void */*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_MOUSEDOWN ImGui::GetIO().AddMouseButtonEvent(translate_mousebutton(mouse_event->button), true); // translated button, down return true; // the event was consumed } @@ -397,7 +415,7 @@ void ImGui_ImplEmscripten_Init() { EMSCRIPTEN_EVENT_TARGET_WINDOW, // target nullptr, // userData false, // useCapture - [](int /*event_type*/, EmscriptenMouseEvent const *mouse_event, void */*data*/){ // callback, event_type == EMSCRIPTEN_EVENT_MOUSEUP + [](int /*event_type*/, EmscriptenMouseEvent const *mouse_event, void */*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_MOUSEUP ImGui::GetIO().AddMouseButtonEvent(translate_mousebutton(mouse_event->button), false); // translated button, up return true; // the event was consumed } @@ -406,7 +424,7 @@ void ImGui_ImplEmscripten_Init() { EMSCRIPTEN_EVENT_TARGET_DOCUMENT, // target - WINDOW doesn't produce mouseenter events nullptr, // userData false, // useCapture - [](int /*event_type*/, EmscriptenMouseEvent const *mouse_event, void */*data*/){ // callback, event_type == EMSCRIPTEN_EVENT_MOUSEENTER + [](int /*event_type*/, EmscriptenMouseEvent const *mouse_event, void */*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_MOUSEENTER ImGui::GetIO().AddMousePosEvent( static_cast(mouse_event->clientX), static_cast(mouse_event->clientY) @@ -418,7 +436,7 @@ void ImGui_ImplEmscripten_Init() { EMSCRIPTEN_EVENT_TARGET_DOCUMENT, // target - WINDOW doesn't produce mouseenter events nullptr, // userData false, // useCapture - [](int /*event_type*/, EmscriptenMouseEvent const */*mouse_event*/, void */*data*/){ // callback, event_type == EMSCRIPTEN_EVENT_MOUSELEAVE + [](int /*event_type*/, EmscriptenMouseEvent const */*mouse_event*/, void */*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_MOUSELEAVE auto &imgui_io{ImGui::GetIO()}; imgui_io.AddMousePosEvent(-FLT_MAX, -FLT_MAX); // cursor is not in the window imgui_io.ClearInputKeys(); // clear pending input keys on mouse exit @@ -429,9 +447,10 @@ void ImGui_ImplEmscripten_Init() { EMSCRIPTEN_EVENT_TARGET_WINDOW, // target nullptr, // userData false, // useCapture - [](int /*event_type*/, EmscriptenWheelEvent const *wheel_event, void */*data*/){ // callback, event_type == EMSCRIPTEN_EVENT_WHEEL + [](int /*event_type*/, EmscriptenWheelEvent const *wheel_event, void */*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_WHEEL float scale{1.0f}; - switch(wheel_event->deltaMode) { + switch (wheel_event->deltaMode) + { case DOM_DELTA_PIXEL: // scrolling in pixels scale = 1.0f / 100.0f; break; @@ -454,11 +473,12 @@ void ImGui_ImplEmscripten_Init() { EMSCRIPTEN_EVENT_TARGET_WINDOW, // target nullptr, // userData false, // useCapture - [](int /*event_type*/, EmscriptenKeyboardEvent const *key_event, void */*data*/){ // callback, event_type == EMSCRIPTEN_EVENT_KEYDOWN + [](int /*event_type*/, EmscriptenKeyboardEvent const *key_event, void */*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_KEYDOWN auto const key{translate_key(key_event->code)}; auto &imgui_io{ImGui::GetIO()}; imgui_io.AddKeyEvent(key, true); - switch(key) { // special cases for certain key events + switch (key) // special cases for certain key events + { case ImGuiKey_LeftCtrl: // additional events for modifier keys case ImGuiKey_RightCtrl: imgui_io.AddKeyEvent(ImGuiMod_Ctrl, true); @@ -491,11 +511,12 @@ void ImGui_ImplEmscripten_Init() { EMSCRIPTEN_EVENT_TARGET_WINDOW, // target nullptr, // userData false, // useCapture - [](int /*event_type*/, EmscriptenKeyboardEvent const *key_event, void */*data*/){ // callback, event_type == EMSCRIPTEN_EVENT_KEYUP + [](int /*event_type*/, EmscriptenKeyboardEvent const *key_event, void */*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_KEYUP auto const key{translate_key(key_event->code)}; auto &imgui_io{ImGui::GetIO()}; imgui_io.AddKeyEvent(key, false); - switch(key) { // special cases for certain key events + switch (key) // special cases for certain key events + { case ImGuiKey_LeftCtrl: // additional events for modifier keys case ImGuiKey_RightCtrl: imgui_io.AddKeyEvent(ImGuiMod_Ctrl, false); @@ -522,7 +543,7 @@ void ImGui_ImplEmscripten_Init() { EMSCRIPTEN_EVENT_TARGET_WINDOW, // target nullptr, // userData false, // useCapture - [](int /*event_type*/, EmscriptenKeyboardEvent const *key_event, void */*data*/){ // callback, event_type == EMSCRIPTEN_EVENT_KEYPRESS + [](int /*event_type*/, EmscriptenKeyboardEvent const *key_event, void */*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_KEYPRESS auto &imgui_io{ImGui::GetIO()}; imgui_io.AddInputCharactersUTF8(key_event->key); return imgui_io.WantCaptureKeyboard; // the event was consumed only if imgui wants to capture the keyboard @@ -587,7 +608,8 @@ void ImGui_ImplEmscripten_Init() { // TODO: touch events } -void ImGui_ImplEmscripten_Shutdown() { +void ImGui_ImplEmscripten_Shutdown() +{ // Unset any callbacks set by Init emscripten_set_mousemove_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr); emscripten_set_mousedown_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr); @@ -609,7 +631,8 @@ void ImGui_ImplEmscripten_Shutdown() { imgui_io.BackendFlags &= ~ImGuiBackendFlags_HasMouseCursors; } -void ImGui_ImplEmscripten_NewFrame() { +void ImGui_ImplEmscripten_NewFrame() +{ // Update any state that needs to be polled update_cursor(); } From c20a89c055c34982968794160d45f402939fc863 Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Tue, 31 Mar 2026 15:43:19 +0100 Subject: [PATCH 12/72] comment on browser cursor helpers, and correct closing namespace comment --- backends/imgui_impl_emscripten.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index a8ecf8e83..d1fe0127d 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -187,7 +187,7 @@ constexpr ImGuiMouseButton translate_mousebutton(unsigned short emscripten_butto namespace emscripten_browser_cursor_internal { -// Browser cursor helpers +// Browser cursor helpers, adapted from https://github.com/Armchair-Software/emscripten-browser-cursor enum class cursor { @@ -312,7 +312,7 @@ void set(std::string const &new_cursor) }, new_cursor.c_str()); } -} // namespace emscripten_browser_cursor +} // namespace emscripten_browser_cursor_internal namespace { From 6ade9da529f7d7d1abdaa1286162588f1c0f84bb Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Tue, 31 Mar 2026 15:46:53 +0100 Subject: [PATCH 13/72] remove use of auto and style consistency adjustments --- backends/imgui_impl_emscripten.cpp | 128 +++++++++++++++-------------- 1 file changed, 65 insertions(+), 63 deletions(-) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index d1fe0127d..1934fcaf9 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -165,7 +165,8 @@ ImGuiKey translate_key(char const* emscripten_key) __attribute__((__const__)); ImGuiKey translate_key(char const* emscripten_key) { // Translate an emscripten-provided browser string describing a keycode to an imgui key code - if (auto it{key_translate_lookup.find(emscripten_key)}; it != key_translate_lookup.end()) + const std::unordered_map::const_iterator it{key_translate_lookup.find(emscripten_key)}; + if (it != key_translate_lookup.end()) { return it->second; } @@ -259,7 +260,7 @@ bool is_set() std::string get_string() { // Return the current cursor setting as a string - auto cursor_str_ptr{reinterpret_cast(EM_ASM_PTR( + char* cursor_str_ptr{reinterpret_cast(EM_ASM_PTR( return stringToNewUTF8(document.body.style.cursor); ))}; std::string const cursor_str{cursor_str_ptr}; @@ -316,18 +317,19 @@ void set(std::string const &new_cursor) namespace { +void set_cursor_if_necessary(emscripten_browser_cursor_internal::cursor& current_cursor, emscripten_browser_cursor_internal::cursor new_cursor) +{ + if (new_cursor == current_cursor) return; // don't do anything if the current cursor is already set + current_cursor = new_cursor; + emscripten_browser_cursor_internal::set(new_cursor); +} + void update_cursor() { // Sync any cursor changes due to ImGui to the browser's cursor static emscripten_browser_cursor_internal::cursor current_cursor{emscripten_browser_cursor_internal::cursor::invalid}; static std::optional cursor_to_restore; - auto set_cursor_if_necessary{[&](emscripten_browser_cursor_internal::cursor new_cursor) { - if (new_cursor == current_cursor) return; // don't do anything if the current cursor is already set - current_cursor = new_cursor; - emscripten_browser_cursor_internal::set(new_cursor); - }}; - if (ImGui::GetIO().WantCaptureMouse) // mouse is hovering over the gui { if (!cursor_to_restore && emscripten_browser_cursor_internal::is_set()) @@ -338,31 +340,31 @@ void update_cursor() switch (ImGui::GetMouseCursor()) { case ImGuiMouseCursor_Arrow: - set_cursor_if_necessary(emscripten_browser_cursor_internal::cursor::cursor_default); + set_cursor_if_necessary(current_cursor, emscripten_browser_cursor_internal::cursor::cursor_default); break; case ImGuiMouseCursor_TextInput: // When hovering over InputText, etc. - set_cursor_if_necessary(emscripten_browser_cursor_internal::cursor::text); + set_cursor_if_necessary(current_cursor, emscripten_browser_cursor_internal::cursor::text); break; case ImGuiMouseCursor_ResizeAll: // (Unused by Dear ImGui functions) - set_cursor_if_necessary(emscripten_browser_cursor_internal::cursor::move); + set_cursor_if_necessary(current_cursor, emscripten_browser_cursor_internal::cursor::move); break; case ImGuiMouseCursor_ResizeNS: // When hovering over a horizontal border - set_cursor_if_necessary(emscripten_browser_cursor_internal::cursor::ns_resize); + set_cursor_if_necessary(current_cursor, emscripten_browser_cursor_internal::cursor::ns_resize); break; case ImGuiMouseCursor_ResizeEW: // When hovering over a vertical border or a column - set_cursor_if_necessary(emscripten_browser_cursor_internal::cursor::ew_resize); + set_cursor_if_necessary(current_cursor, emscripten_browser_cursor_internal::cursor::ew_resize); break; case ImGuiMouseCursor_ResizeNESW: // When hovering over the bottom-left corner of a window - set_cursor_if_necessary(emscripten_browser_cursor_internal::cursor::nesw_resize); + set_cursor_if_necessary(current_cursor, emscripten_browser_cursor_internal::cursor::nesw_resize); break; case ImGuiMouseCursor_ResizeNWSE: // When hovering over the bottom-right corner of a window - set_cursor_if_necessary(emscripten_browser_cursor_internal::cursor::nwse_resize); + set_cursor_if_necessary(current_cursor, emscripten_browser_cursor_internal::cursor::nwse_resize); break; case ImGuiMouseCursor_Hand: // (Unused by Dear ImGui functions. Use for e.g. hyperlinks) - set_cursor_if_necessary(emscripten_browser_cursor_internal::cursor::pointer); + set_cursor_if_necessary(current_cursor, emscripten_browser_cursor_internal::cursor::pointer); break; case ImGuiMouseCursor_NotAllowed: // When hovering something with disallowed interaction. Usually a crossed circle. - set_cursor_if_necessary(emscripten_browser_cursor_internal::cursor::not_allowed); + set_cursor_if_necessary(current_cursor, emscripten_browser_cursor_internal::cursor::not_allowed); break; } } @@ -382,13 +384,13 @@ void update_cursor() void ImGui_ImplEmscripten_Init() { // Initialise the Emscripten backend, setting input callbacks - auto &imgui_io{ImGui::GetIO()}; - imgui_io.BackendPlatformName = "imgui_impl_emscripten"; - imgui_io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; + ImGuiIO& io{ImGui::GetIO()}; + io.BackendPlatformName = "imgui_impl_emscripten"; + io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // set up initial display size values - imgui_io.DisplaySize.x = emscripten::val::global("window")["innerWidth"].as(); - imgui_io.DisplaySize.y = emscripten::val::global("window")["innerHeight"].as(); + io.DisplaySize.x = emscripten::val::global("window")["innerWidth"].as(); + io.DisplaySize.y = emscripten::val::global("window")["innerHeight"].as(); emscripten_set_mousemove_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, // target @@ -437,9 +439,9 @@ void ImGui_ImplEmscripten_Init() nullptr, // userData false, // useCapture [](int /*event_type*/, EmscriptenMouseEvent const */*mouse_event*/, void */*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_MOUSELEAVE - auto &imgui_io{ImGui::GetIO()}; - imgui_io.AddMousePosEvent(-FLT_MAX, -FLT_MAX); // cursor is not in the window - imgui_io.ClearInputKeys(); // clear pending input keys on mouse exit + ImGuiIO& io{ImGui::GetIO()}; + io.AddMousePosEvent(-FLT_MAX, -FLT_MAX); // cursor is not in the window + io.ClearInputKeys(); // clear pending input keys on mouse exit return true; // the event was consumed } ); @@ -474,33 +476,33 @@ void ImGui_ImplEmscripten_Init() nullptr, // userData false, // useCapture [](int /*event_type*/, EmscriptenKeyboardEvent const *key_event, void */*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_KEYDOWN - auto const key{translate_key(key_event->code)}; - auto &imgui_io{ImGui::GetIO()}; - imgui_io.AddKeyEvent(key, true); + const ImGuiKey key{translate_key(key_event->code)}; + ImGuiIO& io{ImGui::GetIO()}; + io.AddKeyEvent(key, true); switch (key) // special cases for certain key events { case ImGuiKey_LeftCtrl: // additional events for modifier keys case ImGuiKey_RightCtrl: - imgui_io.AddKeyEvent(ImGuiMod_Ctrl, true); + io.AddKeyEvent(ImGuiMod_Ctrl, true); break; case ImGuiKey_LeftShift: case ImGuiKey_RightShift: - imgui_io.AddKeyEvent(ImGuiMod_Shift, true); + io.AddKeyEvent(ImGuiMod_Shift, true); break; case ImGuiKey_LeftAlt: case ImGuiKey_RightAlt: - imgui_io.AddKeyEvent(ImGuiMod_Alt, true); + io.AddKeyEvent(ImGuiMod_Alt, true); break; case ImGuiKey_LeftSuper: case ImGuiKey_RightSuper: - imgui_io.AddKeyEvent(ImGuiMod_Super, true); + io.AddKeyEvent(ImGuiMod_Super, true); break; // TODO: case ImGuiKey_Menu: do we want to do anything with this? case ImGuiKey_Tab: // consuming tab prevents the user tabbing to other parts of the browser interface outside the window content - return imgui_io.WantCaptureKeyboard; // the event was consumed only if imgui wants to capture the keyboard + return io.WantCaptureKeyboard; // the event was consumed only if imgui wants to capture the keyboard case ImGuiKey_Enter: // consuming enter prevents the word "Enter" appearing in text input via the keypress callback case ImGuiKey_Delete: // consuming enter prevents the word "Delete" appearing in text input via the keypress callback - return imgui_io.WantTextInput; // the event was consumed only if we're currently accepting text input + return io.WantTextInput; // the event was consumed only if we're currently accepting text input default: break; } @@ -512,26 +514,26 @@ void ImGui_ImplEmscripten_Init() nullptr, // userData false, // useCapture [](int /*event_type*/, EmscriptenKeyboardEvent const *key_event, void */*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_KEYUP - auto const key{translate_key(key_event->code)}; - auto &imgui_io{ImGui::GetIO()}; - imgui_io.AddKeyEvent(key, false); + const ImGuiKey key{translate_key(key_event->code)}; + ImGuiIO& io{ImGui::GetIO()}; + io.AddKeyEvent(key, false); switch (key) // special cases for certain key events { case ImGuiKey_LeftCtrl: // additional events for modifier keys case ImGuiKey_RightCtrl: - imgui_io.AddKeyEvent(ImGuiMod_Ctrl, false); + io.AddKeyEvent(ImGuiMod_Ctrl, false); break; case ImGuiKey_LeftShift: case ImGuiKey_RightShift: - imgui_io.AddKeyEvent(ImGuiMod_Shift, false); + io.AddKeyEvent(ImGuiMod_Shift, false); break; case ImGuiKey_LeftAlt: case ImGuiKey_RightAlt: - imgui_io.AddKeyEvent(ImGuiMod_Alt, false); + io.AddKeyEvent(ImGuiMod_Alt, false); break; case ImGuiKey_LeftSuper: case ImGuiKey_RightSuper: - imgui_io.AddKeyEvent(ImGuiMod_Super, false); + io.AddKeyEvent(ImGuiMod_Super, false); break; default: break; @@ -544,9 +546,9 @@ void ImGui_ImplEmscripten_Init() nullptr, // userData false, // useCapture [](int /*event_type*/, EmscriptenKeyboardEvent const *key_event, void */*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_KEYPRESS - auto &imgui_io{ImGui::GetIO()}; - imgui_io.AddInputCharactersUTF8(key_event->key); - return imgui_io.WantCaptureKeyboard; // the event was consumed only if imgui wants to capture the keyboard + ImGuiIO& io{ImGui::GetIO()}; + io.AddInputCharactersUTF8(key_event->key); + return io.WantCaptureKeyboard; // the event was consumed only if imgui wants to capture the keyboard } ); emscripten_set_resize_callback( @@ -554,9 +556,9 @@ void ImGui_ImplEmscripten_Init() nullptr, // userData false, // useCapture [](int /*event_type*/, EmscriptenUiEvent const *event, void */*data*/) { // event_type == EMSCRIPTEN_EVENT_RESIZE - auto &imgui_io{ImGui::GetIO()}; - imgui_io.DisplaySize.x = static_cast(event->windowInnerWidth); - imgui_io.DisplaySize.y = static_cast(event->windowInnerHeight); + ImGuiIO& io{ImGui::GetIO()}; + io.DisplaySize.x = static_cast(event->windowInnerWidth); + io.DisplaySize.y = static_cast(event->windowInnerHeight); return true; // the event was consumed } ); @@ -565,9 +567,9 @@ void ImGui_ImplEmscripten_Init() nullptr, // userData false, // useCapture [](int /*event_type*/, EmscriptenFocusEvent const */*event*/, void */*data*/) { // event_type == EMSCRIPTEN_EVENT_BLUR - auto &imgui_io{ImGui::GetIO()}; - imgui_io.AddFocusEvent(false); - imgui_io.ClearInputKeys(); // clear pending input keys on focus gain + ImGuiIO& io{ImGui::GetIO()}; + io.AddFocusEvent(false); + io.ClearInputKeys(); // clear pending input keys on focus gain return true; // the event was consumed } ); @@ -576,9 +578,9 @@ void ImGui_ImplEmscripten_Init() nullptr, // userData false, // useCapture [](int /*event_type*/, EmscriptenFocusEvent const */*event*/, void */*data*/) { // event_type == EMSCRIPTEN_EVENT_FOCUS - auto &imgui_io{ImGui::GetIO()}; - imgui_io.AddFocusEvent(true); - imgui_io.ClearInputKeys(); // clear pending input keys on focus loss - for example if you press tab to cycle to another part of the UI + ImGuiIO& io{ImGui::GetIO()}; + io.AddFocusEvent(true); + io.ClearInputKeys(); // clear pending input keys on focus loss - for example if you press tab to cycle to another part of the UI return true; // the event was consumed } ); @@ -587,9 +589,9 @@ void ImGui_ImplEmscripten_Init() nullptr, // userData false, // useCapture [](int /*event_type*/, EmscriptenFocusEvent const */*event*/, void */*data*/) { // event_type == EMSCRIPTEN_EVENT_FOCUSIN - auto &imgui_io{ImGui::GetIO()}; - imgui_io.AddFocusEvent(true); - imgui_io.ClearInputKeys(); // clear pending input keys on focus gain + ImGuiIO& io{ImGui::GetIO()}; + io.AddFocusEvent(true); + io.ClearInputKeys(); // clear pending input keys on focus gain return true; // the event was consumed } ); @@ -598,9 +600,9 @@ void ImGui_ImplEmscripten_Init() nullptr, // userData false, // useCapture [](int /*event_type*/, EmscriptenFocusEvent const */*event*/, void */*data*/) { // event_type == EMSCRIPTEN_EVENT_FOCUSOUT - auto &imgui_io{ImGui::GetIO()}; - imgui_io.AddFocusEvent(false); - imgui_io.ClearInputKeys(); // clear pending input keys on focus loss - for example if you press tab to cycle to another part of the UI + ImGuiIO& io{ImGui::GetIO()}; + io.AddFocusEvent(false); + io.ClearInputKeys(); // clear pending input keys on focus loss - for example if you press tab to cycle to another part of the UI return true; // the event was consumed } ); @@ -625,10 +627,10 @@ void ImGui_ImplEmscripten_Shutdown() emscripten_set_focusout_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr); // TODO: touch events - auto &imgui_io{ImGui::GetIO()}; - imgui_io.BackendPlatformName = nullptr; - imgui_io.BackendPlatformUserData = nullptr; - imgui_io.BackendFlags &= ~ImGuiBackendFlags_HasMouseCursors; + ImGuiIO& io{ImGui::GetIO()}; + io.BackendPlatformName = nullptr; + io.BackendPlatformUserData = nullptr; + io.BackendFlags &= ~ImGuiBackendFlags_HasMouseCursors; } void ImGui_ImplEmscripten_NewFrame() From ff4341d3d6105eb03b80287e6341bb5554543071 Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Tue, 31 Mar 2026 15:47:18 +0100 Subject: [PATCH 14/72] pointer and reference alignment consistent with imgui norms --- backends/imgui_impl_emscripten.cpp | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index 1934fcaf9..d9b05bd8c 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -305,7 +305,7 @@ void set(cursor new_cursor) } } -void set(std::string const &new_cursor) +void set(std::string const& new_cursor) { // Set the cursor from an arbitrary string EM_ASM({ @@ -396,7 +396,7 @@ void ImGui_ImplEmscripten_Init() EMSCRIPTEN_EVENT_TARGET_WINDOW, // target nullptr, // userData false, // useCapture - [](int /*event_type*/, EmscriptenMouseEvent const *mouse_event, void */*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_MOUSEMOVE + [](int /*event_type*/, EmscriptenMouseEvent const* mouse_event, void* /*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_MOUSEMOVE ImGui::GetIO().AddMousePosEvent( static_cast(mouse_event->clientX), static_cast(mouse_event->clientY) @@ -408,7 +408,7 @@ void ImGui_ImplEmscripten_Init() EMSCRIPTEN_EVENT_TARGET_WINDOW, // target nullptr, // userData false, // useCapture - [](int /*event_type*/, EmscriptenMouseEvent const *mouse_event, void */*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_MOUSEDOWN + [](int /*event_type*/, EmscriptenMouseEvent const* mouse_event, void* /*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_MOUSEDOWN ImGui::GetIO().AddMouseButtonEvent(translate_mousebutton(mouse_event->button), true); // translated button, down return true; // the event was consumed } @@ -417,7 +417,7 @@ void ImGui_ImplEmscripten_Init() EMSCRIPTEN_EVENT_TARGET_WINDOW, // target nullptr, // userData false, // useCapture - [](int /*event_type*/, EmscriptenMouseEvent const *mouse_event, void */*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_MOUSEUP + [](int /*event_type*/, EmscriptenMouseEvent const* mouse_event, void* /*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_MOUSEUP ImGui::GetIO().AddMouseButtonEvent(translate_mousebutton(mouse_event->button), false); // translated button, up return true; // the event was consumed } @@ -426,7 +426,7 @@ void ImGui_ImplEmscripten_Init() EMSCRIPTEN_EVENT_TARGET_DOCUMENT, // target - WINDOW doesn't produce mouseenter events nullptr, // userData false, // useCapture - [](int /*event_type*/, EmscriptenMouseEvent const *mouse_event, void */*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_MOUSEENTER + [](int /*event_type*/, EmscriptenMouseEvent const* mouse_event, void* /*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_MOUSEENTER ImGui::GetIO().AddMousePosEvent( static_cast(mouse_event->clientX), static_cast(mouse_event->clientY) @@ -438,7 +438,7 @@ void ImGui_ImplEmscripten_Init() EMSCRIPTEN_EVENT_TARGET_DOCUMENT, // target - WINDOW doesn't produce mouseenter events nullptr, // userData false, // useCapture - [](int /*event_type*/, EmscriptenMouseEvent const */*mouse_event*/, void */*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_MOUSELEAVE + [](int /*event_type*/, EmscriptenMouseEvent const* /*mouse_event*/, void* /*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_MOUSELEAVE ImGuiIO& io{ImGui::GetIO()}; io.AddMousePosEvent(-FLT_MAX, -FLT_MAX); // cursor is not in the window io.ClearInputKeys(); // clear pending input keys on mouse exit @@ -449,7 +449,7 @@ void ImGui_ImplEmscripten_Init() EMSCRIPTEN_EVENT_TARGET_WINDOW, // target nullptr, // userData false, // useCapture - [](int /*event_type*/, EmscriptenWheelEvent const *wheel_event, void */*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_WHEEL + [](int /*event_type*/, EmscriptenWheelEvent const* wheel_event, void* /*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_WHEEL float scale{1.0f}; switch (wheel_event->deltaMode) { @@ -475,7 +475,7 @@ void ImGui_ImplEmscripten_Init() EMSCRIPTEN_EVENT_TARGET_WINDOW, // target nullptr, // userData false, // useCapture - [](int /*event_type*/, EmscriptenKeyboardEvent const *key_event, void */*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_KEYDOWN + [](int /*event_type*/, EmscriptenKeyboardEvent const* key_event, void* /*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_KEYDOWN const ImGuiKey key{translate_key(key_event->code)}; ImGuiIO& io{ImGui::GetIO()}; io.AddKeyEvent(key, true); @@ -513,7 +513,7 @@ void ImGui_ImplEmscripten_Init() EMSCRIPTEN_EVENT_TARGET_WINDOW, // target nullptr, // userData false, // useCapture - [](int /*event_type*/, EmscriptenKeyboardEvent const *key_event, void */*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_KEYUP + [](int /*event_type*/, EmscriptenKeyboardEvent const* key_event, void* /*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_KEYUP const ImGuiKey key{translate_key(key_event->code)}; ImGuiIO& io{ImGui::GetIO()}; io.AddKeyEvent(key, false); @@ -545,7 +545,7 @@ void ImGui_ImplEmscripten_Init() EMSCRIPTEN_EVENT_TARGET_WINDOW, // target nullptr, // userData false, // useCapture - [](int /*event_type*/, EmscriptenKeyboardEvent const *key_event, void */*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_KEYPRESS + [](int /*event_type*/, EmscriptenKeyboardEvent const* key_event, void* /*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_KEYPRESS ImGuiIO& io{ImGui::GetIO()}; io.AddInputCharactersUTF8(key_event->key); return io.WantCaptureKeyboard; // the event was consumed only if imgui wants to capture the keyboard @@ -555,7 +555,7 @@ void ImGui_ImplEmscripten_Init() EMSCRIPTEN_EVENT_TARGET_WINDOW, // target nullptr, // userData false, // useCapture - [](int /*event_type*/, EmscriptenUiEvent const *event, void */*data*/) { // event_type == EMSCRIPTEN_EVENT_RESIZE + [](int /*event_type*/, EmscriptenUiEvent const* event, void* /*data*/) { // event_type == EMSCRIPTEN_EVENT_RESIZE ImGuiIO& io{ImGui::GetIO()}; io.DisplaySize.x = static_cast(event->windowInnerWidth); io.DisplaySize.y = static_cast(event->windowInnerHeight); @@ -566,7 +566,7 @@ void ImGui_ImplEmscripten_Init() EMSCRIPTEN_EVENT_TARGET_WINDOW, // target nullptr, // userData false, // useCapture - [](int /*event_type*/, EmscriptenFocusEvent const */*event*/, void */*data*/) { // event_type == EMSCRIPTEN_EVENT_BLUR + [](int /*event_type*/, EmscriptenFocusEvent const* /*event*/, void* /*data*/) { // event_type == EMSCRIPTEN_EVENT_BLUR ImGuiIO& io{ImGui::GetIO()}; io.AddFocusEvent(false); io.ClearInputKeys(); // clear pending input keys on focus gain @@ -577,7 +577,7 @@ void ImGui_ImplEmscripten_Init() EMSCRIPTEN_EVENT_TARGET_WINDOW, // target nullptr, // userData false, // useCapture - [](int /*event_type*/, EmscriptenFocusEvent const */*event*/, void */*data*/) { // event_type == EMSCRIPTEN_EVENT_FOCUS + [](int /*event_type*/, EmscriptenFocusEvent const* /*event*/, void* /*data*/) { // event_type == EMSCRIPTEN_EVENT_FOCUS ImGuiIO& io{ImGui::GetIO()}; io.AddFocusEvent(true); io.ClearInputKeys(); // clear pending input keys on focus loss - for example if you press tab to cycle to another part of the UI @@ -588,7 +588,7 @@ void ImGui_ImplEmscripten_Init() EMSCRIPTEN_EVENT_TARGET_WINDOW, // target nullptr, // userData false, // useCapture - [](int /*event_type*/, EmscriptenFocusEvent const */*event*/, void */*data*/) { // event_type == EMSCRIPTEN_EVENT_FOCUSIN + [](int /*event_type*/, EmscriptenFocusEvent const* /*event*/, void* /*data*/) { // event_type == EMSCRIPTEN_EVENT_FOCUSIN ImGuiIO& io{ImGui::GetIO()}; io.AddFocusEvent(true); io.ClearInputKeys(); // clear pending input keys on focus gain @@ -599,7 +599,7 @@ void ImGui_ImplEmscripten_Init() EMSCRIPTEN_EVENT_TARGET_WINDOW, // target nullptr, // userData false, // useCapture - [](int /*event_type*/, EmscriptenFocusEvent const */*event*/, void */*data*/) { // event_type == EMSCRIPTEN_EVENT_FOCUSOUT + [](int /*event_type*/, EmscriptenFocusEvent const* /*event*/, void* /*data*/) { // event_type == EMSCRIPTEN_EVENT_FOCUSOUT ImGuiIO& io{ImGui::GetIO()}; io.AddFocusEvent(false); io.ClearInputKeys(); // clear pending input keys on focus loss - for example if you press tab to cycle to another part of the UI From 6d6d7d5f0fa3370b2c3afbf64338cc5c5d81966e Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Tue, 31 Mar 2026 16:08:35 +0100 Subject: [PATCH 15/72] store persistent state in backend data as per imgui norms --- backends/imgui_impl_emscripten.cpp | 65 +++++++++++++++++++++--------- 1 file changed, 45 insertions(+), 20 deletions(-) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index d9b05bd8c..db25ec8a9 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -317,6 +317,23 @@ void set(std::string const& new_cursor) namespace { +struct ImGui_ImplEmscripten_Data +{ + emscripten_browser_cursor_internal::cursor CurrentCursor; + std::optional CursorToRestore; + + ImGui_ImplEmscripten_Data() + : CurrentCursor{emscripten_browser_cursor_internal::cursor::invalid} + { + } +}; + +// Backend data stored in io.BackendPlatformUserData to allow support for multiple Dear ImGui contexts +ImGui_ImplEmscripten_Data* ImGui_ImplEmscripten_GetBackendData() +{ + return ImGui::GetCurrentContext() ? static_cast(ImGui::GetIO().BackendPlatformUserData) : nullptr; +} + void set_cursor_if_necessary(emscripten_browser_cursor_internal::cursor& current_cursor, emscripten_browser_cursor_internal::cursor new_cursor) { if (new_cursor == current_cursor) return; // don't do anything if the current cursor is already set @@ -324,57 +341,54 @@ void set_cursor_if_necessary(emscripten_browser_cursor_internal::cursor& current emscripten_browser_cursor_internal::set(new_cursor); } -void update_cursor() +void update_cursor(ImGui_ImplEmscripten_Data* bd) { // Sync any cursor changes due to ImGui to the browser's cursor - static emscripten_browser_cursor_internal::cursor current_cursor{emscripten_browser_cursor_internal::cursor::invalid}; - static std::optional cursor_to_restore; - if (ImGui::GetIO().WantCaptureMouse) // mouse is hovering over the gui { - if (!cursor_to_restore && emscripten_browser_cursor_internal::is_set()) + if (!bd->CursorToRestore && emscripten_browser_cursor_internal::is_set()) { - cursor_to_restore = emscripten_browser_cursor_internal::get_string(); // back up the existing cursor when entering the imgui capture space + bd->CursorToRestore = emscripten_browser_cursor_internal::get_string(); // back up the existing cursor when entering the imgui capture space } switch (ImGui::GetMouseCursor()) { case ImGuiMouseCursor_Arrow: - set_cursor_if_necessary(current_cursor, emscripten_browser_cursor_internal::cursor::cursor_default); + set_cursor_if_necessary(bd->CurrentCursor, emscripten_browser_cursor_internal::cursor::cursor_default); break; case ImGuiMouseCursor_TextInput: // When hovering over InputText, etc. - set_cursor_if_necessary(current_cursor, emscripten_browser_cursor_internal::cursor::text); + set_cursor_if_necessary(bd->CurrentCursor, emscripten_browser_cursor_internal::cursor::text); break; case ImGuiMouseCursor_ResizeAll: // (Unused by Dear ImGui functions) - set_cursor_if_necessary(current_cursor, emscripten_browser_cursor_internal::cursor::move); + set_cursor_if_necessary(bd->CurrentCursor, emscripten_browser_cursor_internal::cursor::move); break; case ImGuiMouseCursor_ResizeNS: // When hovering over a horizontal border - set_cursor_if_necessary(current_cursor, emscripten_browser_cursor_internal::cursor::ns_resize); + set_cursor_if_necessary(bd->CurrentCursor, emscripten_browser_cursor_internal::cursor::ns_resize); break; case ImGuiMouseCursor_ResizeEW: // When hovering over a vertical border or a column - set_cursor_if_necessary(current_cursor, emscripten_browser_cursor_internal::cursor::ew_resize); + set_cursor_if_necessary(bd->CurrentCursor, emscripten_browser_cursor_internal::cursor::ew_resize); break; case ImGuiMouseCursor_ResizeNESW: // When hovering over the bottom-left corner of a window - set_cursor_if_necessary(current_cursor, emscripten_browser_cursor_internal::cursor::nesw_resize); + set_cursor_if_necessary(bd->CurrentCursor, emscripten_browser_cursor_internal::cursor::nesw_resize); break; case ImGuiMouseCursor_ResizeNWSE: // When hovering over the bottom-right corner of a window - set_cursor_if_necessary(current_cursor, emscripten_browser_cursor_internal::cursor::nwse_resize); + set_cursor_if_necessary(bd->CurrentCursor, emscripten_browser_cursor_internal::cursor::nwse_resize); break; case ImGuiMouseCursor_Hand: // (Unused by Dear ImGui functions. Use for e.g. hyperlinks) - set_cursor_if_necessary(current_cursor, emscripten_browser_cursor_internal::cursor::pointer); + set_cursor_if_necessary(bd->CurrentCursor, emscripten_browser_cursor_internal::cursor::pointer); break; case ImGuiMouseCursor_NotAllowed: // When hovering something with disallowed interaction. Usually a crossed circle. - set_cursor_if_necessary(current_cursor, emscripten_browser_cursor_internal::cursor::not_allowed); + set_cursor_if_necessary(bd->CurrentCursor, emscripten_browser_cursor_internal::cursor::not_allowed); break; } } else // mouse is away from the gui, hovering over some other part of the viewport { - if (cursor_to_restore) + if (bd->CursorToRestore) { - emscripten_browser_cursor_internal::set(*cursor_to_restore); // restore the previous cursor when leaving the imgui capture space - cursor_to_restore = std::nullopt; - current_cursor = emscripten_browser_cursor_internal::cursor::invalid; // select an unused value for current cursor to force a set next time set_cursor_if_necessary is called + emscripten_browser_cursor_internal::set(*bd->CursorToRestore); // restore the previous cursor when leaving the imgui capture space + bd->CursorToRestore = std::nullopt; + bd->CurrentCursor = emscripten_browser_cursor_internal::cursor::invalid; // select an unused value for current cursor to force a set next time set_cursor_if_necessary is called } } } @@ -385,6 +399,10 @@ void ImGui_ImplEmscripten_Init() { // Initialise the Emscripten backend, setting input callbacks ImGuiIO& io{ImGui::GetIO()}; + IMGUI_CHECKVERSION(); + IM_ASSERT(io.BackendPlatformUserData == nullptr && "Already initialized a platform backend!"); + ImGui_ImplEmscripten_Data* bd{IM_NEW(ImGui_ImplEmscripten_Data)()}; + io.BackendPlatformUserData = static_cast(bd); io.BackendPlatformName = "imgui_impl_emscripten"; io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; @@ -612,6 +630,9 @@ void ImGui_ImplEmscripten_Init() void ImGui_ImplEmscripten_Shutdown() { + ImGui_ImplEmscripten_Data* bd{ImGui_ImplEmscripten_GetBackendData()}; + IM_ASSERT(bd != nullptr && "No platform backend to shutdown, or already shutdown?"); + // Unset any callbacks set by Init emscripten_set_mousemove_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr); emscripten_set_mousedown_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr); @@ -631,12 +652,16 @@ void ImGui_ImplEmscripten_Shutdown() io.BackendPlatformName = nullptr; io.BackendPlatformUserData = nullptr; io.BackendFlags &= ~ImGuiBackendFlags_HasMouseCursors; + IM_DELETE(bd); } void ImGui_ImplEmscripten_NewFrame() { + ImGui_ImplEmscripten_Data* bd{ImGui_ImplEmscripten_GetBackendData()}; + IM_ASSERT(bd != nullptr && "Context or backend not initialized? Did you call ImGui_ImplEmscripten_Init()?"); + // Update any state that needs to be polled - update_cursor(); + update_cursor(bd); } #endif // IMGUI_DISABLE From 58236a21f8a706e8ed343359eff27d20770c05a7 Mon Sep 17 00:00:00 2001 From: slowriot Date: Tue, 31 Mar 2026 16:25:03 +0100 Subject: [PATCH 16/72] comment typo Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- backends/imgui_impl_emscripten.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backends/imgui_impl_emscripten.h b/backends/imgui_impl_emscripten.h index 923d0def2..0b3e088e9 100644 --- a/backends/imgui_impl_emscripten.h +++ b/backends/imgui_impl_emscripten.h @@ -34,7 +34,7 @@ #pragma once #ifndef __EMSCRIPTEN__ -#error The imgui_impl_emscripten backend reqiuires Emscripten. +#error The imgui_impl_emscripten backend requires Emscripten. #endif // Initialise the Emscripten backend, setting input callbacks. This should be called after ImGui::CreateContext(); From 426bff6aed03bcaedc42468dfb3862ed7f35ac9f Mon Sep 17 00:00:00 2001 From: slowriot Date: Tue, 31 Mar 2026 16:25:57 +0100 Subject: [PATCH 17/72] update comments re. gamepads Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- backends/imgui_impl_emscripten.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backends/imgui_impl_emscripten.h b/backends/imgui_impl_emscripten.h index 0b3e088e9..aa6bee29f 100644 --- a/backends/imgui_impl_emscripten.h +++ b/backends/imgui_impl_emscripten.h @@ -45,6 +45,6 @@ void ImGui_ImplEmscripten_Init(); // Note also there is no obligation to ever call this, as there is not necessarily any such concept as "shutting down" when running in the browser, and we have no resources to release. The user can just close the tab. void ImGui_ImplEmscripten_Shutdown(); -// Call every frame to update polled input events, i.e. gamepads, and update imgui's cursors. -// If you aren't using gamepad input to control imgui, and you're not using browser native cursor rendering (i.e. if imgui is rendering cursors internally), you don't need to call this. +// Call every frame to synchronize Dear ImGui's cursor state with the browser's native cursors. +// If you are not using browser native cursor rendering (i.e. if Dear ImGui is rendering cursors internally), you don't need to call this. void ImGui_ImplEmscripten_NewFrame(); From 416d85c9e732d474f276d18c7b911067cc04786c Mon Sep 17 00:00:00 2001 From: slowriot Date: Tue, 31 Mar 2026 16:28:20 +0100 Subject: [PATCH 18/72] catch out of range buttons Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- backends/imgui_impl_emscripten.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index db25ec8a9..51a0c221d 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -179,7 +179,7 @@ constexpr ImGuiMouseButton translate_mousebutton(unsigned short emscripten_butto // Translate an emscripten-provided integer describing a mouse button to an imgui mouse button if (emscripten_button == 1) return ImGuiMouseButton_Middle; // 1 = middle mouse button if (emscripten_button == 2) return ImGuiMouseButton_Right; // 2 = right mouse button - if (emscripten_button > ImGuiMouseButton_COUNT) return ImGuiMouseButton_Middle; // treat any weird clicks on unexpected buttons (button 6 upwards) as middle mouse + if (emscripten_button >= ImGuiMouseButton_COUNT) return ImGuiMouseButton_Middle; // treat any weird clicks on unexpected buttons (button 6 upwards) as middle mouse return emscripten_button; // any other button translates 1:1 } From 72fae61cbaaac2cec6619d03e16667fe5a9d49f8 Mon Sep 17 00:00:00 2001 From: slowriot Date: Tue, 31 Mar 2026 16:29:41 +0100 Subject: [PATCH 19/72] comment adjustment re. gamepads & touch Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- backends/imgui_impl_emscripten.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backends/imgui_impl_emscripten.h b/backends/imgui_impl_emscripten.h index aa6bee29f..17a0669c6 100644 --- a/backends/imgui_impl_emscripten.h +++ b/backends/imgui_impl_emscripten.h @@ -3,7 +3,7 @@ // This is a platform back-end, similar to and offering an alternative to imgui_impl_glfw. // The intended use-case is for applications built with Emscripten, running in the browser, but *not* using GLFW. // It uses Emscripten's HTML5 interface to tie callbacks to imgui input, handling window resizing, -// focus, cursor, keyboard input, touch, gamepad devices etc. It does not attempt to handle rendering. +// focus, cursor, keyboard input, etc. It does not attempt to handle rendering. // // A note about GLFW on Emscripten: Emscripten includes its own GLFW implementation, which wraps browser HTML5 callbacks to provide the standard GLFW input interface. So there are two levels of indirection. // This backend removes the middleman for input, providing a more efficient direct interface between Emscripten's functionality and imgui input. From 41233b09b0ebeee7d01cf5a477be67e9250df228 Mon Sep 17 00:00:00 2001 From: slowriot Date: Tue, 31 Mar 2026 16:30:26 +0100 Subject: [PATCH 20/72] remove duplicate F6 Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- backends/imgui_impl_emscripten.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index 51a0c221d..5a203d5e0 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -147,7 +147,6 @@ static const std::unordered_map key_translate_lookup{ {"F4", ImGuiKey_F4}, {"F5", ImGuiKey_F5}, {"F6", ImGuiKey_F6}, - {"F6", ImGuiKey_F6}, {"F7", ImGuiKey_F7}, {"F8", ImGuiKey_F8}, {"F9", ImGuiKey_F9}, From 8e35b501a4542d5f2c9c6166d99cddcf439d0a0f Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Tue, 31 Mar 2026 16:33:50 +0100 Subject: [PATCH 21/72] fix comments on focus gain / loss --- backends/imgui_impl_emscripten.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index 5a203d5e0..e8a9f24bf 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -586,7 +586,7 @@ void ImGui_ImplEmscripten_Init() [](int /*event_type*/, EmscriptenFocusEvent const* /*event*/, void* /*data*/) { // event_type == EMSCRIPTEN_EVENT_BLUR ImGuiIO& io{ImGui::GetIO()}; io.AddFocusEvent(false); - io.ClearInputKeys(); // clear pending input keys on focus gain + io.ClearInputKeys(); // clear pending input keys on focus loss return true; // the event was consumed } ); @@ -597,7 +597,7 @@ void ImGui_ImplEmscripten_Init() [](int /*event_type*/, EmscriptenFocusEvent const* /*event*/, void* /*data*/) { // event_type == EMSCRIPTEN_EVENT_FOCUS ImGuiIO& io{ImGui::GetIO()}; io.AddFocusEvent(true); - io.ClearInputKeys(); // clear pending input keys on focus loss - for example if you press tab to cycle to another part of the UI + io.ClearInputKeys(); // clear pending input keys on focus gain - for example if you press tab to cycle back into the browser window return true; // the event was consumed } ); From bf1893272a95b118bb7dda9114380cfd59f19178 Mon Sep 17 00:00:00 2001 From: slowriot Date: Tue, 31 Mar 2026 16:45:11 +0100 Subject: [PATCH 22/72] add imgui include and use IMGUI_IMPL_API and honour IMGUI_DISABLE Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- backends/imgui_impl_emscripten.h | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/backends/imgui_impl_emscripten.h b/backends/imgui_impl_emscripten.h index 17a0669c6..d7f0b63e5 100644 --- a/backends/imgui_impl_emscripten.h +++ b/backends/imgui_impl_emscripten.h @@ -32,19 +32,24 @@ // Don't forget to set io.BackendFlags |= ImGuiBackendFlags_HasGamepad when a gamepad is connected. #pragma once +#include "imgui.h" + +#ifndef IMGUI_DISABLE #ifndef __EMSCRIPTEN__ -#error The imgui_impl_emscripten backend requires Emscripten. +#error The imgui_impl_emscripten backend reqiuires Emscripten. #endif // Initialise the Emscripten backend, setting input callbacks. This should be called after ImGui::CreateContext(); -void ImGui_ImplEmscripten_Init(); +IMGUI_IMPL_API void ImGui_ImplEmscripten_Init(); // Shut down the Emscripten backend. This unsets all Emscripten input callbacks set by Init. // Note it'll also unset any Emscripten input callbacks set elsewhere in the program! // Note also there is no obligation to ever call this, as there is not necessarily any such concept as "shutting down" when running in the browser, and we have no resources to release. The user can just close the tab. -void ImGui_ImplEmscripten_Shutdown(); +IMGUI_IMPL_API void ImGui_ImplEmscripten_Shutdown(); -// Call every frame to synchronize Dear ImGui's cursor state with the browser's native cursors. -// If you are not using browser native cursor rendering (i.e. if Dear ImGui is rendering cursors internally), you don't need to call this. -void ImGui_ImplEmscripten_NewFrame(); +// Call every frame to update polled input events, i.e. gamepads, and update imgui's cursors. +// If you aren't using gamepad input to control imgui, and you're not using browser native cursor rendering (i.e. if imgui is rendering cursors internally), you don't need to call this. +IMGUI_IMPL_API void ImGui_ImplEmscripten_NewFrame(); + +#endif // IMGUI_DISABLE From 0454ffec277bdd9a39c964e56490446df286c896 Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Tue, 31 Mar 2026 16:47:03 +0100 Subject: [PATCH 23/72] header comment fixes --- backends/imgui_impl_emscripten.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backends/imgui_impl_emscripten.h b/backends/imgui_impl_emscripten.h index d7f0b63e5..a0c1a7d7d 100644 --- a/backends/imgui_impl_emscripten.h +++ b/backends/imgui_impl_emscripten.h @@ -37,7 +37,7 @@ #ifndef IMGUI_DISABLE #ifndef __EMSCRIPTEN__ -#error The imgui_impl_emscripten backend reqiuires Emscripten. +#error The imgui_impl_emscripten backend requires Emscripten. #endif // Initialise the Emscripten backend, setting input callbacks. This should be called after ImGui::CreateContext(); @@ -48,8 +48,8 @@ IMGUI_IMPL_API void ImGui_ImplEmscripten_Init(); // Note also there is no obligation to ever call this, as there is not necessarily any such concept as "shutting down" when running in the browser, and we have no resources to release. The user can just close the tab. IMGUI_IMPL_API void ImGui_ImplEmscripten_Shutdown(); -// Call every frame to update polled input events, i.e. gamepads, and update imgui's cursors. -// If you aren't using gamepad input to control imgui, and you're not using browser native cursor rendering (i.e. if imgui is rendering cursors internally), you don't need to call this. +// Call every frame to synchronize Dear ImGui's cursor state with the browser's native cursors. +// If you are not using browser native cursor rendering (i.e. if Dear ImGui is rendering cursors internally), you don't need to call this. IMGUI_IMPL_API void ImGui_ImplEmscripten_NewFrame(); #endif // IMGUI_DISABLE From 57c52e75b22bb291853eb0a60ca78b1879d41961 Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Tue, 31 Mar 2026 16:55:05 +0100 Subject: [PATCH 24/72] restore cursor reliably even if previously unset --- backends/imgui_impl_emscripten.cpp | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index e8a9f24bf..6a94544fb 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -248,14 +248,6 @@ void unset(); // Browser cursor helper implementation -bool is_set() -{ - // Returns whether the cursor is currently set - return EM_ASM_INT( - return !(!document.body.style.cursor || document.body.style.cursor.length === 0 ); - ); -} - std::string get_string() { // Return the current cursor setting as a string @@ -273,6 +265,9 @@ void set(cursor new_cursor) // Note, implementations omitted for cursors not used by imgui. For full implementation, use https://github.com/Armchair-Software/emscripten-browser-cursor switch (new_cursor) { + case cursor::none: + EM_ASM(document.body.style.cursor = 'none';); + break; case cursor::cursor_default: default: EM_ASM(document.body.style.cursor = 'default';); @@ -345,13 +340,16 @@ void update_cursor(ImGui_ImplEmscripten_Data* bd) // Sync any cursor changes due to ImGui to the browser's cursor if (ImGui::GetIO().WantCaptureMouse) // mouse is hovering over the gui { - if (!bd->CursorToRestore && emscripten_browser_cursor_internal::is_set()) + if (!bd->CursorToRestore) { - bd->CursorToRestore = emscripten_browser_cursor_internal::get_string(); // back up the existing cursor when entering the imgui capture space + bd->CursorToRestore = emscripten_browser_cursor_internal::get_string(); // back up the existing cursor state when entering the imgui capture space } switch (ImGui::GetMouseCursor()) { + case ImGuiMouseCursor_None: + set_cursor_if_necessary(bd->CurrentCursor, emscripten_browser_cursor_internal::cursor::none); + break; case ImGuiMouseCursor_Arrow: set_cursor_if_necessary(bd->CurrentCursor, emscripten_browser_cursor_internal::cursor::cursor_default); break; @@ -385,7 +383,7 @@ void update_cursor(ImGui_ImplEmscripten_Data* bd) { if (bd->CursorToRestore) { - emscripten_browser_cursor_internal::set(*bd->CursorToRestore); // restore the previous cursor when leaving the imgui capture space + emscripten_browser_cursor_internal::set(*bd->CursorToRestore); // restore the previous cursor state when leaving the imgui capture space bd->CursorToRestore = std::nullopt; bd->CurrentCursor = emscripten_browser_cursor_internal::cursor::invalid; // select an unused value for current cursor to force a set next time set_cursor_if_necessary is called } From c96f9679b43e80f1d3066c1b0bc70ae2ee162020 Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Tue, 31 Mar 2026 17:01:02 +0100 Subject: [PATCH 25/72] unset blur and focus callbacks --- backends/imgui_impl_emscripten.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index 6a94544fb..d492ca696 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -641,6 +641,8 @@ void ImGui_ImplEmscripten_Shutdown() emscripten_set_keyup_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr); emscripten_set_keypress_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr); emscripten_set_resize_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr); + emscripten_set_blur_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr); + emscripten_set_focus_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr); emscripten_set_focusin_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr); emscripten_set_focusout_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr); // TODO: touch events From 00400b697f0ce2ec6c8d36e79c3a16ac541636e6 Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Tue, 31 Mar 2026 17:09:11 +0100 Subject: [PATCH 26/72] use attribute pure --- backends/imgui_impl_emscripten.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index d492ca696..ad0683e11 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -160,7 +160,7 @@ static const std::unordered_map key_translate_lookup{ {"Pause", ImGuiKey_Pause}, }; -ImGuiKey translate_key(char const* emscripten_key) __attribute__((__const__)); +ImGuiKey translate_key(char const* emscripten_key) __attribute__((__pure__)); ImGuiKey translate_key(char const* emscripten_key) { // Translate an emscripten-provided browser string describing a keycode to an imgui key code From a8de6dadc74c787b65ea5df817433887f7177551 Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Tue, 31 Mar 2026 19:43:01 +0100 Subject: [PATCH 27/72] reorder browser cursor implementation to the bottom of the file --- backends/imgui_impl_emscripten.cpp | 126 +++++++++++++++-------------- 1 file changed, 65 insertions(+), 61 deletions(-) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index ad0683e11..edea64941 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -244,68 +244,9 @@ enum class cursor }; void set(cursor new_cursor); // set a new cursor from a cursor enum +std::string get_string(); // read the current cursor setting as a string void unset(); // clear the current cursor setting - -// Browser cursor helper implementation - -std::string get_string() -{ - // Return the current cursor setting as a string - char* cursor_str_ptr{reinterpret_cast(EM_ASM_PTR( - return stringToNewUTF8(document.body.style.cursor); - ))}; - std::string const cursor_str{cursor_str_ptr}; - free(cursor_str_ptr); - return cursor_str; -} - -void set(cursor new_cursor) -{ - // Set the cursor according to the given enum - // Note, implementations omitted for cursors not used by imgui. For full implementation, use https://github.com/Armchair-Software/emscripten-browser-cursor - switch (new_cursor) - { - case cursor::none: - EM_ASM(document.body.style.cursor = 'none';); - break; - case cursor::cursor_default: - default: - EM_ASM(document.body.style.cursor = 'default';); - break; - case cursor::pointer: - EM_ASM(document.body.style.cursor = 'pointer';); - break; - case cursor::text: - EM_ASM(document.body.style.cursor = 'text';); - break; - case cursor::move: - EM_ASM(document.body.style.cursor = 'move';); - break; - case cursor::not_allowed: - EM_ASM(document.body.style.cursor = 'not-allowed';); - break; - case cursor::ew_resize: - EM_ASM(document.body.style.cursor = 'ew-resize';); - break; - case cursor::ns_resize: - EM_ASM(document.body.style.cursor = 'ns-resize';); - break; - case cursor::nesw_resize: - EM_ASM(document.body.style.cursor = 'nesw-resize';); - break; - case cursor::nwse_resize: - EM_ASM(document.body.style.cursor = 'nwse-resize';); - break; - } -} - -void set(std::string const& new_cursor) -{ - // Set the cursor from an arbitrary string - EM_ASM({ - document.body.style.cursor = UTF8ToString($0); - }, new_cursor.c_str()); -} +void set(std::string const& new_cursor); // set the cursor from an arbitrary string } // namespace emscripten_browser_cursor_internal @@ -663,4 +604,67 @@ void ImGui_ImplEmscripten_NewFrame() update_cursor(bd); } +namespace emscripten_browser_cursor_internal +{ + +std::string get_string() +{ + // Return the current cursor setting as a string + char* cursor_str_ptr{reinterpret_cast(EM_ASM_PTR( + return stringToNewUTF8(document.body.style.cursor); + ))}; + std::string const cursor_str{cursor_str_ptr}; + free(cursor_str_ptr); + return cursor_str; +} + +void set(cursor new_cursor) +{ + // Set the cursor according to the given enum + // Note, implementations omitted for cursors not used by imgui. For full implementation, use https://github.com/Armchair-Software/emscripten-browser-cursor + switch (new_cursor) + { + case cursor::none: + EM_ASM(document.body.style.cursor = 'none';); + break; + case cursor::cursor_default: + default: + EM_ASM(document.body.style.cursor = 'default';); + break; + case cursor::pointer: + EM_ASM(document.body.style.cursor = 'pointer';); + break; + case cursor::text: + EM_ASM(document.body.style.cursor = 'text';); + break; + case cursor::move: + EM_ASM(document.body.style.cursor = 'move';); + break; + case cursor::not_allowed: + EM_ASM(document.body.style.cursor = 'not-allowed';); + break; + case cursor::ew_resize: + EM_ASM(document.body.style.cursor = 'ew-resize';); + break; + case cursor::ns_resize: + EM_ASM(document.body.style.cursor = 'ns-resize';); + break; + case cursor::nesw_resize: + EM_ASM(document.body.style.cursor = 'nesw-resize';); + break; + case cursor::nwse_resize: + EM_ASM(document.body.style.cursor = 'nwse-resize';); + break; + } +} + +void set(std::string const& new_cursor) +{ + // Set the cursor from an arbitrary string + EM_ASM({ + document.body.style.cursor = UTF8ToString($0); + }, new_cursor.c_str()); +} + +} // namespace emscripten_browser_cursor_internal #endif // IMGUI_DISABLE From c4b6c689accfdb746c76859d90069c749dd62582 Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Tue, 31 Mar 2026 19:43:50 +0100 Subject: [PATCH 28/72] move cursor setting helper function implementations to the bottom of the file --- backends/imgui_impl_emscripten.cpp | 128 +++++++++++++++-------------- 1 file changed, 66 insertions(+), 62 deletions(-) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index edea64941..be1163d9c 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -269,68 +269,6 @@ ImGui_ImplEmscripten_Data* ImGui_ImplEmscripten_GetBackendData() return ImGui::GetCurrentContext() ? static_cast(ImGui::GetIO().BackendPlatformUserData) : nullptr; } -void set_cursor_if_necessary(emscripten_browser_cursor_internal::cursor& current_cursor, emscripten_browser_cursor_internal::cursor new_cursor) -{ - if (new_cursor == current_cursor) return; // don't do anything if the current cursor is already set - current_cursor = new_cursor; - emscripten_browser_cursor_internal::set(new_cursor); -} - -void update_cursor(ImGui_ImplEmscripten_Data* bd) -{ - // Sync any cursor changes due to ImGui to the browser's cursor - if (ImGui::GetIO().WantCaptureMouse) // mouse is hovering over the gui - { - if (!bd->CursorToRestore) - { - bd->CursorToRestore = emscripten_browser_cursor_internal::get_string(); // back up the existing cursor state when entering the imgui capture space - } - - switch (ImGui::GetMouseCursor()) - { - case ImGuiMouseCursor_None: - set_cursor_if_necessary(bd->CurrentCursor, emscripten_browser_cursor_internal::cursor::none); - break; - case ImGuiMouseCursor_Arrow: - set_cursor_if_necessary(bd->CurrentCursor, emscripten_browser_cursor_internal::cursor::cursor_default); - break; - case ImGuiMouseCursor_TextInput: // When hovering over InputText, etc. - set_cursor_if_necessary(bd->CurrentCursor, emscripten_browser_cursor_internal::cursor::text); - break; - case ImGuiMouseCursor_ResizeAll: // (Unused by Dear ImGui functions) - set_cursor_if_necessary(bd->CurrentCursor, emscripten_browser_cursor_internal::cursor::move); - break; - case ImGuiMouseCursor_ResizeNS: // When hovering over a horizontal border - set_cursor_if_necessary(bd->CurrentCursor, emscripten_browser_cursor_internal::cursor::ns_resize); - break; - case ImGuiMouseCursor_ResizeEW: // When hovering over a vertical border or a column - set_cursor_if_necessary(bd->CurrentCursor, emscripten_browser_cursor_internal::cursor::ew_resize); - break; - case ImGuiMouseCursor_ResizeNESW: // When hovering over the bottom-left corner of a window - set_cursor_if_necessary(bd->CurrentCursor, emscripten_browser_cursor_internal::cursor::nesw_resize); - break; - case ImGuiMouseCursor_ResizeNWSE: // When hovering over the bottom-right corner of a window - set_cursor_if_necessary(bd->CurrentCursor, emscripten_browser_cursor_internal::cursor::nwse_resize); - break; - case ImGuiMouseCursor_Hand: // (Unused by Dear ImGui functions. Use for e.g. hyperlinks) - set_cursor_if_necessary(bd->CurrentCursor, emscripten_browser_cursor_internal::cursor::pointer); - break; - case ImGuiMouseCursor_NotAllowed: // When hovering something with disallowed interaction. Usually a crossed circle. - set_cursor_if_necessary(bd->CurrentCursor, emscripten_browser_cursor_internal::cursor::not_allowed); - break; - } - } - else // mouse is away from the gui, hovering over some other part of the viewport - { - if (bd->CursorToRestore) - { - emscripten_browser_cursor_internal::set(*bd->CursorToRestore); // restore the previous cursor state when leaving the imgui capture space - bd->CursorToRestore = std::nullopt; - bd->CurrentCursor = emscripten_browser_cursor_internal::cursor::invalid; // select an unused value for current cursor to force a set next time set_cursor_if_necessary is called - } - } -} - } // anonymous namespace void ImGui_ImplEmscripten_Init() @@ -595,6 +533,72 @@ void ImGui_ImplEmscripten_Shutdown() IM_DELETE(bd); } +namespace { + +void set_cursor_if_necessary(emscripten_browser_cursor_internal::cursor& current_cursor, emscripten_browser_cursor_internal::cursor new_cursor) +{ + if (new_cursor == current_cursor) return; // don't do anything if the current cursor is already set + current_cursor = new_cursor; + emscripten_browser_cursor_internal::set(new_cursor); +} + +void update_cursor(ImGui_ImplEmscripten_Data* bd) +{ + // Sync any cursor changes due to ImGui to the browser's cursor + if (ImGui::GetIO().WantCaptureMouse) // mouse is hovering over the gui + { + if (!bd->CursorToRestore) + { + bd->CursorToRestore = emscripten_browser_cursor_internal::get_string(); // back up the existing cursor state when entering the imgui capture space + } + + switch (ImGui::GetMouseCursor()) + { + case ImGuiMouseCursor_None: + set_cursor_if_necessary(bd->CurrentCursor, emscripten_browser_cursor_internal::cursor::none); + break; + case ImGuiMouseCursor_Arrow: + set_cursor_if_necessary(bd->CurrentCursor, emscripten_browser_cursor_internal::cursor::cursor_default); + break; + case ImGuiMouseCursor_TextInput: // When hovering over InputText, etc. + set_cursor_if_necessary(bd->CurrentCursor, emscripten_browser_cursor_internal::cursor::text); + break; + case ImGuiMouseCursor_ResizeAll: // (Unused by Dear ImGui functions) + set_cursor_if_necessary(bd->CurrentCursor, emscripten_browser_cursor_internal::cursor::move); + break; + case ImGuiMouseCursor_ResizeNS: // When hovering over a horizontal border + set_cursor_if_necessary(bd->CurrentCursor, emscripten_browser_cursor_internal::cursor::ns_resize); + break; + case ImGuiMouseCursor_ResizeEW: // When hovering over a vertical border or a column + set_cursor_if_necessary(bd->CurrentCursor, emscripten_browser_cursor_internal::cursor::ew_resize); + break; + case ImGuiMouseCursor_ResizeNESW: // When hovering over the bottom-left corner of a window + set_cursor_if_necessary(bd->CurrentCursor, emscripten_browser_cursor_internal::cursor::nesw_resize); + break; + case ImGuiMouseCursor_ResizeNWSE: // When hovering over the bottom-right corner of a window + set_cursor_if_necessary(bd->CurrentCursor, emscripten_browser_cursor_internal::cursor::nwse_resize); + break; + case ImGuiMouseCursor_Hand: // (Unused by Dear ImGui functions. Use for e.g. hyperlinks) + set_cursor_if_necessary(bd->CurrentCursor, emscripten_browser_cursor_internal::cursor::pointer); + break; + case ImGuiMouseCursor_NotAllowed: // When hovering something with disallowed interaction. Usually a crossed circle. + set_cursor_if_necessary(bd->CurrentCursor, emscripten_browser_cursor_internal::cursor::not_allowed); + break; + } + } + else // mouse is away from the gui, hovering over some other part of the viewport + { + if (bd->CursorToRestore) + { + emscripten_browser_cursor_internal::set(*bd->CursorToRestore); // restore the previous cursor state when leaving the imgui capture space + bd->CursorToRestore = std::nullopt; + bd->CurrentCursor = emscripten_browser_cursor_internal::cursor::invalid; // select an unused value for current cursor to force a set next time set_cursor_if_necessary is called + } + } +} + +} // anonymous namespace + void ImGui_ImplEmscripten_NewFrame() { ImGui_ImplEmscripten_Data* bd{ImGui_ImplEmscripten_GetBackendData()}; From 94900e607aea200a9aa605cdf0c083225d2f1d3d Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Tue, 31 Mar 2026 19:55:55 +0100 Subject: [PATCH 29/72] replace key lookup map with handcrafted optimised decision tree --- backends/imgui_impl_emscripten.cpp | 544 ++++++++++++++++++++--------- 1 file changed, 387 insertions(+), 157 deletions(-) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index be1163d9c..a27206fe5 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -18,169 +18,15 @@ #include #include #include +#include namespace { -// A map of HTML5 key names to imgui keys -static const std::unordered_map key_translate_lookup{ - // main character keys - {"Backquote", ImGuiKey_GraveAccent}, - {"Backslash", ImGuiKey_Backslash}, - {"BracketLeft", ImGuiKey_LeftBracket}, - {"BracketRight", ImGuiKey_RightBracket}, - {"Comma", ImGuiKey_Comma}, - {"Digit0", ImGuiKey_0}, - {"Digit1", ImGuiKey_1}, - {"Digit2", ImGuiKey_2}, - {"Digit3", ImGuiKey_3}, - {"Digit4", ImGuiKey_4}, - {"Digit5", ImGuiKey_5}, - {"Digit6", ImGuiKey_6}, - {"Digit7", ImGuiKey_7}, - {"Digit8", ImGuiKey_8}, - {"Digit9", ImGuiKey_9}, - {"Equal", ImGuiKey_Equal}, - {"IntlBackslash", ImGuiKey_Backslash}, // Mapping to generic backslash - {"IntlRo", ImGuiKey_Slash}, // Closest match for non-standard layouts - {"IntlYen", ImGuiKey_Backslash}, // Closest match for non-standard layouts - {"KeyA", ImGuiKey_A}, - {"KeyB", ImGuiKey_B}, - {"KeyC", ImGuiKey_C}, - {"KeyD", ImGuiKey_D}, - {"KeyE", ImGuiKey_E}, - {"KeyF", ImGuiKey_F}, - {"KeyG", ImGuiKey_G}, - {"KeyH", ImGuiKey_H}, - {"KeyI", ImGuiKey_I}, - {"KeyJ", ImGuiKey_J}, - {"KeyK", ImGuiKey_K}, - {"KeyL", ImGuiKey_L}, - {"KeyM", ImGuiKey_M}, - {"KeyN", ImGuiKey_N}, - {"KeyO", ImGuiKey_O}, - {"KeyP", ImGuiKey_P}, - {"KeyQ", ImGuiKey_Q}, - {"KeyR", ImGuiKey_R}, - {"KeyS", ImGuiKey_S}, - {"KeyT", ImGuiKey_T}, - {"KeyU", ImGuiKey_U}, - {"KeyV", ImGuiKey_V}, - {"KeyW", ImGuiKey_W}, - {"KeyX", ImGuiKey_X}, - {"KeyY", ImGuiKey_Y}, - {"KeyZ", ImGuiKey_Z}, - {"Minus", ImGuiKey_Minus}, - {"Period", ImGuiKey_Period}, - {"Quote", ImGuiKey_Apostrophe}, - {"Semicolon", ImGuiKey_Semicolon}, - {"Slash", ImGuiKey_Slash}, - - // control keys - {"AltLeft", ImGuiKey_LeftAlt}, - {"AltRight", ImGuiKey_RightAlt}, - {"Backspace", ImGuiKey_Backspace}, - {"CapsLock", ImGuiKey_CapsLock}, - {"ContextMenu", ImGuiKey_Menu}, - {"ControlLeft", ImGuiKey_LeftCtrl}, - {"ControlRight", ImGuiKey_RightCtrl}, - {"Enter", ImGuiKey_Enter}, - {"MetaLeft", ImGuiKey_LeftSuper}, - {"MetaRight", ImGuiKey_RightSuper}, - {"ShiftLeft", ImGuiKey_LeftShift}, - {"ShiftRight", ImGuiKey_RightShift}, - {"Space", ImGuiKey_Space}, - {"Tab", ImGuiKey_Tab}, - - // navigation key group - {"Delete", ImGuiKey_Delete}, - {"End", ImGuiKey_End}, - //{"Help", ImGuiKey_PrintScreen}, // Best approximation - {"Home", ImGuiKey_Home}, - {"Insert", ImGuiKey_Insert}, - {"PageDown", ImGuiKey_PageDown}, - {"PageUp", ImGuiKey_PageUp}, - - // arrow key group - {"ArrowDown", ImGuiKey_DownArrow}, - {"ArrowLeft", ImGuiKey_LeftArrow}, - {"ArrowRight", ImGuiKey_RightArrow}, - {"ArrowUp", ImGuiKey_UpArrow}, - - // number pad group - {"NumLock", ImGuiKey_NumLock}, - {"Numpad0", ImGuiKey_Keypad0}, - {"Numpad1", ImGuiKey_Keypad1}, - {"Numpad2", ImGuiKey_Keypad2}, - {"Numpad3", ImGuiKey_Keypad3}, - {"Numpad4", ImGuiKey_Keypad4}, - {"Numpad5", ImGuiKey_Keypad5}, - {"Numpad6", ImGuiKey_Keypad6}, - {"Numpad7", ImGuiKey_Keypad7}, - {"Numpad8", ImGuiKey_Keypad8}, - {"Numpad9", ImGuiKey_Keypad9}, - {"NumpadAdd", ImGuiKey_KeypadAdd}, - {"NumpadBackspace", ImGuiKey_Backspace}, // No direct mapping; backspace functionality - //{"NumpadClear", ImGuiKey_KeypadClear}, // Custom-defined if needed - //{"NumpadClearEntry", ImGuiKey_KeypadClear}, // Custom-defined if needed - {"NumpadComma", ImGuiKey_KeypadDecimal}, // Closest match - {"NumpadDecimal", ImGuiKey_KeypadDecimal}, - {"NumpadDivide", ImGuiKey_KeypadDivide}, - {"NumpadEnter", ImGuiKey_KeypadEnter}, - {"NumpadEqual", ImGuiKey_KeypadEqual}, - {"NumpadHash", ImGuiKey_Backslash}, // Mapped to # on UK keyboard - //{"NumpadMemoryAdd", ImGuiKey_None}, // No defined mapping - //{"NumpadMemoryClear", ImGuiKey_None}, // No defined mapping - //{"NumpadMemoryRecall", ImGuiKey_None}, // No defined mapping - //{"NumpadMemoryStore", ImGuiKey_None}, // No defined mapping - //{"NumpadMemorySubtract", ImGuiKey_None}, // No defined mapping - {"NumpadMultiply", ImGuiKey_KeypadMultiply}, - {"NumpadParenLeft", ImGuiKey_LeftBracket}, // Closest available - {"NumpadParenRight", ImGuiKey_RightBracket}, // Closest available - {"NumpadStar", ImGuiKey_KeypadMultiply}, // Same as multiply - {"NumpadSubtract", ImGuiKey_KeypadSubtract}, - - // top row key groups - {"Escape", ImGuiKey_Escape}, - {"F1", ImGuiKey_F1}, - {"F2", ImGuiKey_F2}, - {"F3", ImGuiKey_F3}, - {"F4", ImGuiKey_F4}, - {"F5", ImGuiKey_F5}, - {"F6", ImGuiKey_F6}, - {"F7", ImGuiKey_F7}, - {"F8", ImGuiKey_F8}, - {"F9", ImGuiKey_F9}, - {"F10", ImGuiKey_F10}, - {"F11", ImGuiKey_F11}, - {"F12", ImGuiKey_F12}, - //{"Fn", ImGuiKey_None}, // No direct mapping - //{"FnLock", ImGuiKey_None}, // No direct mapping - {"PrintScreen", ImGuiKey_PrintScreen}, - {"ScrollLock", ImGuiKey_ScrollLock}, - {"Pause", ImGuiKey_Pause}, -}; +// W3C UI Events KeyboardEvent.code translation helpers +ImGuiKey translate_numpad_key(char const* emscripten_key, size_t key_len) __attribute__((__pure__)); ImGuiKey translate_key(char const* emscripten_key) __attribute__((__pure__)); -ImGuiKey translate_key(char const* emscripten_key) -{ - // Translate an emscripten-provided browser string describing a keycode to an imgui key code - const std::unordered_map::const_iterator it{key_translate_lookup.find(emscripten_key)}; - if (it != key_translate_lookup.end()) - { - return it->second; - } - return ImGuiKey_None; -} - constexpr ImGuiMouseButton translate_mousebutton(unsigned short emscripten_button) __attribute__((__const__)); -constexpr ImGuiMouseButton translate_mousebutton(unsigned short emscripten_button) -{ - // Translate an emscripten-provided integer describing a mouse button to an imgui mouse button - if (emscripten_button == 1) return ImGuiMouseButton_Middle; // 1 = middle mouse button - if (emscripten_button == 2) return ImGuiMouseButton_Right; // 2 = right mouse button - if (emscripten_button >= ImGuiMouseButton_COUNT) return ImGuiMouseButton_Middle; // treat any weird clicks on unexpected buttons (button 6 upwards) as middle mouse - return emscripten_button; // any other button translates 1:1 -} } // anonymous namespace @@ -671,4 +517,388 @@ void set(std::string const& new_cursor) } } // namespace emscripten_browser_cursor_internal + +namespace { + +IM_STATIC_ASSERT(static_cast(ImGuiKey_Z) == static_cast(ImGuiKey_A) + 25); +IM_STATIC_ASSERT(static_cast(ImGuiKey_9) == static_cast(ImGuiKey_0) + 9); +IM_STATIC_ASSERT(static_cast(ImGuiKey_F12) == static_cast(ImGuiKey_F1) + 11); +IM_STATIC_ASSERT(static_cast(ImGuiKey_F24) == static_cast(ImGuiKey_F1) + 23); +IM_STATIC_ASSERT(static_cast(ImGuiKey_Keypad9) == static_cast(ImGuiKey_Keypad0) + 9); + +constexpr ImGuiMouseButton translate_mousebutton(unsigned short emscripten_button) +{ + // Translate an emscripten-provided integer describing a mouse button to an imgui mouse button + if (emscripten_button == 1) return ImGuiMouseButton_Middle; // 1 = middle mouse button + if (emscripten_button == 2) return ImGuiMouseButton_Right; // 2 = right mouse button + if (emscripten_button >= ImGuiMouseButton_COUNT) return ImGuiMouseButton_Middle; // treat any weird clicks on unexpected buttons (button 6 upwards) as middle mouse + return emscripten_button; // any other button translates 1:1 +} + +ImGuiKey translate_numpad_key(char const* emscripten_key, size_t key_len) +{ + // W3C UI Events, "Numpad section" code values. + // These optional W3C numpad codes currently have no ImGuiKey equivalent: + // "NumpadClear", "NumpadClearEntry", "NumpadMemoryAdd", "NumpadMemoryClear", + // "NumpadMemoryRecall", "NumpadMemoryStore", "NumpadMemorySubtract" + switch (emscripten_key[6]) + { + case 'A': return ImGuiKey_KeypadAdd; // "NumpadAdd" + case 'B': return ImGuiKey_Backspace; // "NumpadBackspace": closest editing equivalent + case 'C': + switch (key_len) + { + case 11: + if (emscripten_key[7] == 'o') return ImGuiKey_KeypadDecimal; // "NumpadComma": Dear ImGui only exposes KeypadDecimal + // if (emscripten_key[7] == 'l') return ImGuiKey_None; // "NumpadClear" + break; + // case 16: return ImGuiKey_None; // "NumpadClearEntry" + } + break; + case 'D': + switch (key_len) + { + case 12: return ImGuiKey_KeypadDivide; // "NumpadDivide" + case 13: return ImGuiKey_KeypadDecimal; // "NumpadDecimal" + } + break; + case 'E': + switch (emscripten_key[7]) + { + case 'n': return ImGuiKey_KeypadEnter; // "NumpadEnter" + case 'q': return ImGuiKey_KeypadEqual; // "NumpadEqual" + } + break; + case 'H': return ImGuiKey_Backslash; // "NumpadHash": closest printable equivalent for telephone-style '#' + case 'M': + switch (key_len) + { + case 14: return ImGuiKey_KeypadMultiply; // "NumpadMultiply" + // case 15: return ImGuiKey_None; // "NumpadMemoryAdd" + case 17: + // switch (emscripten_key[12]) + // { + // case 'C': return ImGuiKey_None; // "NumpadMemoryClear" + // case 'S': return ImGuiKey_None; // "NumpadMemoryStore" + // } + break; + // case 18: return ImGuiKey_None; // "NumpadMemoryRecall" + } + break; + case 'P': + switch (key_len) + { + case 15: return ImGuiKey_LeftBracket; // "NumpadParenLeft": closest available bracket key + case 16: return ImGuiKey_RightBracket; // "NumpadParenRight": closest available bracket key + } + break; + case 'S': + switch (key_len) + { + case 10: return ImGuiKey_KeypadMultiply; // "NumpadStar": phone/remote '*' behaves like keypad multiply + case 14: return ImGuiKey_KeypadSubtract; // "NumpadSubtract" + // case 20: return ImGuiKey_None; // "NumpadMemorySubtract" + } + break; + default: return static_cast(ImGuiKey_Keypad0 + (emscripten_key[6] - '0')); // Remaining canonical "Numpad..." codes here are "Numpad0" .. "Numpad9" + } + return ImGuiKey_None; +} + +ImGuiKey translate_key(char const* emscripten_key) +{ + // Translate a W3C KeyboardEvent.code string into an ImGuiKey. + // Canonical references: W3C UI Events code tables, with MDN used for browser quirks/aliases. + if (emscripten_key == nullptr || emscripten_key[0] == '\0') return ImGuiKey_None; + + size_t const key_len{strlen(emscripten_key)}; + + switch (emscripten_key[0]) + { + case 'A': + switch (key_len) + { + // case 5: + // switch (emscripten_key[1]) + // { + // case 'b': return ImGuiKey_None; // "Abort" + // case 'g': return ImGuiKey_None; // "Again" + // } + // break; + case 7: + switch (emscripten_key[1]) + { + case 'l': return ImGuiKey_LeftAlt; // "AltLeft" + case 'r': return ImGuiKey_UpArrow; // "ArrowUp" + } + break; + case 8: return ImGuiKey_RightAlt; // "AltRight" + case 9: + switch (emscripten_key[5]) + { + case 'D': return ImGuiKey_DownArrow; // "ArrowDown" + case 'L': return ImGuiKey_LeftArrow; // "ArrowLeft" + } + break; + case 10: return ImGuiKey_RightArrow; // "ArrowRight" + // case 13: return ImGuiKey_None; // "AudioVolumeUp" + // case 15: + // switch (emscripten_key[11]) + // { + // case 'D': return ImGuiKey_None; // "AudioVolumeDown" + // case 'M': return ImGuiKey_None; // "AudioVolumeMute" + // } + // break; + } + break; + case 'B': + switch (key_len) + { + case 9: + switch (emscripten_key[4]) + { + case 'q': return ImGuiKey_GraveAccent; // "Backquote" + case 's': + if (emscripten_key[5] == 'l') return ImGuiKey_Backslash; // "Backslash": W3C uses this for both US \| and the UK #~ key tucked under Enter + if (emscripten_key[5] == 'p') return ImGuiKey_Backspace; // "Backspace" + break; + } + break; + case 11: + if (emscripten_key[3] == 'c') return ImGuiKey_LeftBracket; // "BracketLeft" + switch (emscripten_key[7]) + { + case 'B': return ImGuiKey_AppBack; // "BrowserBack": pass through even in browser hosts so the embedding app can decide + // case 'H': return ImGuiKey_None; // "BrowserHome" + // case 'S': return ImGuiKey_None; // "BrowserStop" + } + break; + case 12: return ImGuiKey_RightBracket; // "BracketRight" + // case 13: return ImGuiKey_None; // "BrowserSearch" + case 14: + switch (emscripten_key[7]) + { + case 'F': return ImGuiKey_AppForward; // "BrowserForward": pass through even in browser hosts so the embedding app can decide + // case 'R': return ImGuiKey_None; // "BrowserRefresh" + } + break; + // case 16: return ImGuiKey_None; // "BrowserFavorites" + } + break; + case 'C': + switch (key_len) + { + // case 3: return ImGuiKey_None; // "Cut" + // case 4: return ImGuiKey_None; // "Copy" + case 5: return ImGuiKey_Comma; // "Comma" + // case 7: return ImGuiKey_None; // "Convert" + case 8: return ImGuiKey_CapsLock; // "CapsLock" + case 11: + switch (emscripten_key[4]) + { + case 'e': return ImGuiKey_Menu; // "ContextMenu" + case 'r': return ImGuiKey_LeftCtrl; // "ControlLeft" + } + break; + case 12: return ImGuiKey_RightCtrl; // "ControlRight" + } + break; + case 'D': + if (key_len == 6) + { + if (emscripten_key[1] == 'e') return ImGuiKey_Delete; // "Delete" + else return static_cast(ImGuiKey_0 + (emscripten_key[5] - '0')); // "Digit0" .. "Digit9" + } + break; + case 'E': + switch (key_len) + { + case 3: return ImGuiKey_End; // "End" + case 5: + switch (emscripten_key[1]) + { + case 'n': return ImGuiKey_Enter; // "Enter" + case 'q': return ImGuiKey_Equal; // "Equal" + // case 'j': return ImGuiKey_None; // "Eject" + } + break; + case 6: return ImGuiKey_Escape; // "Escape" + } + break; + case 'F': + switch (key_len) + { + case 2: + if (emscripten_key[1] == 'n') return ImGuiKey_None; // "Fn": no ImGuiKey equivalent + else return static_cast(ImGuiKey_F1 + (emscripten_key[1] - '1')); // "F1" .. "F9" + case 3: + if (emscripten_key[1] == '1') return static_cast(ImGuiKey_F10 + (emscripten_key[2] - '0')); // "F10" .. "F19" + else return static_cast(ImGuiKey_F20 + (emscripten_key[2] - '0')); // "F20" .. "F24" + // case 4: return ImGuiKey_None; // "Find" + // case 6: return ImGuiKey_None; // "FnLock" + } + break; + case 'H': + switch (key_len) + { + case 4: + switch (emscripten_key[1]) + { + case 'o': return ImGuiKey_Home; // "Home" + // case 'e': return ImGuiKey_None; // "Help" + } + break; + // case 5: return ImGuiKey_None; // "Hyper" + // case 8: return ImGuiKey_None; // "Hiragana" + } + break; + case 'I': + switch (key_len) + { + case 6: + switch (emscripten_key[2]) + { + case 's': return ImGuiKey_Insert; // "Insert" + case 't': return ImGuiKey_Slash; // "IntlRo": closest printable equivalent + } + break; + case 7: return ImGuiKey_Backslash; // "IntlYen": closest printable equivalent + case 13: return ImGuiKey_Backslash; // "IntlBackslash": preserve original behavior and map to generic backslash + } + break; + case 'K': + switch (key_len) + { + case 4: return static_cast(ImGuiKey_A + (emscripten_key[3] - 'A')); // "KeyA" .. "KeyZ" + // case 8: + // switch (emscripten_key[2]) + // { + // case 'n': return ImGuiKey_None; // "KanaMode" + // default: return ImGuiKey_None; // "Katakana" + // } + } + break; + // case 'L': + // switch (key_len) + // { + // case 5: + // switch (emscripten_key[4]) + // { + // case '1': return ImGuiKey_None; // "Lang1" + // case '2': return ImGuiKey_None; // "Lang2" + // } + // break; + // case 10: + // switch (emscripten_key[6]) + // { + // case 'A': + // switch (emscripten_key[9]) + // { + // case '1': return ImGuiKey_None; // "LaunchApp1" + // case '2': return ImGuiKey_None; // "LaunchApp2" + // } + // break; + // case 'M': return ImGuiKey_None; // "LaunchMail" + // } + // break; + // } + case 'M': + switch (key_len) + { + case 5: return ImGuiKey_Minus; // "Minus" + case 8: return ImGuiKey_LeftSuper; // "MetaLeft" + case 9: + switch (emscripten_key[3]) + { + case 'a': return ImGuiKey_RightSuper; // "MetaRight" + default: + // return ImGuiKey_None; // "MediaStop" + } + break; + // case 11: return ImGuiKey_None; // "MediaSelect" + // case 14: + // switch (emscripten_key[10]) + // { + // case 'N': return ImGuiKey_None; // "MediaTrackNext" + // default: return ImGuiKey_None; // "MediaPlayPause" + // } + // case 18: return ImGuiKey_None; // "MediaTrackPrevious" + } + break; + case 'N': + if (emscripten_key[3] == 'p') + { + ImGuiKey const numpad_key{translate_numpad_key(emscripten_key, key_len)}; + if (numpad_key != ImGuiKey_None) return numpad_key; + } + if (key_len == 7) return ImGuiKey_NumLock; // "NumLock" + // if (key_len == 10) return ImGuiKey_None; // "NonConvert" + break; + // case 'O': return ImGuiKey_None; // "Open" + case 'P': + switch (key_len) + { + case 5: + if (emscripten_key[2] == 'u') return ImGuiKey_Pause; // "Pause" + // if (emscripten_key[2] == 's') return ImGuiKey_None; // "Paste" + // if (emscripten_key[1] == 'o') return ImGuiKey_None; // "Power" + // if (emscripten_key[1] == 'r') return ImGuiKey_None; // "Props" + break; + case 6: + switch (emscripten_key[1]) + { + case 'a': return ImGuiKey_PageUp; // "PageUp" + case 'e': return ImGuiKey_Period; // "Period" + } + break; + case 8: return ImGuiKey_PageDown; // "PageDown" + case 11: return ImGuiKey_PrintScreen; // "PrintScreen" + } + break; + case 'Q': return ImGuiKey_Apostrophe; // "Quote" + // case 'R': return ImGuiKey_None; // "Resume" + case 'S': + switch (key_len) + { + case 5: + if (emscripten_key[2] == 'a') return ImGuiKey_Slash; // "Slash" + if (emscripten_key[1] == 'p') return ImGuiKey_Space; // "Space" + // if (emscripten_key[2] == 'e') return ImGuiKey_None; // "Sleep" + // if (emscripten_key[1] == 'u') return ImGuiKey_None; // "Super" + break; + // case 6: return ImGuiKey_None; // "Select" + // case 7: return ImGuiKey_None; // "Suspend" + case 9: + switch (emscripten_key[1]) + { + case 'e': return ImGuiKey_Semicolon; // "Semicolon" + case 'h': return ImGuiKey_LeftShift; // "ShiftLeft" + } + break; + case 10: + switch (emscripten_key[1]) + { + case 'c': return ImGuiKey_ScrollLock; // "ScrollLock" + case 'h': return ImGuiKey_RightShift; // "ShiftRight" + } + break; + } + break; + case 'T': + if (key_len == 3) return ImGuiKey_Tab; // "Tab" + // if (key_len == 5) return ImGuiKey_None; // "Turbo" + break; + // case 'U': + // switch (key_len) + // { + // case 4: return ImGuiKey_None; // "Undo" + // case 12: return ImGuiKey_None; // "Unidentified" + // } + // case 'W': return ImGuiKey_None; // "WakeUp" + } + return ImGuiKey_None; +} + +} // anonymous namespace + #endif // IMGUI_DISABLE From 6a2efe6a6fe7167d1507e659d1ff98f481b1ce6e Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Tue, 31 Mar 2026 20:01:55 +0100 Subject: [PATCH 30/72] remove std::string, use C style strings --- backends/imgui_impl_emscripten.cpp | 38 +++++++++++++++++------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index a27206fe5..be23894f3 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -90,9 +90,8 @@ enum class cursor }; void set(cursor new_cursor); // set a new cursor from a cursor enum -std::string get_string(); // read the current cursor setting as a string -void unset(); // clear the current cursor setting -void set(std::string const& new_cursor); // set the cursor from an arbitrary string +char* get_string(); // read the current cursor setting as an owned string, caller must free() +void set(char const* new_cursor); // set the cursor from an arbitrary string } // namespace emscripten_browser_cursor_internal @@ -101,10 +100,11 @@ namespace { struct ImGui_ImplEmscripten_Data { emscripten_browser_cursor_internal::cursor CurrentCursor; - std::optional CursorToRestore; + char* CursorToRestore; ImGui_ImplEmscripten_Data() : CurrentCursor{emscripten_browser_cursor_internal::cursor::invalid} + , CursorToRestore{nullptr} { } }; @@ -372,6 +372,12 @@ void ImGui_ImplEmscripten_Shutdown() emscripten_set_focusout_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr); // TODO: touch events + if (bd->CursorToRestore != nullptr) + { + emscripten_browser_cursor_internal::set(bd->CursorToRestore); // restore the previous cursor state if imgui still owns the cursor on shutdown + free(bd->CursorToRestore); + } + ImGuiIO& io{ImGui::GetIO()}; io.BackendPlatformName = nullptr; io.BackendPlatformUserData = nullptr; @@ -393,7 +399,7 @@ void update_cursor(ImGui_ImplEmscripten_Data* bd) // Sync any cursor changes due to ImGui to the browser's cursor if (ImGui::GetIO().WantCaptureMouse) // mouse is hovering over the gui { - if (!bd->CursorToRestore) + if (bd->CursorToRestore == nullptr) { bd->CursorToRestore = emscripten_browser_cursor_internal::get_string(); // back up the existing cursor state when entering the imgui capture space } @@ -434,10 +440,11 @@ void update_cursor(ImGui_ImplEmscripten_Data* bd) } else // mouse is away from the gui, hovering over some other part of the viewport { - if (bd->CursorToRestore) + if (bd->CursorToRestore != nullptr) { - emscripten_browser_cursor_internal::set(*bd->CursorToRestore); // restore the previous cursor state when leaving the imgui capture space - bd->CursorToRestore = std::nullopt; + emscripten_browser_cursor_internal::set(bd->CursorToRestore); // restore the previous cursor state when leaving the imgui capture space + free(bd->CursorToRestore); + bd->CursorToRestore = nullptr; bd->CurrentCursor = emscripten_browser_cursor_internal::cursor::invalid; // select an unused value for current cursor to force a set next time set_cursor_if_necessary is called } } @@ -457,15 +464,12 @@ void ImGui_ImplEmscripten_NewFrame() namespace emscripten_browser_cursor_internal { -std::string get_string() +char* get_string() { - // Return the current cursor setting as a string - char* cursor_str_ptr{reinterpret_cast(EM_ASM_PTR( + // Return the current cursor setting as a newly-allocated string, caller must free it. + return reinterpret_cast(EM_ASM_PTR( return stringToNewUTF8(document.body.style.cursor); - ))}; - std::string const cursor_str{cursor_str_ptr}; - free(cursor_str_ptr); - return cursor_str; + )); } void set(cursor new_cursor) @@ -508,12 +512,12 @@ void set(cursor new_cursor) } } -void set(std::string const& new_cursor) +void set(char const* new_cursor) { // Set the cursor from an arbitrary string EM_ASM({ document.body.style.cursor = UTF8ToString($0); - }, new_cursor.c_str()); + }, new_cursor); } } // namespace emscripten_browser_cursor_internal From 7a4179856df65c8c41bdf46bceef8d3201cd0744 Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Tue, 31 Mar 2026 20:09:07 +0100 Subject: [PATCH 31/72] don't use std::numeric_limits --- backends/imgui_impl_emscripten.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index be23894f3..1e9c43244 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -60,7 +60,7 @@ enum class cursor copy, // cursor next to a smaller folder icon with a plus sign - Something is to be copied. move, // plus sign made of two thin lines, with small arrows facing out - Something is to be moved. no_drop, // cursor next to circle with a line through it - An item may not be dropped at the current location. - not_allowed, // Circle with a line through it - The requested action will not be carried out. + not_allowed, // circle with a line through it - The requested action will not be carried out. grab, // fully opened hand - Something can be grabbed (dragged to be moved). grabbing, // closed hand - Something is being grabbed (dragged to be moved). @@ -86,7 +86,7 @@ enum class cursor zoom_out, // Special invalid value - invalid = std::numeric_limits::max() + invalid }; void set(cursor new_cursor); // set a new cursor from a cursor enum From 921fb3901d526b5baea3ff11d33cc275e373ef21 Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Tue, 31 Mar 2026 20:09:55 +0100 Subject: [PATCH 32/72] add changelog entries --- backends/imgui_impl_emscripten.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index 1e9c43244..e495b1c0c 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -4,6 +4,8 @@ // // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-03-31: Emscripten: Reworked KeyboardEvent.code translation to avoid std::unordered_map and added BrowserBack/Forward and F13-F24 mappings. +// 2026-03-31: Emscripten: Moved cursor state into backend userdata and replaced cursor restore storage with owned C strings. // 2024-12-09: Inputs: Added special handling for modifier keys to also generate modifier key events. // 2024-12-08: Inputs: Prevent "Delete" key from getting printed in text input. // 2024-12-06: Inputs: Added special handling for Tab and Enter event capture. From 9c4f1e55f1a7935072b1ccaf0fb91a62937ee361 Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Tue, 31 Mar 2026 20:37:26 +0100 Subject: [PATCH 33/72] tidy up MediaStop commented default --- backends/imgui_impl_emscripten.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index e495b1c0c..42d6d47f2 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -817,8 +817,7 @@ ImGuiKey translate_key(char const* emscripten_key) switch (emscripten_key[3]) { case 'a': return ImGuiKey_RightSuper; // "MetaRight" - default: - // return ImGuiKey_None; // "MediaStop" + // default: return ImGuiKey_None; // "MediaStop" } break; // case 11: return ImGuiKey_None; // "MediaSelect" From b144ac3b53497ef7df33186ced812e03147ef921 Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Tue, 31 Mar 2026 20:50:38 +0100 Subject: [PATCH 34/72] use static assert instead of IM_STATIC_ASSERT --- backends/imgui_impl_emscripten.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index 42d6d47f2..5a2a1af36 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -526,11 +526,11 @@ void set(char const* new_cursor) namespace { -IM_STATIC_ASSERT(static_cast(ImGuiKey_Z) == static_cast(ImGuiKey_A) + 25); -IM_STATIC_ASSERT(static_cast(ImGuiKey_9) == static_cast(ImGuiKey_0) + 9); -IM_STATIC_ASSERT(static_cast(ImGuiKey_F12) == static_cast(ImGuiKey_F1) + 11); -IM_STATIC_ASSERT(static_cast(ImGuiKey_F24) == static_cast(ImGuiKey_F1) + 23); -IM_STATIC_ASSERT(static_cast(ImGuiKey_Keypad9) == static_cast(ImGuiKey_Keypad0) + 9); +static_assert(static_cast(ImGuiKey_Z) == static_cast(ImGuiKey_A) + 25, ""); +static_assert(static_cast(ImGuiKey_9) == static_cast(ImGuiKey_0) + 9, ""); +static_assert(static_cast(ImGuiKey_F12) == static_cast(ImGuiKey_F1) + 11, ""); +static_assert(static_cast(ImGuiKey_F24) == static_cast(ImGuiKey_F1) + 23, ""); +static_assert(static_cast(ImGuiKey_Keypad9) == static_cast(ImGuiKey_Keypad0) + 9, ""); constexpr ImGuiMouseButton translate_mousebutton(unsigned short emscripten_button) { From 438a3326fd3bf9a47d68fca8eccebc542a49339f Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Tue, 31 Mar 2026 21:00:07 +0100 Subject: [PATCH 35/72] query display size from EM_ASM rather than requiring emscripten::val --- backends/imgui_impl_emscripten.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index 5a2a1af36..f9cefa0f4 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -19,7 +19,6 @@ #include "imgui_impl_emscripten.h" #include #include -#include #include namespace { @@ -131,8 +130,8 @@ void ImGui_ImplEmscripten_Init() io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // set up initial display size values - io.DisplaySize.x = emscripten::val::global("window")["innerWidth"].as(); - io.DisplaySize.y = emscripten::val::global("window")["innerHeight"].as(); + io.DisplaySize.x = static_cast(EM_ASM_INT(return window.innerWidth;)); + io.DisplaySize.y = static_cast(EM_ASM_INT(return window.innerHeight;)); emscripten_set_mousemove_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, // target From 784ad4e0d269d9c9d82617931f6c2308d2bbeb55 Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Tue, 31 Mar 2026 21:20:39 +0100 Subject: [PATCH 36/72] add example --- docs/EXAMPLES.md | 5 +- .../Makefile.emscripten | 99 ++++++ examples/example_emscripten_wgpu/README.md | 24 ++ examples/example_emscripten_wgpu/main.cpp | 329 ++++++++++++++++++ 4 files changed, 456 insertions(+), 1 deletion(-) create mode 100644 examples/example_emscripten_wgpu/Makefile.emscripten create mode 100644 examples/example_emscripten_wgpu/README.md create mode 100644 examples/example_emscripten_wgpu/main.cpp diff --git a/docs/EXAMPLES.md b/docs/EXAMPLES.md index 676d745bc..7cf73d214 100644 --- a/docs/EXAMPLES.md +++ b/docs/EXAMPLES.md @@ -71,6 +71,10 @@ OSX + OpenGL2 example.
(NB: imgui_impl_osx.mm is currently not as feature complete as other platforms backends. You may prefer to use the GLFW Or SDL backends, which will also support Windows and Linux.) +[example_emscripten_wgpu/](https://github.com/ocornut/imgui/blob/master/examples/example_emscripten_wgpu/)
+Emscripten (browser) + WebGPU example. Uses imgui_impl_emscripten for platform input and imgui_impl_wgpu for rendering.
+= main.cpp + imgui_impl_emscripten.cpp + imgui_impl_wgpu.cpp + [example_glfw_wgpu/](https://github.com/ocornut/imgui/blob/master/examples/example_glfw_wgpu/)
GLFW + WebGPU example. Supports Emscripten (web), Dawn (native), WGPU (native).
= main.cpp + imgui_impl_glfw.cpp + imgui_impl_wgpu.cpp @@ -253,4 +257,3 @@ when an interactive drag is in progress. Note that some setup configurations or GPU drivers may introduce additional display lag depending on their settings. If you notice that dragging windows is laggy and you are not sure what the cause is: try drawing a simple 2D shape directly under the mouse cursor to help identify the issue! - diff --git a/examples/example_emscripten_wgpu/Makefile.emscripten b/examples/example_emscripten_wgpu/Makefile.emscripten new file mode 100644 index 000000000..62e21b232 --- /dev/null +++ b/examples/example_emscripten_wgpu/Makefile.emscripten @@ -0,0 +1,99 @@ +# +# Makefile to use with emscripten +# See https://emscripten.org/docs/getting_started/downloads.html +# for installation instructions. +# +# This Makefile assumes you have loaded emscripten's environment. +# (On Windows, you may need to execute emsdk_env.bat or encmdprompt.bat ahead) +# +# Running `make -f Makefile.emscripten` will produce three files: +# - web/index.html +# - web/index.js +# - web/index.wasm +# +# All three are needed to run the demo. + +CC = emcc +CXX = em++ +WEB_DIR = web +EXE = $(WEB_DIR)/index.html +IMGUI_DIR = ../.. +SOURCES = main.cpp +SOURCES += $(IMGUI_DIR)/backends/imgui_impl_emscripten.cpp $(IMGUI_DIR)/backends/imgui_impl_wgpu.cpp +SOURCES += $(IMGUI_DIR)/imgui.cpp $(IMGUI_DIR)/imgui_demo.cpp $(IMGUI_DIR)/imgui_draw.cpp $(IMGUI_DIR)/imgui_tables.cpp $(IMGUI_DIR)/imgui_widgets.cpp +OBJS = $(addsuffix .o, $(basename $(notdir $(SOURCES)))) +UNAME_S := $(shell uname -s) +CPPFLAGS = +LDFLAGS = +EMS = + +##--------------------------------------------------------------------- +## EMSCRIPTEN OPTIONS +##--------------------------------------------------------------------- + +# ("EMS" options gets added to both CPPFLAGS and LDFLAGS, whereas some options are for linker only) +EMS += -s DISABLE_EXCEPTION_CATCHING=1 +LDFLAGS += -s WASM=1 +LDFLAGS += -s ALLOW_MEMORY_GROWTH=1 +LDFLAGS += -s ASYNCIFY=1 +LDFLAGS += -s NO_EXIT_RUNTIME=0 +LDFLAGS += -s ASSERTIONS=1 + +# This example uses imgui_impl_emscripten directly, so no GLFW port is required. +# Use Emscripten's Dawn-based WebGPU port. +EMS += --use-port=emdawnwebgpu +LDFLAGS += --use-port=emdawnwebgpu + +# Build as single file (binary text encoded in .html file) +#LDFLAGS += -sSINGLE_FILE + +# Emscripten allows preloading a file or folder to be accessible at runtime. +# The Makefile for this example project suggests embedding the misc/fonts/ folder into our application, it will then be accessible as "/fonts" +# See documentation for more details: https://emscripten.org/docs/porting/files/packaging_files.html +# (Default value is 0. Set to 1 to enable file-system and include the misc/fonts/ folder as part of the build.) +USE_FILE_SYSTEM ?= 0 +ifeq ($(USE_FILE_SYSTEM), 0) +LDFLAGS += -s NO_FILESYSTEM=1 +CPPFLAGS += -DIMGUI_DISABLE_FILE_FUNCTIONS +endif +ifeq ($(USE_FILE_SYSTEM), 1) +LDFLAGS += --no-heap-copy --preload-file ../../misc/fonts@/fonts +endif + +##--------------------------------------------------------------------- +## FINAL BUILD FLAGS +##--------------------------------------------------------------------- + +CPPFLAGS += -I$(IMGUI_DIR) -I$(IMGUI_DIR)/backends +#CPPFLAGS += -g +CPPFLAGS += -Wall -Wformat -Os $(EMS) +LDFLAGS += --shell-file ../libs/emscripten/shell_minimal.html +LDFLAGS += $(EMS) + +##--------------------------------------------------------------------- +## BUILD RULES +##--------------------------------------------------------------------- + +%.o:%.cpp + $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $< + +%.o:$(IMGUI_DIR)/%.cpp + $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $< + +%.o:$(IMGUI_DIR)/backends/%.cpp + $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $< + +all: $(EXE) + @echo Build complete for $(EXE) + +$(WEB_DIR): + mkdir $@ + +serve: all + python3 -m http.server -d $(WEB_DIR) + +$(EXE): $(OBJS) $(WEB_DIR) + $(CXX) -o $@ $(OBJS) $(LDFLAGS) + +clean: + rm -f $(EXE) $(OBJS) $(WEB_DIR)/*.js $(WEB_DIR)/*.wasm $(WEB_DIR)/*.wasm.pre diff --git a/examples/example_emscripten_wgpu/README.md b/examples/example_emscripten_wgpu/README.md new file mode 100644 index 000000000..1913514c7 --- /dev/null +++ b/examples/example_emscripten_wgpu/README.md @@ -0,0 +1,24 @@ +## How to Build + +- You need to install Emscripten from https://emscripten.org/docs/getting_started/downloads.html, and have the environment variables set, as described in https://emscripten.org/docs/getting_started/downloads.html#installation-instructions + +- Depending on your configuration, in Windows you may need to run `emsdk/emsdk_env.bat` in your console to access the Emscripten command-line tools. + +- You may also refer to our [Continuous Integration setup](https://github.com/ocornut/imgui/tree/master/.github/workflows) for Emscripten setup. + +- Then build using `make -f Makefile.emscripten` while in the `example_emscripten_wgpu/` directory. + +- This example is browser-only. It uses `imgui_impl_emscripten` for platform input and `imgui_impl_wgpu` for rendering. + +- Requires recent Emscripten with the `emdawnwebgpu` port available. + +## How to Run + +To run on a local machine: +- Make sure your browser supports WebGPU and it is enabled. +- `emrun web/index.html` will spawn a temporary local webserver and open the example in your browser. See https://emscripten.org/docs/compiling/Running-html-files-with-emrun.html for details. +- Otherwise, generally you will need a local webserver: + - Quoting [https://emscripten.org/docs/getting_started](https://emscripten.org/docs/getting_started/Tutorial.html#generating-html):
+_"Unfortunately several browsers (including Chrome, Safari, and Internet Explorer) do not support file:// [XHR](https://emscripten.org/docs/site/glossary.html#term-xhr) requests, and can’t load extra files needed by the HTML (like a .wasm file, or packaged file data as mentioned lower down). For these browsers you’ll need to serve the files using a [local webserver](https://emscripten.org/docs/getting_started/FAQ.html#faq-local-webserver) and then open http://localhost:8000/hello.html."_ + - You may use Python 3 builtin webserver: `python -m http.server -d web` (this is what `make serve` uses). + - You may use Python 2 builtin webserver: `cd web && python -m SimpleHTTPServer`. diff --git a/examples/example_emscripten_wgpu/main.cpp b/examples/example_emscripten_wgpu/main.cpp new file mode 100644 index 000000000..b509030a8 --- /dev/null +++ b/examples/example_emscripten_wgpu/main.cpp @@ -0,0 +1,329 @@ +// Dear ImGui: standalone example application for using Emscripten + WebGPU +// - This uses imgui_impl_emscripten for platform inputs and imgui_impl_wgpu for rendering. +// - Emscripten is required to build this example. See https://emscripten.org. +// - Dawn is used as the WebGPU implementation via Emscripten's emdawnwebgpu port. + +// 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 + +#include "imgui.h" +#include "imgui_impl_emscripten.h" +#include "imgui_impl_wgpu.h" +#include +#include +#include +#include +#include +#include "../libs/emscripten/emscripten_mainloop_stub.h" +#include +#include + +#if !defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) +#error This example requires IMGUI_IMPL_WEBGPU_BACKEND_DAWN. +#endif + +// Data +static WGPUInstance wgpu_instance = nullptr; +static WGPUDevice wgpu_device = nullptr; +static WGPUSurface wgpu_surface = nullptr; +static WGPUQueue wgpu_queue = nullptr; +static WGPUSurfaceConfiguration wgpu_surface_configuration = {}; +static int wgpu_surface_width = 1280; +static int wgpu_surface_height = 800; + +// Forward declarations +static bool InitWGPU(); +static void ResizeSurface(int width, int height); +static WGPUAdapter RequestAdapter(wgpu::Instance& instance); +static WGPUDevice RequestDevice(wgpu::Instance& instance, wgpu::Adapter& adapter); + +static WGPUAdapter RequestAdapter(wgpu::Instance& instance) +{ + wgpu::Adapter acquired_adapter; + wgpu::RequestAdapterOptions adapter_options; + auto onRequestAdapter = [&](wgpu::RequestAdapterStatus status, wgpu::Adapter adapter, wgpu::StringView message) + { + if (status != wgpu::RequestAdapterStatus::Success) + { + printf("Failed to get an adapter: %s\n", message.data); + return; + } + acquired_adapter = std::move(adapter); + }; + + // Synchronously (wait until) acquire Adapter + wgpu::Future wait_adapter_func { instance.RequestAdapter(&adapter_options, wgpu::CallbackMode::WaitAnyOnly, onRequestAdapter) }; + wgpu::WaitStatus wait_status_adapter = instance.WaitAny(wait_adapter_func, UINT64_MAX); + IM_ASSERT(acquired_adapter != nullptr && wait_status_adapter == wgpu::WaitStatus::Success && "Error on Adapter request"); + return acquired_adapter.MoveToCHandle(); +} + +static WGPUDevice RequestDevice(wgpu::Instance& instance, wgpu::Adapter& adapter) +{ + // Set device callback functions + wgpu::DeviceDescriptor device_desc; + device_desc.SetDeviceLostCallback(wgpu::CallbackMode::AllowSpontaneous, + [](const wgpu::Device&, wgpu::DeviceLostReason type, wgpu::StringView msg) { fprintf(stderr, "%s error: %s\n", ImGui_ImplWGPU_GetDeviceLostReasonName((WGPUDeviceLostReason)type), msg.data); } + ); + device_desc.SetUncapturedErrorCallback( + [](const wgpu::Device&, wgpu::ErrorType type, wgpu::StringView msg) { fprintf(stderr, "%s error: %s\n", ImGui_ImplWGPU_GetErrorTypeName((WGPUErrorType)type), msg.data); } + ); + + wgpu::Device acquired_device; + auto onRequestDevice = [&](wgpu::RequestDeviceStatus status, wgpu::Device local_device, wgpu::StringView message) + { + if (status != wgpu::RequestDeviceStatus::Success) + { + printf("Failed to get a device: %s\n", message.data); + return; + } + acquired_device = std::move(local_device); + }; + + // Synchronously (wait until) get Device + wgpu::Future wait_device_func { adapter.RequestDevice(&device_desc, wgpu::CallbackMode::WaitAnyOnly, onRequestDevice) }; + wgpu::WaitStatus wait_status_device = instance.WaitAny(wait_device_func, UINT64_MAX); + IM_ASSERT(acquired_device != nullptr && wait_status_device == wgpu::WaitStatus::Success && "Error on Device request"); + return acquired_device.MoveToCHandle(); +} + +static bool InitWGPU() +{ + WGPUTextureFormat preferred_fmt = WGPUTextureFormat_Undefined; + + wgpu::InstanceDescriptor instance_desc = {}; + static constexpr wgpu::InstanceFeatureName timedWaitAny = wgpu::InstanceFeatureName::TimedWaitAny; + instance_desc.requiredFeatureCount = 1; + instance_desc.requiredFeatures = &timedWaitAny; + wgpu::Instance instance = wgpu::CreateInstance(&instance_desc); + + wgpu::Adapter adapter = RequestAdapter(instance); + ImGui_ImplWGPU_DebugPrintAdapterInfo(adapter.Get()); + + wgpu_device = RequestDevice(instance, adapter); + + wgpu::EmscriptenSurfaceSourceCanvasHTMLSelector canvas_desc = {}; + canvas_desc.selector = "#canvas"; + + wgpu::SurfaceDescriptor surface_desc = {}; + surface_desc.nextInChain = &canvas_desc; + wgpu_surface = instance.CreateSurface(&surface_desc).MoveToCHandle(); + if (!wgpu_surface) + return false; + + wgpu_instance = instance.MoveToCHandle(); + + WGPUSurfaceCapabilities surface_capabilities = {}; + wgpuSurfaceGetCapabilities(wgpu_surface, adapter.Get(), &surface_capabilities); + preferred_fmt = surface_capabilities.formats[0]; + + wgpu_surface_configuration.presentMode = WGPUPresentMode_Fifo; + wgpu_surface_configuration.alphaMode = WGPUCompositeAlphaMode_Auto; + wgpu_surface_configuration.usage = WGPUTextureUsage_RenderAttachment; + wgpu_surface_configuration.width = wgpu_surface_width; + wgpu_surface_configuration.height = wgpu_surface_height; + wgpu_surface_configuration.device = wgpu_device; + wgpu_surface_configuration.format = preferred_fmt; + + wgpuSurfaceConfigure(wgpu_surface, &wgpu_surface_configuration); + wgpu_queue = wgpuDeviceGetQueue(wgpu_device); + + return true; +} + +static void ResizeSurface(int width, int height) +{ + if (width <= 0 || height <= 0) return; + emscripten_set_canvas_element_size("#canvas", width, height); + wgpu_surface_configuration.width = wgpu_surface_width = width; + wgpu_surface_configuration.height = wgpu_surface_height = height; + wgpuSurfaceConfigure(wgpu_surface, &wgpu_surface_configuration); +} + +// Main code +int main(int, char**) +{ + if (!InitWGPU()) return 1; + + // Setup Dear ImGui context + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO& io{ImGui::GetIO()}; (void)io; + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls + io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls + + // Setup Dear ImGui style + ImGui::StyleColorsDark(); + //ImGui::StyleColorsLight(); + + // Setup Platform/Renderer backends + ImGui_ImplEmscripten_Init(); + if (io.DisplaySize.x > 0.0f && io.DisplaySize.y > 0.0f) + ResizeSurface((int)io.DisplaySize.x, (int)io.DisplaySize.y); + + ImGui_ImplWGPU_InitInfo init_info; + init_info.Device = wgpu_device; + init_info.NumFramesInFlight = 3; + init_info.RenderTargetFormat = wgpu_surface_configuration.format; + init_info.DepthStencilFormat = WGPUTextureFormat_Undefined; + ImGui_ImplWGPU_Init(&init_info); + + // Load Fonts + // - If fonts are not explicitly loaded, Dear ImGui will select an embedded font: either AddFontDefaultVector() or AddFontDefaultBitmap(). + // This selection is based on (style.FontSizeBase * style.FontScaleMain * style.FontScaleDpi) reaching a small threshold. + // - You can load multiple fonts and use ImGui::PushFont()/PopFont() to select them. + // - If a file cannot be loaded, AddFont functions will return a nullptr. Please handle those errors in your code (e.g. use an assertion, display an error and quit). + // - Read 'docs/FONTS.md' for more instructions and details. + // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use FreeType for higher quality font rendering. + // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! + // - Our Emscripten build process allows embedding fonts to be accessible at runtime from the "fonts/" folder. See Makefile.emscripten for details. + //style.FontSizeBase = 20.0f; + //io.Fonts->AddFontDefaultVector(); + //io.Fonts->AddFontDefaultBitmap(); +#ifndef IMGUI_DISABLE_FILE_FUNCTIONS + //io.Fonts->AddFontFromFileTTF("fonts/segoeui.ttf"); + //io.Fonts->AddFontFromFileTTF("fonts/DroidSans.ttf"); + //io.Fonts->AddFontFromFileTTF("fonts/Roboto-Medium.ttf"); + //io.Fonts->AddFontFromFileTTF("fonts/Cousine-Regular.ttf"); + //ImFont* font = io.Fonts->AddFontFromFileTTF("fonts/ArialUni.ttf"); + //IM_ASSERT(font != nullptr); +#endif + + // Our state + bool show_demo_window = true; + bool show_another_window = false; + ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); + + // Main loop + // For an Emscripten build we are disabling file-system access, so let's not attempt to do a fopen() of the imgui.ini file. + // You may manually call LoadIniSettingsFromMemory() to load settings from your own storage. + io.IniFilename = nullptr; + EMSCRIPTEN_MAINLOOP_BEGIN + { + // Input handling is callback-driven via imgui_impl_emscripten, so there is no event pump here. + + // React to changes in browser window size. + int width = (int)io.DisplaySize.x; + int height = (int)io.DisplaySize.y; + if (width <= 0 || height <= 0) + continue; + if (width != wgpu_surface_width || height != wgpu_surface_height) + ResizeSurface(width, height); + + // Check surface status for error. If texture is not optimal, try to reconfigure the surface. + WGPUSurfaceTexture surface_texture; + wgpuSurfaceGetCurrentTexture(wgpu_surface, &surface_texture); + if (ImGui_ImplWGPU_IsSurfaceStatusError(surface_texture.status)) + { + fprintf(stderr, "Unrecoverable Surface Texture status=%#.8x\n", surface_texture.status); + abort(); + } + if (ImGui_ImplWGPU_IsSurfaceStatusSubOptimal(surface_texture.status)) + { + if (surface_texture.texture) + wgpuTextureRelease(surface_texture.texture); + ResizeSurface(width, height); + continue; + } + + // Start the Dear ImGui frame + ImGui_ImplWGPU_NewFrame(); + ImGui_ImplEmscripten_NewFrame(); + ImGui::NewFrame(); + + // 1. Show the big demo window (Most of the sample code is in ImGui::ShowDemoWindow()! You can browse its code to learn more about Dear ImGui!). + if (show_demo_window) + ImGui::ShowDemoWindow(&show_demo_window); + + // 2. Show a simple window that we create ourselves. We use a Begin/End pair to create a named window. + { + static float f = 0.0f; + static int counter = 0; + + ImGui::Begin("Hello, world!"); // Create a window called "Hello, world!" and append into it. + + ImGui::Text("This is some useful text."); // Display some text (you can use a format strings too) + ImGui::Text("Platform backend: imgui_impl_emscripten"); + ImGui::Checkbox("Demo Window", &show_demo_window); // Edit bools storing our window open/close state + ImGui::Checkbox("Another Window", &show_another_window); + + ImGui::SliderFloat("float", &f, 0.0f, 1.0f); // Edit 1 float using a slider from 0.0f to 1.0f + ImGui::ColorEdit3("clear color", (float*)&clear_color); // Edit 3 floats representing a color + + if (ImGui::Button("Button")) // Buttons return true when clicked (most widgets return true when edited/activated) + counter++; + ImGui::SameLine(); + ImGui::Text("counter = %d", counter); + + ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); + ImGui::End(); + } + + // 3. Show another simple window. + if (show_another_window) + { + ImGui::Begin("Another Window", &show_another_window); // Pass a pointer to our bool variable (the window will have a closing button that will clear the bool when clicked) + ImGui::Text("Hello from another window!"); + if (ImGui::Button("Close Me")) + show_another_window = false; + ImGui::End(); + } + + // Rendering + ImGui::Render(); + + WGPUTextureViewDescriptor view_desc = {}; + view_desc.format = wgpu_surface_configuration.format; + view_desc.dimension = WGPUTextureViewDimension_2D; + view_desc.mipLevelCount = WGPU_MIP_LEVEL_COUNT_UNDEFINED; + view_desc.arrayLayerCount = WGPU_ARRAY_LAYER_COUNT_UNDEFINED; + view_desc.aspect = WGPUTextureAspect_All; + + WGPUTextureView texture_view = wgpuTextureCreateView(surface_texture.texture, &view_desc); + + WGPURenderPassColorAttachment color_attachments = {}; + color_attachments.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED; + color_attachments.loadOp = WGPULoadOp_Clear; + color_attachments.storeOp = WGPUStoreOp_Store; + color_attachments.clearValue = { clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w }; + color_attachments.view = texture_view; + + WGPURenderPassDescriptor render_pass_desc = {}; + render_pass_desc.colorAttachmentCount = 1; + render_pass_desc.colorAttachments = &color_attachments; + render_pass_desc.depthStencilAttachment = nullptr; + + WGPUCommandEncoderDescriptor enc_desc = {}; + WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(wgpu_device, &enc_desc); + + WGPURenderPassEncoder pass = wgpuCommandEncoderBeginRenderPass(encoder, &render_pass_desc); + ImGui_ImplWGPU_RenderDrawData(ImGui::GetDrawData(), pass); + wgpuRenderPassEncoderEnd(pass); + + WGPUCommandBufferDescriptor cmd_buffer_desc = {}; + WGPUCommandBuffer cmd_buffer = wgpuCommandEncoderFinish(encoder, &cmd_buffer_desc); + wgpuQueueSubmit(wgpu_queue, 1, &cmd_buffer); + + wgpuTextureViewRelease(texture_view); + wgpuRenderPassEncoderRelease(pass); + wgpuCommandEncoderRelease(encoder); + wgpuCommandBufferRelease(cmd_buffer); + } + EMSCRIPTEN_MAINLOOP_END; + + // Cleanup + ImGui_ImplWGPU_Shutdown(); + ImGui_ImplEmscripten_Shutdown(); + ImGui::DestroyContext(); + + wgpuSurfaceUnconfigure(wgpu_surface); + wgpuSurfaceRelease(wgpu_surface); + wgpuQueueRelease(wgpu_queue); + wgpuDeviceRelease(wgpu_device); + wgpuInstanceRelease(wgpu_instance); + + return 0; +} From 88fe8d401f684ddd6767272dffb1aafa0dbe6891 Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Tue, 31 Mar 2026 21:31:11 +0100 Subject: [PATCH 37/72] correct scaling independent of device pixel ratio --- backends/imgui_impl_emscripten.cpp | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index f9cefa0f4..659b99cdf 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -100,11 +100,13 @@ namespace { struct ImGui_ImplEmscripten_Data { + float DevicePixelRatio; emscripten_browser_cursor_internal::cursor CurrentCursor; char* CursorToRestore; ImGui_ImplEmscripten_Data() - : CurrentCursor{emscripten_browser_cursor_internal::cursor::invalid} + : DevicePixelRatio{1.0f} + , CurrentCursor{emscripten_browser_cursor_internal::cursor::invalid} , CursorToRestore{nullptr} { } @@ -130,17 +132,21 @@ void ImGui_ImplEmscripten_Init() io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // set up initial display size values - io.DisplaySize.x = static_cast(EM_ASM_INT(return window.innerWidth;)); - io.DisplaySize.y = static_cast(EM_ASM_INT(return window.innerHeight;)); + bd->DevicePixelRatio = static_cast(emscripten_get_device_pixel_ratio()); + io.DisplaySize.x = static_cast(EM_ASM_INT(return window.innerWidth;)) * bd->DevicePixelRatio; + io.DisplaySize.y = static_cast(EM_ASM_INT(return window.innerHeight;)) * bd->DevicePixelRatio; + io.DisplayFramebufferScale = ImVec2(1.0f, 1.0f); emscripten_set_mousemove_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, // target nullptr, // userData false, // useCapture [](int /*event_type*/, EmscriptenMouseEvent const* mouse_event, void* /*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_MOUSEMOVE + ImGui_ImplEmscripten_Data* bd{ImGui_ImplEmscripten_GetBackendData()}; + float const device_pixel_ratio{bd ? bd->DevicePixelRatio : 1.0f}; ImGui::GetIO().AddMousePosEvent( - static_cast(mouse_event->clientX), - static_cast(mouse_event->clientY) + static_cast(mouse_event->clientX) * device_pixel_ratio, + static_cast(mouse_event->clientY) * device_pixel_ratio ); return true; // the event was consumed } @@ -168,9 +174,11 @@ void ImGui_ImplEmscripten_Init() nullptr, // userData false, // useCapture [](int /*event_type*/, EmscriptenMouseEvent const* mouse_event, void* /*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_MOUSEENTER + ImGui_ImplEmscripten_Data* bd{ImGui_ImplEmscripten_GetBackendData()}; + float const device_pixel_ratio{bd ? bd->DevicePixelRatio : 1.0f}; ImGui::GetIO().AddMousePosEvent( - static_cast(mouse_event->clientX), - static_cast(mouse_event->clientY) + static_cast(mouse_event->clientX) * device_pixel_ratio, + static_cast(mouse_event->clientY) * device_pixel_ratio ); return true; // the event was consumed } @@ -298,8 +306,12 @@ void ImGui_ImplEmscripten_Init() false, // useCapture [](int /*event_type*/, EmscriptenUiEvent const* event, void* /*data*/) { // event_type == EMSCRIPTEN_EVENT_RESIZE ImGuiIO& io{ImGui::GetIO()}; - io.DisplaySize.x = static_cast(event->windowInnerWidth); - io.DisplaySize.y = static_cast(event->windowInnerHeight); + ImGui_ImplEmscripten_Data* bd{ImGui_ImplEmscripten_GetBackendData()}; + float const device_pixel_ratio{static_cast(emscripten_get_device_pixel_ratio())}; + if (bd != nullptr) bd->DevicePixelRatio = device_pixel_ratio; + io.DisplaySize.x = static_cast(event->windowInnerWidth) * device_pixel_ratio; + io.DisplaySize.y = static_cast(event->windowInnerHeight) * device_pixel_ratio; + io.DisplayFramebufferScale = ImVec2(1.0f, 1.0f); return true; // the event was consumed } ); From 117e14d5a5fc75c4dd63d6000535458d966f0ece Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Tue, 31 Mar 2026 22:46:35 +0100 Subject: [PATCH 38/72] user-adjustable target device pixel ratio --- backends/imgui_impl_emscripten.cpp | 60 +++++++++++++++++------------- backends/imgui_impl_emscripten.h | 4 ++ 2 files changed, 39 insertions(+), 25 deletions(-) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index 659b99cdf..e813a755d 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -4,6 +4,7 @@ // // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-03-31: Emscripten: Added configurable TargetDevicePixelRatio to control how browser device pixels map to Dear ImGui pixels. // 2026-03-31: Emscripten: Reworked KeyboardEvent.code translation to avoid std::unordered_map and added BrowserBack/Forward and F13-F24 mappings. // 2026-03-31: Emscripten: Moved cursor state into backend userdata and replaced cursor restore storage with owned C strings. // 2024-12-09: Inputs: Added special handling for modifier keys to also generate modifier key events. @@ -21,6 +22,8 @@ #include #include +float ImGui_ImplEmscripten_TargetDevicePixelRatio{1.0f}; + namespace { // W3C UI Events KeyboardEvent.code translation helpers @@ -100,18 +103,27 @@ namespace { struct ImGui_ImplEmscripten_Data { - float DevicePixelRatio; - emscripten_browser_cursor_internal::cursor CurrentCursor; - char* CursorToRestore; - - ImGui_ImplEmscripten_Data() - : DevicePixelRatio{1.0f} - , CurrentCursor{emscripten_browser_cursor_internal::cursor::invalid} - , CursorToRestore{nullptr} - { - } + float CssToImGuiScale{1.0f}; + emscripten_browser_cursor_internal::cursor CurrentCursor{emscripten_browser_cursor_internal::cursor::invalid}; + char* CursorToRestore{nullptr}; }; +float get_target_device_pixel_ratio() +{ + IM_ASSERT(ImGui_ImplEmscripten_TargetDevicePixelRatio > 0.0f && "ImGui_ImplEmscripten_TargetDevicePixelRatio must be positive."); + return ImGui_ImplEmscripten_TargetDevicePixelRatio > 0.0f ? ImGui_ImplEmscripten_TargetDevicePixelRatio : 1.0f; +} + +void update_display_properties(ImGuiIO& io, ImGui_ImplEmscripten_Data* bd, float css_width, float css_height) +{ + float const target_device_pixel_ratio{get_target_device_pixel_ratio()}; + float const css_to_imgui_scale{static_cast(emscripten_get_device_pixel_ratio()) / target_device_pixel_ratio}; + bd->CssToImGuiScale = css_to_imgui_scale; + io.DisplaySize.x = css_width * css_to_imgui_scale; + io.DisplaySize.y = css_height * css_to_imgui_scale; + io.DisplayFramebufferScale = ImVec2(target_device_pixel_ratio, target_device_pixel_ratio); +} + // Backend data stored in io.BackendPlatformUserData to allow support for multiple Dear ImGui contexts ImGui_ImplEmscripten_Data* ImGui_ImplEmscripten_GetBackendData() { @@ -132,10 +144,12 @@ void ImGui_ImplEmscripten_Init() io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // set up initial display size values - bd->DevicePixelRatio = static_cast(emscripten_get_device_pixel_ratio()); - io.DisplaySize.x = static_cast(EM_ASM_INT(return window.innerWidth;)) * bd->DevicePixelRatio; - io.DisplaySize.y = static_cast(EM_ASM_INT(return window.innerHeight;)) * bd->DevicePixelRatio; - io.DisplayFramebufferScale = ImVec2(1.0f, 1.0f); + update_display_properties( + io, + bd, + static_cast(EM_ASM_INT(return window.innerWidth;)), + static_cast(EM_ASM_INT(return window.innerHeight;)) + ); emscripten_set_mousemove_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, // target @@ -143,10 +157,10 @@ void ImGui_ImplEmscripten_Init() false, // useCapture [](int /*event_type*/, EmscriptenMouseEvent const* mouse_event, void* /*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_MOUSEMOVE ImGui_ImplEmscripten_Data* bd{ImGui_ImplEmscripten_GetBackendData()}; - float const device_pixel_ratio{bd ? bd->DevicePixelRatio : 1.0f}; + float const css_to_imgui_scale{bd ? bd->CssToImGuiScale : 1.0f}; ImGui::GetIO().AddMousePosEvent( - static_cast(mouse_event->clientX) * device_pixel_ratio, - static_cast(mouse_event->clientY) * device_pixel_ratio + static_cast(mouse_event->clientX) * css_to_imgui_scale, + static_cast(mouse_event->clientY) * css_to_imgui_scale ); return true; // the event was consumed } @@ -175,10 +189,10 @@ void ImGui_ImplEmscripten_Init() false, // useCapture [](int /*event_type*/, EmscriptenMouseEvent const* mouse_event, void* /*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_MOUSEENTER ImGui_ImplEmscripten_Data* bd{ImGui_ImplEmscripten_GetBackendData()}; - float const device_pixel_ratio{bd ? bd->DevicePixelRatio : 1.0f}; + float const css_to_imgui_scale{bd ? bd->CssToImGuiScale : 1.0f}; ImGui::GetIO().AddMousePosEvent( - static_cast(mouse_event->clientX) * device_pixel_ratio, - static_cast(mouse_event->clientY) * device_pixel_ratio + static_cast(mouse_event->clientX) * css_to_imgui_scale, + static_cast(mouse_event->clientY) * css_to_imgui_scale ); return true; // the event was consumed } @@ -307,11 +321,7 @@ void ImGui_ImplEmscripten_Init() [](int /*event_type*/, EmscriptenUiEvent const* event, void* /*data*/) { // event_type == EMSCRIPTEN_EVENT_RESIZE ImGuiIO& io{ImGui::GetIO()}; ImGui_ImplEmscripten_Data* bd{ImGui_ImplEmscripten_GetBackendData()}; - float const device_pixel_ratio{static_cast(emscripten_get_device_pixel_ratio())}; - if (bd != nullptr) bd->DevicePixelRatio = device_pixel_ratio; - io.DisplaySize.x = static_cast(event->windowInnerWidth) * device_pixel_ratio; - io.DisplaySize.y = static_cast(event->windowInnerHeight) * device_pixel_ratio; - io.DisplayFramebufferScale = ImVec2(1.0f, 1.0f); + if (bd != nullptr) update_display_properties(io, bd, static_cast(event->windowInnerWidth), static_cast(event->windowInnerHeight)); return true; // the event was consumed } ); diff --git a/backends/imgui_impl_emscripten.h b/backends/imgui_impl_emscripten.h index a0c1a7d7d..5b23023d5 100644 --- a/backends/imgui_impl_emscripten.h +++ b/backends/imgui_impl_emscripten.h @@ -40,6 +40,10 @@ #error The imgui_impl_emscripten backend requires Emscripten. #endif +// Controls how many device pixels Dear ImGui should target per Dear ImGui pixel. +// Default 1.0f gives 1:1 device-pixel rendering. Set before Init() if you want a different scaling policy. +extern IMGUI_IMPL_API float ImGui_ImplEmscripten_TargetDevicePixelRatio; + // Initialise the Emscripten backend, setting input callbacks. This should be called after ImGui::CreateContext(); IMGUI_IMPL_API void ImGui_ImplEmscripten_Init(); From 4c94c127faed8f2db8436c96d12e36b75cd21472 Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Tue, 31 Mar 2026 22:49:38 +0100 Subject: [PATCH 39/72] tidy up example formatting --- examples/example_emscripten_wgpu/main.cpp | 100 ++++++++++++---------- 1 file changed, 56 insertions(+), 44 deletions(-) diff --git a/examples/example_emscripten_wgpu/main.cpp b/examples/example_emscripten_wgpu/main.cpp index b509030a8..e5f916c76 100644 --- a/examples/example_emscripten_wgpu/main.cpp +++ b/examples/example_emscripten_wgpu/main.cpp @@ -26,16 +26,17 @@ #endif // Data -static WGPUInstance wgpu_instance = nullptr; -static WGPUDevice wgpu_device = nullptr; -static WGPUSurface wgpu_surface = nullptr; -static WGPUQueue wgpu_queue = nullptr; -static WGPUSurfaceConfiguration wgpu_surface_configuration = {}; +static WGPUInstance wgpu_instance{nullptr}; +static WGPUDevice wgpu_device{nullptr}; +static WGPUSurface wgpu_surface{nullptr}; +static WGPUQueue wgpu_queue{nullptr}; +static WGPUSurfaceConfiguration wgpu_surface_configuration{}; static int wgpu_surface_width = 1280; static int wgpu_surface_height = 800; // Forward declarations static bool InitWGPU(); +static void GetFramebufferSizeFromDisplaySize(int width, int height, int* framebuffer_width, int* framebuffer_height); static void ResizeSurface(int width, int height); static WGPUAdapter RequestAdapter(wgpu::Instance& instance); static WGPUDevice RequestDevice(wgpu::Instance& instance, wgpu::Adapter& adapter); @@ -92,31 +93,30 @@ static WGPUDevice RequestDevice(wgpu::Instance& instance, wgpu::Adapter& adapter static bool InitWGPU() { - WGPUTextureFormat preferred_fmt = WGPUTextureFormat_Undefined; + WGPUTextureFormat preferred_fmt{WGPUTextureFormat_Undefined}; - wgpu::InstanceDescriptor instance_desc = {}; + wgpu::InstanceDescriptor instance_desc{}; static constexpr wgpu::InstanceFeatureName timedWaitAny = wgpu::InstanceFeatureName::TimedWaitAny; instance_desc.requiredFeatureCount = 1; instance_desc.requiredFeatures = &timedWaitAny; - wgpu::Instance instance = wgpu::CreateInstance(&instance_desc); + wgpu::Instance instance{wgpu::CreateInstance(&instance_desc)}; wgpu::Adapter adapter = RequestAdapter(instance); ImGui_ImplWGPU_DebugPrintAdapterInfo(adapter.Get()); wgpu_device = RequestDevice(instance, adapter); - wgpu::EmscriptenSurfaceSourceCanvasHTMLSelector canvas_desc = {}; + wgpu::EmscriptenSurfaceSourceCanvasHTMLSelector canvas_desc{}; canvas_desc.selector = "#canvas"; - wgpu::SurfaceDescriptor surface_desc = {}; + wgpu::SurfaceDescriptor surface_desc{}; surface_desc.nextInChain = &canvas_desc; wgpu_surface = instance.CreateSurface(&surface_desc).MoveToCHandle(); - if (!wgpu_surface) - return false; + if (!wgpu_surface) return false; wgpu_instance = instance.MoveToCHandle(); - WGPUSurfaceCapabilities surface_capabilities = {}; + WGPUSurfaceCapabilities surface_capabilities{}; wgpuSurfaceGetCapabilities(wgpu_surface, adapter.Get(), &surface_capabilities); preferred_fmt = surface_capabilities.formats[0]; @@ -134,12 +134,25 @@ static bool InitWGPU() return true; } +static void GetFramebufferSizeFromDisplaySize(int width, int height, int* framebuffer_width, int* framebuffer_height) +{ + ImGuiIO& io{ImGui::GetIO()}; + *framebuffer_width = static_cast(width * io.DisplayFramebufferScale.x + 0.5f); + *framebuffer_height = static_cast(height * io.DisplayFramebufferScale.y + 0.5f); +} + static void ResizeSurface(int width, int height) { if (width <= 0 || height <= 0) return; - emscripten_set_canvas_element_size("#canvas", width, height); - wgpu_surface_configuration.width = wgpu_surface_width = width; - wgpu_surface_configuration.height = wgpu_surface_height = height; + + int framebuffer_width{0}; + int framebuffer_height{0}; + GetFramebufferSizeFromDisplaySize(width, height, &framebuffer_width, &framebuffer_height); + if (framebuffer_width <= 0 || framebuffer_height <= 0) return; + + emscripten_set_canvas_element_size("#canvas", framebuffer_width, framebuffer_height); + wgpu_surface_configuration.width = wgpu_surface_width = framebuffer_width; + wgpu_surface_configuration.height = wgpu_surface_height = framebuffer_height; wgpuSurfaceConfigure(wgpu_surface, &wgpu_surface_configuration); } @@ -152,8 +165,8 @@ int main(int, char**) IMGUI_CHECKVERSION(); ImGui::CreateContext(); ImGuiIO& io{ImGui::GetIO()}; (void)io; - io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls - io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls + io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls // Setup Dear ImGui style ImGui::StyleColorsDark(); @@ -161,8 +174,7 @@ int main(int, char**) // Setup Platform/Renderer backends ImGui_ImplEmscripten_Init(); - if (io.DisplaySize.x > 0.0f && io.DisplaySize.y > 0.0f) - ResizeSurface((int)io.DisplaySize.x, (int)io.DisplaySize.y); + if (io.DisplaySize.x > 0.0f && io.DisplaySize.y > 0.0f) ResizeSurface((int)io.DisplaySize.x, (int)io.DisplaySize.y); ImGui_ImplWGPU_InitInfo init_info; init_info.Device = wgpu_device; @@ -193,9 +205,9 @@ int main(int, char**) #endif // Our state - bool show_demo_window = true; - bool show_another_window = false; - ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); + bool show_demo_window{true}; + bool show_another_window{false}; + ImVec4 clear_color{0.45f, 0.55f, 0.60f, 1.00f}; // Main loop // For an Emscripten build we are disabling file-system access, so let's not attempt to do a fopen() of the imgui.ini file. @@ -210,8 +222,11 @@ int main(int, char**) int height = (int)io.DisplaySize.y; if (width <= 0 || height <= 0) continue; - if (width != wgpu_surface_width || height != wgpu_surface_height) - ResizeSurface(width, height); + + int framebuffer_width{0}; + int framebuffer_height{0}; + GetFramebufferSizeFromDisplaySize(width, height, &framebuffer_width, &framebuffer_height); + if (framebuffer_width != wgpu_surface_width || framebuffer_height != wgpu_surface_height) ResizeSurface(width, height); // Check surface status for error. If texture is not optimal, try to reconfigure the surface. WGPUSurfaceTexture surface_texture; @@ -223,8 +238,7 @@ int main(int, char**) } if (ImGui_ImplWGPU_IsSurfaceStatusSubOptimal(surface_texture.status)) { - if (surface_texture.texture) - wgpuTextureRelease(surface_texture.texture); + if (surface_texture.texture) wgpuTextureRelease(surface_texture.texture); ResizeSurface(width, height); continue; } @@ -240,21 +254,20 @@ int main(int, char**) // 2. Show a simple window that we create ourselves. We use a Begin/End pair to create a named window. { - static float f = 0.0f; - static int counter = 0; + static float f{0.0f}; + static int counter{0}; - ImGui::Begin("Hello, world!"); // Create a window called "Hello, world!" and append into it. + ImGui::Begin("Hello, world!"); // Create a window called "Hello, world!" and append into it. - ImGui::Text("This is some useful text."); // Display some text (you can use a format strings too) + ImGui::Text("This is some useful text."); // Display some text (you can use a format strings too) ImGui::Text("Platform backend: imgui_impl_emscripten"); - ImGui::Checkbox("Demo Window", &show_demo_window); // Edit bools storing our window open/close state + ImGui::Checkbox("Demo Window", &show_demo_window); // Edit bools storing our window open/close state ImGui::Checkbox("Another Window", &show_another_window); - ImGui::SliderFloat("float", &f, 0.0f, 1.0f); // Edit 1 float using a slider from 0.0f to 1.0f - ImGui::ColorEdit3("clear color", (float*)&clear_color); // Edit 3 floats representing a color + ImGui::SliderFloat("float", &f, 0.0f, 1.0f); // Edit 1 float using a slider from 0.0f to 1.0f + ImGui::ColorEdit3("clear color", (float*)&clear_color); // Edit 3 floats representing a color - if (ImGui::Button("Button")) // Buttons return true when clicked (most widgets return true when edited/activated) - counter++; + if (ImGui::Button("Button")) ++counter; // Buttons return true when clicked (most widgets return true when edited/activated) ImGui::SameLine(); ImGui::Text("counter = %d", counter); @@ -265,17 +278,16 @@ int main(int, char**) // 3. Show another simple window. if (show_another_window) { - ImGui::Begin("Another Window", &show_another_window); // Pass a pointer to our bool variable (the window will have a closing button that will clear the bool when clicked) + ImGui::Begin("Another Window", &show_another_window); // Pass a pointer to our bool variable (the window will have a closing button that will clear the bool when clicked) ImGui::Text("Hello from another window!"); - if (ImGui::Button("Close Me")) - show_another_window = false; + if (ImGui::Button("Close Me")) show_another_window = false; ImGui::End(); } // Rendering ImGui::Render(); - WGPUTextureViewDescriptor view_desc = {}; + WGPUTextureViewDescriptor view_desc{}; view_desc.format = wgpu_surface_configuration.format; view_desc.dimension = WGPUTextureViewDimension_2D; view_desc.mipLevelCount = WGPU_MIP_LEVEL_COUNT_UNDEFINED; @@ -284,26 +296,26 @@ int main(int, char**) WGPUTextureView texture_view = wgpuTextureCreateView(surface_texture.texture, &view_desc); - WGPURenderPassColorAttachment color_attachments = {}; + WGPURenderPassColorAttachment color_attachments{}; color_attachments.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED; color_attachments.loadOp = WGPULoadOp_Clear; color_attachments.storeOp = WGPUStoreOp_Store; color_attachments.clearValue = { clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w }; color_attachments.view = texture_view; - WGPURenderPassDescriptor render_pass_desc = {}; + WGPURenderPassDescriptor render_pass_desc{}; render_pass_desc.colorAttachmentCount = 1; render_pass_desc.colorAttachments = &color_attachments; render_pass_desc.depthStencilAttachment = nullptr; - WGPUCommandEncoderDescriptor enc_desc = {}; + WGPUCommandEncoderDescriptor enc_desc{}; WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(wgpu_device, &enc_desc); WGPURenderPassEncoder pass = wgpuCommandEncoderBeginRenderPass(encoder, &render_pass_desc); ImGui_ImplWGPU_RenderDrawData(ImGui::GetDrawData(), pass); wgpuRenderPassEncoderEnd(pass); - WGPUCommandBufferDescriptor cmd_buffer_desc = {}; + WGPUCommandBufferDescriptor cmd_buffer_desc{}; WGPUCommandBuffer cmd_buffer = wgpuCommandEncoderFinish(encoder, &cmd_buffer_desc); wgpuQueueSubmit(wgpu_queue, 1, &cmd_buffer); From f9aa76fe583135c1c68ae8a21ae1eba9fd6ca1f3 Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Tue, 31 Mar 2026 22:49:45 +0100 Subject: [PATCH 40/72] add example artifacts to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 1aedcf779..e853afbd9 100644 --- a/.gitignore +++ b/.gitignore @@ -43,6 +43,7 @@ examples/*/*.dSYM examples/*.o.tmp examples/*.out.js examples/*.out.wasm +examples/example_emscripten_wgpu/web/* examples/example_glfw_opengl3/web/* examples/example_glfw_wgpu/web/* examples/example_glfw_wgpu/external/* From 34f61cf7e37efc1a8274001c3290dbb7c0195655 Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Thu, 2 Apr 2026 21:43:06 +0100 Subject: [PATCH 41/72] add new backend to compatible list in wgpu comments --- backends/imgui_impl_wgpu.cpp | 4 ++-- backends/imgui_impl_wgpu.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/backends/imgui_impl_wgpu.cpp b/backends/imgui_impl_wgpu.cpp index 86bb36cc3..7c61c50e9 100644 --- a/backends/imgui_impl_wgpu.cpp +++ b/backends/imgui_impl_wgpu.cpp @@ -1,6 +1,6 @@ // dear imgui: Renderer for WebGPU -// This needs to be used along with a Platform Binding (e.g. GLFW, SDL2, SDL3) -// (Please note that WebGPU is a recent API, may not be supported by all browser, and its ecosystem is generally a mess) +// This needs to be used along with a Platform Binding (e.g. GLFW, SDL2, SDL3, Emscripten) +// (Please note that WebGPU is a recent API, may not be supported by all browsers, and its ecosystem is generally a mess) // Implemented features: // [X] Renderer: User texture binding. Use 'WGPUTextureView' as ImTextureID. Read the FAQ about ImTextureID/ImTextureRef! diff --git a/backends/imgui_impl_wgpu.h b/backends/imgui_impl_wgpu.h index c06c25f8a..813d7ae1a 100644 --- a/backends/imgui_impl_wgpu.h +++ b/backends/imgui_impl_wgpu.h @@ -1,6 +1,6 @@ // dear imgui: Renderer for WebGPU -// This needs to be used along with a Platform Binding (e.g. GLFW, SDL2, SDL3) -// (Please note that WebGPU is a recent API, may not be supported by all browser, and its ecosystem is generally a mess) +// This needs to be used along with a Platform Binding (e.g. GLFW, SDL2, SDL3, Emscripten) +// (Please note that WebGPU is a recent API, may not be supported by all browsers, and its ecosystem is generally a mess) // When targeting native platforms: // - One of IMGUI_IMPL_WEBGPU_BACKEND_DAWN, IMGUI_IMPL_WEBGPU_BACKEND_WGPU or IMGUI_IMPL_WEBGPU_BACKEND_WGVK *must* be provided. From 8fa2322760cdb653c76707d2ce6fbbd45be09232 Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Thu, 2 Apr 2026 22:24:52 +0100 Subject: [PATCH 42/72] use imgui string hashing for key name to imgui key lookup --- backends/imgui_impl_emscripten.cpp | 582 +++++++++++------------------ 1 file changed, 219 insertions(+), 363 deletions(-) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index e813a755d..f651f1299 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -4,8 +4,9 @@ // // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-04-02: Inputs: Replaced custom KeyboardEvent.code parser with ImHashStr()/ImGuiStorage lookup to match Dear ImGui backend style. // 2026-03-31: Emscripten: Added configurable TargetDevicePixelRatio to control how browser device pixels map to Dear ImGui pixels. -// 2026-03-31: Emscripten: Reworked KeyboardEvent.code translation to avoid std::unordered_map and added BrowserBack/Forward and F13-F24 mappings. +// 2026-03-31: Inputs: Added BrowserBack/Forward and F13-F24 key mappings. // 2026-03-31: Emscripten: Moved cursor state into backend userdata and replaced cursor restore storage with owned C strings. // 2024-12-09: Inputs: Added special handling for modifier keys to also generate modifier key events. // 2024-12-08: Inputs: Prevent "Delete" key from getting printed in text input. @@ -20,7 +21,8 @@ #include "imgui_impl_emscripten.h" #include #include -#include + +extern ImGuiID ImHashStr(char const* data, size_t data_size = 0, ImGuiID seed = 0); // Declared in imgui_internal.h. float ImGui_ImplEmscripten_TargetDevicePixelRatio{1.0f}; @@ -28,8 +30,7 @@ namespace { // W3C UI Events KeyboardEvent.code translation helpers -ImGuiKey translate_numpad_key(char const* emscripten_key, size_t key_len) __attribute__((__pure__)); -ImGuiKey translate_key(char const* emscripten_key) __attribute__((__pure__)); +ImGuiKey translate_key(char const* emscripten_key); constexpr ImGuiMouseButton translate_mousebutton(unsigned short emscripten_button) __attribute__((__const__)); } // anonymous namespace @@ -547,12 +548,6 @@ void set(char const* new_cursor) namespace { -static_assert(static_cast(ImGuiKey_Z) == static_cast(ImGuiKey_A) + 25, ""); -static_assert(static_cast(ImGuiKey_9) == static_cast(ImGuiKey_0) + 9, ""); -static_assert(static_cast(ImGuiKey_F12) == static_cast(ImGuiKey_F1) + 11, ""); -static_assert(static_cast(ImGuiKey_F24) == static_cast(ImGuiKey_F1) + 23, ""); -static_assert(static_cast(ImGuiKey_Keypad9) == static_cast(ImGuiKey_Keypad0) + 9, ""); - constexpr ImGuiMouseButton translate_mousebutton(unsigned short emscripten_button) { // Translate an emscripten-provided integer describing a mouse button to an imgui mouse button @@ -562,367 +557,228 @@ constexpr ImGuiMouseButton translate_mousebutton(unsigned short emscripten_butto return emscripten_button; // any other button translates 1:1 } -ImGuiKey translate_numpad_key(char const* emscripten_key, size_t key_len) +ImGuiStorage const& get_key_translation_storage() { - // W3C UI Events, "Numpad section" code values. - // These optional W3C numpad codes currently have no ImGuiKey equivalent: - // "NumpadClear", "NumpadClearEntry", "NumpadMemoryAdd", "NumpadMemoryClear", - // "NumpadMemoryRecall", "NumpadMemoryStore", "NumpadMemorySubtract" - switch (emscripten_key[6]) - { - case 'A': return ImGuiKey_KeypadAdd; // "NumpadAdd" - case 'B': return ImGuiKey_Backspace; // "NumpadBackspace": closest editing equivalent - case 'C': - switch (key_len) - { - case 11: - if (emscripten_key[7] == 'o') return ImGuiKey_KeypadDecimal; // "NumpadComma": Dear ImGui only exposes KeypadDecimal - // if (emscripten_key[7] == 'l') return ImGuiKey_None; // "NumpadClear" - break; - // case 16: return ImGuiKey_None; // "NumpadClearEntry" - } - break; - case 'D': - switch (key_len) - { - case 12: return ImGuiKey_KeypadDivide; // "NumpadDivide" - case 13: return ImGuiKey_KeypadDecimal; // "NumpadDecimal" - } - break; - case 'E': - switch (emscripten_key[7]) - { - case 'n': return ImGuiKey_KeypadEnter; // "NumpadEnter" - case 'q': return ImGuiKey_KeypadEqual; // "NumpadEqual" - } - break; - case 'H': return ImGuiKey_Backslash; // "NumpadHash": closest printable equivalent for telephone-style '#' - case 'M': - switch (key_len) - { - case 14: return ImGuiKey_KeypadMultiply; // "NumpadMultiply" - // case 15: return ImGuiKey_None; // "NumpadMemoryAdd" - case 17: - // switch (emscripten_key[12]) - // { - // case 'C': return ImGuiKey_None; // "NumpadMemoryClear" - // case 'S': return ImGuiKey_None; // "NumpadMemoryStore" - // } - break; - // case 18: return ImGuiKey_None; // "NumpadMemoryRecall" - } - break; - case 'P': - switch (key_len) - { - case 15: return ImGuiKey_LeftBracket; // "NumpadParenLeft": closest available bracket key - case 16: return ImGuiKey_RightBracket; // "NumpadParenRight": closest available bracket key - } - break; - case 'S': - switch (key_len) - { - case 10: return ImGuiKey_KeypadMultiply; // "NumpadStar": phone/remote '*' behaves like keypad multiply - case 14: return ImGuiKey_KeypadSubtract; // "NumpadSubtract" - // case 20: return ImGuiKey_None; // "NumpadMemorySubtract" - } - break; - default: return static_cast(ImGuiKey_Keypad0 + (emscripten_key[6] - '0')); // Remaining canonical "Numpad..." codes here are "Numpad0" .. "Numpad9" - } - return ImGuiKey_None; + static ImGuiStorage storage; + static bool is_initialized{false}; + if (is_initialized) return storage; + is_initialized = true; + storage.Data.reserve(128); + + // main character keys + storage.Data.push_back(ImGuiStoragePair(ImHashStr("Backquote"), ImGuiKey_GraveAccent)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("Backslash"), ImGuiKey_Backslash)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("BracketLeft"), ImGuiKey_LeftBracket)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("BracketRight"), ImGuiKey_RightBracket)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("Comma"), ImGuiKey_Comma)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("Digit0"), ImGuiKey_0)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("Digit1"), ImGuiKey_1)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("Digit2"), ImGuiKey_2)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("Digit3"), ImGuiKey_3)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("Digit4"), ImGuiKey_4)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("Digit5"), ImGuiKey_5)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("Digit6"), ImGuiKey_6)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("Digit7"), ImGuiKey_7)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("Digit8"), ImGuiKey_8)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("Digit9"), ImGuiKey_9)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("Equal"), ImGuiKey_Equal)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("IntlBackslash"), ImGuiKey_Backslash)); // Mapping to generic backslash + storage.Data.push_back(ImGuiStoragePair(ImHashStr("IntlRo"), ImGuiKey_Slash)); // Closest match for non-standard layouts + storage.Data.push_back(ImGuiStoragePair(ImHashStr("IntlYen"), ImGuiKey_Backslash)); // Closest match for non-standard layouts + storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyA"), ImGuiKey_A)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyB"), ImGuiKey_B)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyC"), ImGuiKey_C)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyD"), ImGuiKey_D)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyE"), ImGuiKey_E)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyF"), ImGuiKey_F)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyG"), ImGuiKey_G)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyH"), ImGuiKey_H)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyI"), ImGuiKey_I)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyJ"), ImGuiKey_J)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyK"), ImGuiKey_K)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyL"), ImGuiKey_L)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyM"), ImGuiKey_M)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyN"), ImGuiKey_N)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyO"), ImGuiKey_O)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyP"), ImGuiKey_P)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyQ"), ImGuiKey_Q)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyR"), ImGuiKey_R)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyS"), ImGuiKey_S)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyT"), ImGuiKey_T)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyU"), ImGuiKey_U)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyV"), ImGuiKey_V)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyW"), ImGuiKey_W)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyX"), ImGuiKey_X)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyY"), ImGuiKey_Y)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyZ"), ImGuiKey_Z)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("Minus"), ImGuiKey_Minus)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("Period"), ImGuiKey_Period)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("Quote"), ImGuiKey_Apostrophe)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("Semicolon"), ImGuiKey_Semicolon)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("Slash"), ImGuiKey_Slash)); + + // control keys + storage.Data.push_back(ImGuiStoragePair(ImHashStr("AltLeft"), ImGuiKey_LeftAlt)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("AltRight"), ImGuiKey_RightAlt)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("Backspace"), ImGuiKey_Backspace)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("CapsLock"), ImGuiKey_CapsLock)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("ContextMenu"), ImGuiKey_Menu)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("ControlLeft"), ImGuiKey_LeftCtrl)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("ControlRight"), ImGuiKey_RightCtrl)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("Enter"), ImGuiKey_Enter)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("MetaLeft"), ImGuiKey_LeftSuper)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("MetaRight"), ImGuiKey_RightSuper)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("ShiftLeft"), ImGuiKey_LeftShift)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("ShiftRight"), ImGuiKey_RightShift)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("Space"), ImGuiKey_Space)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("Tab"), ImGuiKey_Tab)); + + // navigation key group + storage.Data.push_back(ImGuiStoragePair(ImHashStr("Delete"), ImGuiKey_Delete)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("End"), ImGuiKey_End)); + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Help"), ImGuiKey_PrintScreen)); // Best approximation + storage.Data.push_back(ImGuiStoragePair(ImHashStr("Home"), ImGuiKey_Home)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("Insert"), ImGuiKey_Insert)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("PageDown"), ImGuiKey_PageDown)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("PageUp"), ImGuiKey_PageUp)); + + // arrow key group + storage.Data.push_back(ImGuiStoragePair(ImHashStr("ArrowDown"), ImGuiKey_DownArrow)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("ArrowLeft"), ImGuiKey_LeftArrow)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("ArrowRight"), ImGuiKey_RightArrow)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("ArrowUp"), ImGuiKey_UpArrow)); + + // browser key group + storage.Data.push_back(ImGuiStoragePair(ImHashStr("BrowserBack"), ImGuiKey_AppBack)); // Pass through so the embedding app can decide + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("BrowserFavorites"), ImGuiKey_None)); // No direct mapping + storage.Data.push_back(ImGuiStoragePair(ImHashStr("BrowserForward"), ImGuiKey_AppForward)); // Pass through so the embedding app can decide + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("BrowserHome"), ImGuiKey_None)); // No direct mapping + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("BrowserRefresh"), ImGuiKey_None)); // No direct mapping + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("BrowserSearch"), ImGuiKey_None)); // No direct mapping + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("BrowserStop"), ImGuiKey_None)); // No direct mapping + + // number pad group + storage.Data.push_back(ImGuiStoragePair(ImHashStr("NumLock"), ImGuiKey_NumLock)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("Numpad0"), ImGuiKey_Keypad0)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("Numpad1"), ImGuiKey_Keypad1)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("Numpad2"), ImGuiKey_Keypad2)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("Numpad3"), ImGuiKey_Keypad3)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("Numpad4"), ImGuiKey_Keypad4)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("Numpad5"), ImGuiKey_Keypad5)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("Numpad6"), ImGuiKey_Keypad6)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("Numpad7"), ImGuiKey_Keypad7)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("Numpad8"), ImGuiKey_Keypad8)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("Numpad9"), ImGuiKey_Keypad9)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("NumpadAdd"), ImGuiKey_KeypadAdd)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("NumpadBackspace"), ImGuiKey_Backspace)); // No direct mapping; backspace functionality + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("NumpadClear"), ImGuiKey_None)); // No defined Dear ImGui mapping + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("NumpadClearEntry"), ImGuiKey_None)); // No defined Dear ImGui mapping + storage.Data.push_back(ImGuiStoragePair(ImHashStr("NumpadComma"), ImGuiKey_KeypadDecimal)); // Closest match + storage.Data.push_back(ImGuiStoragePair(ImHashStr("NumpadDecimal"), ImGuiKey_KeypadDecimal)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("NumpadDivide"), ImGuiKey_KeypadDivide)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("NumpadEnter"), ImGuiKey_KeypadEnter)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("NumpadEqual"), ImGuiKey_KeypadEqual)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("NumpadHash"), ImGuiKey_Backslash)); // Mapped to generic backslash for telephone-style '#' + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("NumpadMemoryAdd"), ImGuiKey_None)); // No defined mapping + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("NumpadMemoryClear"), ImGuiKey_None)); // No defined mapping + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("NumpadMemoryRecall"), ImGuiKey_None)); // No defined mapping + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("NumpadMemoryStore"), ImGuiKey_None)); // No defined mapping + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("NumpadMemorySubtract"), ImGuiKey_None)); // No defined mapping + storage.Data.push_back(ImGuiStoragePair(ImHashStr("NumpadMultiply"), ImGuiKey_KeypadMultiply)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("NumpadParenLeft"), ImGuiKey_LeftBracket)); // Closest available + storage.Data.push_back(ImGuiStoragePair(ImHashStr("NumpadParenRight"), ImGuiKey_RightBracket)); // Closest available + storage.Data.push_back(ImGuiStoragePair(ImHashStr("NumpadStar"), ImGuiKey_KeypadMultiply)); // Same as multiply + storage.Data.push_back(ImGuiStoragePair(ImHashStr("NumpadSubtract"), ImGuiKey_KeypadSubtract)); + + // top row key group + storage.Data.push_back(ImGuiStoragePair(ImHashStr("Escape"), ImGuiKey_Escape)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("F1"), ImGuiKey_F1)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("F2"), ImGuiKey_F2)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("F3"), ImGuiKey_F3)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("F4"), ImGuiKey_F4)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("F5"), ImGuiKey_F5)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("F6"), ImGuiKey_F6)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("F7"), ImGuiKey_F7)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("F8"), ImGuiKey_F8)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("F9"), ImGuiKey_F9)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("F10"), ImGuiKey_F10)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("F11"), ImGuiKey_F11)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("F12"), ImGuiKey_F12)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("F13"), ImGuiKey_F13)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("F14"), ImGuiKey_F14)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("F15"), ImGuiKey_F15)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("F16"), ImGuiKey_F16)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("F17"), ImGuiKey_F17)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("F18"), ImGuiKey_F18)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("F19"), ImGuiKey_F19)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("F20"), ImGuiKey_F20)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("F21"), ImGuiKey_F21)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("F22"), ImGuiKey_F22)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("F23"), ImGuiKey_F23)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("F24"), ImGuiKey_F24)); + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Fn"), ImGuiKey_None)); // No direct mapping + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("FnLock"), ImGuiKey_None)); // No direct mapping + storage.Data.push_back(ImGuiStoragePair(ImHashStr("PrintScreen"), ImGuiKey_PrintScreen)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("ScrollLock"), ImGuiKey_ScrollLock)); + storage.Data.push_back(ImGuiStoragePair(ImHashStr("Pause"), ImGuiKey_Pause)); + + // clipboard/editing keys without direct mapping + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Abort"), ImGuiKey_None)); + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Again"), ImGuiKey_None)); + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Convert"), ImGuiKey_None)); + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Copy"), ImGuiKey_None)); + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Cut"), ImGuiKey_None)); + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Find"), ImGuiKey_None)); + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Open"), ImGuiKey_None)); + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Paste"), ImGuiKey_None)); + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Props"), ImGuiKey_None)); + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Resume"), ImGuiKey_None)); + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Select"), ImGuiKey_None)); + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Undo"), ImGuiKey_None)); + + // IME and international keys without direct mapping + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Hiragana"), ImGuiKey_None)); + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("KanaMode"), ImGuiKey_None)); + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Katakana"), ImGuiKey_None)); + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Lang1"), ImGuiKey_None)); + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Lang2"), ImGuiKey_None)); + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("NonConvert"), ImGuiKey_None)); + + // media and launcher keys without direct mapping + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("AudioVolumeDown"), ImGuiKey_None)); + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("AudioVolumeMute"), ImGuiKey_None)); + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("AudioVolumeUp"), ImGuiKey_None)); + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("LaunchApp1"), ImGuiKey_None)); + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("LaunchApp2"), ImGuiKey_None)); + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("LaunchMail"), ImGuiKey_None)); + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("MediaPlayPause"), ImGuiKey_None)); + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("MediaSelect"), ImGuiKey_None)); + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("MediaStop"), ImGuiKey_None)); + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("MediaTrackNext"), ImGuiKey_None)); + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("MediaTrackPrevious"), ImGuiKey_None)); + + // system keys without direct mapping + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Eject"), ImGuiKey_None)); + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Hyper"), ImGuiKey_None)); + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Power"), ImGuiKey_None)); + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Sleep"), ImGuiKey_None)); + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Super"), ImGuiKey_None)); + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Suspend"), ImGuiKey_None)); + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Turbo"), ImGuiKey_None)); + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Unidentified"), ImGuiKey_None)); + //storage.Data.push_back(ImGuiStoragePair(ImHashStr("WakeUp"), ImGuiKey_None)); + + storage.BuildSortByKey(); + return storage; } ImGuiKey translate_key(char const* emscripten_key) { // Translate a W3C KeyboardEvent.code string into an ImGuiKey. - // Canonical references: W3C UI Events code tables, with MDN used for browser quirks/aliases. if (emscripten_key == nullptr || emscripten_key[0] == '\0') return ImGuiKey_None; - size_t const key_len{strlen(emscripten_key)}; - - switch (emscripten_key[0]) - { - case 'A': - switch (key_len) - { - // case 5: - // switch (emscripten_key[1]) - // { - // case 'b': return ImGuiKey_None; // "Abort" - // case 'g': return ImGuiKey_None; // "Again" - // } - // break; - case 7: - switch (emscripten_key[1]) - { - case 'l': return ImGuiKey_LeftAlt; // "AltLeft" - case 'r': return ImGuiKey_UpArrow; // "ArrowUp" - } - break; - case 8: return ImGuiKey_RightAlt; // "AltRight" - case 9: - switch (emscripten_key[5]) - { - case 'D': return ImGuiKey_DownArrow; // "ArrowDown" - case 'L': return ImGuiKey_LeftArrow; // "ArrowLeft" - } - break; - case 10: return ImGuiKey_RightArrow; // "ArrowRight" - // case 13: return ImGuiKey_None; // "AudioVolumeUp" - // case 15: - // switch (emscripten_key[11]) - // { - // case 'D': return ImGuiKey_None; // "AudioVolumeDown" - // case 'M': return ImGuiKey_None; // "AudioVolumeMute" - // } - // break; - } - break; - case 'B': - switch (key_len) - { - case 9: - switch (emscripten_key[4]) - { - case 'q': return ImGuiKey_GraveAccent; // "Backquote" - case 's': - if (emscripten_key[5] == 'l') return ImGuiKey_Backslash; // "Backslash": W3C uses this for both US \| and the UK #~ key tucked under Enter - if (emscripten_key[5] == 'p') return ImGuiKey_Backspace; // "Backspace" - break; - } - break; - case 11: - if (emscripten_key[3] == 'c') return ImGuiKey_LeftBracket; // "BracketLeft" - switch (emscripten_key[7]) - { - case 'B': return ImGuiKey_AppBack; // "BrowserBack": pass through even in browser hosts so the embedding app can decide - // case 'H': return ImGuiKey_None; // "BrowserHome" - // case 'S': return ImGuiKey_None; // "BrowserStop" - } - break; - case 12: return ImGuiKey_RightBracket; // "BracketRight" - // case 13: return ImGuiKey_None; // "BrowserSearch" - case 14: - switch (emscripten_key[7]) - { - case 'F': return ImGuiKey_AppForward; // "BrowserForward": pass through even in browser hosts so the embedding app can decide - // case 'R': return ImGuiKey_None; // "BrowserRefresh" - } - break; - // case 16: return ImGuiKey_None; // "BrowserFavorites" - } - break; - case 'C': - switch (key_len) - { - // case 3: return ImGuiKey_None; // "Cut" - // case 4: return ImGuiKey_None; // "Copy" - case 5: return ImGuiKey_Comma; // "Comma" - // case 7: return ImGuiKey_None; // "Convert" - case 8: return ImGuiKey_CapsLock; // "CapsLock" - case 11: - switch (emscripten_key[4]) - { - case 'e': return ImGuiKey_Menu; // "ContextMenu" - case 'r': return ImGuiKey_LeftCtrl; // "ControlLeft" - } - break; - case 12: return ImGuiKey_RightCtrl; // "ControlRight" - } - break; - case 'D': - if (key_len == 6) - { - if (emscripten_key[1] == 'e') return ImGuiKey_Delete; // "Delete" - else return static_cast(ImGuiKey_0 + (emscripten_key[5] - '0')); // "Digit0" .. "Digit9" - } - break; - case 'E': - switch (key_len) - { - case 3: return ImGuiKey_End; // "End" - case 5: - switch (emscripten_key[1]) - { - case 'n': return ImGuiKey_Enter; // "Enter" - case 'q': return ImGuiKey_Equal; // "Equal" - // case 'j': return ImGuiKey_None; // "Eject" - } - break; - case 6: return ImGuiKey_Escape; // "Escape" - } - break; - case 'F': - switch (key_len) - { - case 2: - if (emscripten_key[1] == 'n') return ImGuiKey_None; // "Fn": no ImGuiKey equivalent - else return static_cast(ImGuiKey_F1 + (emscripten_key[1] - '1')); // "F1" .. "F9" - case 3: - if (emscripten_key[1] == '1') return static_cast(ImGuiKey_F10 + (emscripten_key[2] - '0')); // "F10" .. "F19" - else return static_cast(ImGuiKey_F20 + (emscripten_key[2] - '0')); // "F20" .. "F24" - // case 4: return ImGuiKey_None; // "Find" - // case 6: return ImGuiKey_None; // "FnLock" - } - break; - case 'H': - switch (key_len) - { - case 4: - switch (emscripten_key[1]) - { - case 'o': return ImGuiKey_Home; // "Home" - // case 'e': return ImGuiKey_None; // "Help" - } - break; - // case 5: return ImGuiKey_None; // "Hyper" - // case 8: return ImGuiKey_None; // "Hiragana" - } - break; - case 'I': - switch (key_len) - { - case 6: - switch (emscripten_key[2]) - { - case 's': return ImGuiKey_Insert; // "Insert" - case 't': return ImGuiKey_Slash; // "IntlRo": closest printable equivalent - } - break; - case 7: return ImGuiKey_Backslash; // "IntlYen": closest printable equivalent - case 13: return ImGuiKey_Backslash; // "IntlBackslash": preserve original behavior and map to generic backslash - } - break; - case 'K': - switch (key_len) - { - case 4: return static_cast(ImGuiKey_A + (emscripten_key[3] - 'A')); // "KeyA" .. "KeyZ" - // case 8: - // switch (emscripten_key[2]) - // { - // case 'n': return ImGuiKey_None; // "KanaMode" - // default: return ImGuiKey_None; // "Katakana" - // } - } - break; - // case 'L': - // switch (key_len) - // { - // case 5: - // switch (emscripten_key[4]) - // { - // case '1': return ImGuiKey_None; // "Lang1" - // case '2': return ImGuiKey_None; // "Lang2" - // } - // break; - // case 10: - // switch (emscripten_key[6]) - // { - // case 'A': - // switch (emscripten_key[9]) - // { - // case '1': return ImGuiKey_None; // "LaunchApp1" - // case '2': return ImGuiKey_None; // "LaunchApp2" - // } - // break; - // case 'M': return ImGuiKey_None; // "LaunchMail" - // } - // break; - // } - case 'M': - switch (key_len) - { - case 5: return ImGuiKey_Minus; // "Minus" - case 8: return ImGuiKey_LeftSuper; // "MetaLeft" - case 9: - switch (emscripten_key[3]) - { - case 'a': return ImGuiKey_RightSuper; // "MetaRight" - // default: return ImGuiKey_None; // "MediaStop" - } - break; - // case 11: return ImGuiKey_None; // "MediaSelect" - // case 14: - // switch (emscripten_key[10]) - // { - // case 'N': return ImGuiKey_None; // "MediaTrackNext" - // default: return ImGuiKey_None; // "MediaPlayPause" - // } - // case 18: return ImGuiKey_None; // "MediaTrackPrevious" - } - break; - case 'N': - if (emscripten_key[3] == 'p') - { - ImGuiKey const numpad_key{translate_numpad_key(emscripten_key, key_len)}; - if (numpad_key != ImGuiKey_None) return numpad_key; - } - if (key_len == 7) return ImGuiKey_NumLock; // "NumLock" - // if (key_len == 10) return ImGuiKey_None; // "NonConvert" - break; - // case 'O': return ImGuiKey_None; // "Open" - case 'P': - switch (key_len) - { - case 5: - if (emscripten_key[2] == 'u') return ImGuiKey_Pause; // "Pause" - // if (emscripten_key[2] == 's') return ImGuiKey_None; // "Paste" - // if (emscripten_key[1] == 'o') return ImGuiKey_None; // "Power" - // if (emscripten_key[1] == 'r') return ImGuiKey_None; // "Props" - break; - case 6: - switch (emscripten_key[1]) - { - case 'a': return ImGuiKey_PageUp; // "PageUp" - case 'e': return ImGuiKey_Period; // "Period" - } - break; - case 8: return ImGuiKey_PageDown; // "PageDown" - case 11: return ImGuiKey_PrintScreen; // "PrintScreen" - } - break; - case 'Q': return ImGuiKey_Apostrophe; // "Quote" - // case 'R': return ImGuiKey_None; // "Resume" - case 'S': - switch (key_len) - { - case 5: - if (emscripten_key[2] == 'a') return ImGuiKey_Slash; // "Slash" - if (emscripten_key[1] == 'p') return ImGuiKey_Space; // "Space" - // if (emscripten_key[2] == 'e') return ImGuiKey_None; // "Sleep" - // if (emscripten_key[1] == 'u') return ImGuiKey_None; // "Super" - break; - // case 6: return ImGuiKey_None; // "Select" - // case 7: return ImGuiKey_None; // "Suspend" - case 9: - switch (emscripten_key[1]) - { - case 'e': return ImGuiKey_Semicolon; // "Semicolon" - case 'h': return ImGuiKey_LeftShift; // "ShiftLeft" - } - break; - case 10: - switch (emscripten_key[1]) - { - case 'c': return ImGuiKey_ScrollLock; // "ScrollLock" - case 'h': return ImGuiKey_RightShift; // "ShiftRight" - } - break; - } - break; - case 'T': - if (key_len == 3) return ImGuiKey_Tab; // "Tab" - // if (key_len == 5) return ImGuiKey_None; // "Turbo" - break; - // case 'U': - // switch (key_len) - // { - // case 4: return ImGuiKey_None; // "Undo" - // case 12: return ImGuiKey_None; // "Unidentified" - // } - // case 'W': return ImGuiKey_None; // "WakeUp" - } - return ImGuiKey_None; + ImGuiStorage const& storage{get_key_translation_storage()}; + return static_cast(storage.GetInt(ImHashStr(emscripten_key), ImGuiKey_None)); } } // anonymous namespace From c0842c78cb462275177b57394fb7444ba8f4ab2e Mon Sep 17 00:00:00 2001 From: slowriot Date: Thu, 2 Apr 2026 22:39:51 +0100 Subject: [PATCH 43/72] consume mousewheel event if imgui wants to capture the mouse Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- backends/imgui_impl_emscripten.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index f651f1299..ce490569b 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -228,11 +228,12 @@ void ImGui_ImplEmscripten_Init() break; } // TODO: make scrolling speeds configurable - ImGui::GetIO().AddMouseWheelEvent( + ImGuiIO& io{ImGui::GetIO()}; + io.AddMouseWheelEvent( -static_cast(wheel_event->deltaX) * scale, -static_cast(wheel_event->deltaY) * scale ); - return false; // the event was not consumed + return io.WantCaptureMouse; // consume the event when imgui wants to capture mouse input } ); emscripten_set_keydown_callback( From e09c94739976b33bcdf1e27cc157430b10ed6966 Mon Sep 17 00:00:00 2001 From: slowriot Date: Thu, 2 Apr 2026 22:40:38 +0100 Subject: [PATCH 44/72] apply comment suggestion Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- backends/imgui_impl_emscripten.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index ce490569b..19c42c2bd 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -12,7 +12,7 @@ // 2024-12-08: Inputs: Prevent "Delete" key from getting printed in text input. // 2024-12-06: Inputs: Added special handling for Tab and Enter event capture. // 2024-12-06: Inputs: Handle blur and focus events correctly, focusin and focusout aren't enough. -// 2024-12-06: Emscripten: Don't use devicePixelRatio as it's not needed with WebGPU. +// 2024-12-06: Emscripten: Don't rely on devicePixelRatio for WebGPU framebuffer sizing; CSS->ImGui scaling may still use it. // 2024-11-22: Initial version by Eugene Hopkinson. (#8178) #include "imgui.h" From ed34b93d20a661bd4b57e2be92b892d53a45fb9c Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Thu, 2 Apr 2026 22:45:01 +0100 Subject: [PATCH 45/72] update comment to clarify shutdown conditions --- backends/imgui_impl_emscripten.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backends/imgui_impl_emscripten.h b/backends/imgui_impl_emscripten.h index 5b23023d5..6814c6f6c 100644 --- a/backends/imgui_impl_emscripten.h +++ b/backends/imgui_impl_emscripten.h @@ -49,7 +49,8 @@ IMGUI_IMPL_API void ImGui_ImplEmscripten_Init(); // Shut down the Emscripten backend. This unsets all Emscripten input callbacks set by Init. // Note it'll also unset any Emscripten input callbacks set elsewhere in the program! -// Note also there is no obligation to ever call this, as there is not necessarily any such concept as "shutting down" when running in the browser, and we have no resources to release. The user can just close the tab. +// Note also there is no obligation to ever call this, unless you intend to reset the backend (i.e. with ImGui::DestroyContext()). +// Otherwise, there is not necessarily any such concept as "shutting down" when running in the browser, and we have no resources to release. The user can just close the tab, so you don't need to worry about exiting cleanly. IMGUI_IMPL_API void ImGui_ImplEmscripten_Shutdown(); // Call every frame to synchronize Dear ImGui's cursor state with the browser's native cursors. From 4d012cfda272d318b0aba20370bc0ed26cc14dc4 Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Thu, 2 Apr 2026 22:54:32 +0100 Subject: [PATCH 46/72] leave the cursor alone if config requests no cursor change --- backends/imgui_impl_emscripten.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index 19c42c2bd..837131841 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -422,7 +422,10 @@ void set_cursor_if_necessary(emscripten_browser_cursor_internal::cursor& current void update_cursor(ImGui_ImplEmscripten_Data* bd) { // Sync any cursor changes due to ImGui to the browser's cursor - if (ImGui::GetIO().WantCaptureMouse) // mouse is hovering over the gui + ImGuiIO& io{ImGui::GetIO()}; + if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange) return; + + if (io.WantCaptureMouse) // mouse is hovering over the gui { if (bd->CursorToRestore == nullptr) { From b452984bca56bceb9821a6c90f47efd5888fa06f Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Thu, 2 Apr 2026 23:03:29 +0100 Subject: [PATCH 47/72] hide the browser cursor if imgui is handling software cursor rendering --- backends/imgui_impl_emscripten.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index 837131841..df8f9047a 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -425,6 +425,12 @@ void update_cursor(ImGui_ImplEmscripten_Data* bd) ImGuiIO& io{ImGui::GetIO()}; if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange) return; + if (io.MouseDrawCursor) // hide the cursor for the entire window if imgui is handling cursor drawing - not just when imgui wants to capture the mouse + { + set_cursor_if_necessary(bd->CurrentCursor, emscripten_browser_cursor_internal::cursor::none); + return; + } + if (io.WantCaptureMouse) // mouse is hovering over the gui { if (bd->CursorToRestore == nullptr) From 106660b527dd2e3108d1a35e5dfec57e2d1983b1 Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Thu, 2 Apr 2026 23:13:24 +0100 Subject: [PATCH 48/72] optimise browser cursor original state save and restore --- backends/imgui_impl_emscripten.cpp | 57 ++++++++++++++++-------------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index df8f9047a..1b6975053 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -412,10 +412,20 @@ void ImGui_ImplEmscripten_Shutdown() namespace { -void set_cursor_if_necessary(emscripten_browser_cursor_internal::cursor& current_cursor, emscripten_browser_cursor_internal::cursor new_cursor) +void restore_cursor_if_necessary(ImGui_ImplEmscripten_Data* bd) { - if (new_cursor == current_cursor) return; // don't do anything if the current cursor is already set - current_cursor = new_cursor; + if (bd->CursorToRestore == nullptr) return; + emscripten_browser_cursor_internal::set(bd->CursorToRestore); // restore the previous cursor state when leaving imgui cursor ownership + free(bd->CursorToRestore); + bd->CursorToRestore = nullptr; + bd->CurrentCursor = emscripten_browser_cursor_internal::cursor::invalid; // select an unused value for current cursor to force a set next time +} + +void set_cursor_if_necessary(ImGui_ImplEmscripten_Data* bd, emscripten_browser_cursor_internal::cursor new_cursor) +{ + if (new_cursor == bd->CurrentCursor) return; // don't do anything if the current cursor is already set + if (bd->CursorToRestore == nullptr) bd->CursorToRestore = emscripten_browser_cursor_internal::get_string(); // back up the existing cursor state when first taking cursor ownership + bd->CurrentCursor = new_cursor; emscripten_browser_cursor_internal::set(new_cursor); } @@ -423,64 +433,57 @@ void update_cursor(ImGui_ImplEmscripten_Data* bd) { // Sync any cursor changes due to ImGui to the browser's cursor ImGuiIO& io{ImGui::GetIO()}; - if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange) return; + if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange) + { + restore_cursor_if_necessary(bd); + return; + } if (io.MouseDrawCursor) // hide the cursor for the entire window if imgui is handling cursor drawing - not just when imgui wants to capture the mouse { - set_cursor_if_necessary(bd->CurrentCursor, emscripten_browser_cursor_internal::cursor::none); + set_cursor_if_necessary(bd, emscripten_browser_cursor_internal::cursor::none); return; } if (io.WantCaptureMouse) // mouse is hovering over the gui { - if (bd->CursorToRestore == nullptr) - { - bd->CursorToRestore = emscripten_browser_cursor_internal::get_string(); // back up the existing cursor state when entering the imgui capture space - } - switch (ImGui::GetMouseCursor()) { case ImGuiMouseCursor_None: - set_cursor_if_necessary(bd->CurrentCursor, emscripten_browser_cursor_internal::cursor::none); + set_cursor_if_necessary(bd, emscripten_browser_cursor_internal::cursor::none); break; case ImGuiMouseCursor_Arrow: - set_cursor_if_necessary(bd->CurrentCursor, emscripten_browser_cursor_internal::cursor::cursor_default); + set_cursor_if_necessary(bd, emscripten_browser_cursor_internal::cursor::cursor_default); break; case ImGuiMouseCursor_TextInput: // When hovering over InputText, etc. - set_cursor_if_necessary(bd->CurrentCursor, emscripten_browser_cursor_internal::cursor::text); + set_cursor_if_necessary(bd, emscripten_browser_cursor_internal::cursor::text); break; case ImGuiMouseCursor_ResizeAll: // (Unused by Dear ImGui functions) - set_cursor_if_necessary(bd->CurrentCursor, emscripten_browser_cursor_internal::cursor::move); + set_cursor_if_necessary(bd, emscripten_browser_cursor_internal::cursor::move); break; case ImGuiMouseCursor_ResizeNS: // When hovering over a horizontal border - set_cursor_if_necessary(bd->CurrentCursor, emscripten_browser_cursor_internal::cursor::ns_resize); + set_cursor_if_necessary(bd, emscripten_browser_cursor_internal::cursor::ns_resize); break; case ImGuiMouseCursor_ResizeEW: // When hovering over a vertical border or a column - set_cursor_if_necessary(bd->CurrentCursor, emscripten_browser_cursor_internal::cursor::ew_resize); + set_cursor_if_necessary(bd, emscripten_browser_cursor_internal::cursor::ew_resize); break; case ImGuiMouseCursor_ResizeNESW: // When hovering over the bottom-left corner of a window - set_cursor_if_necessary(bd->CurrentCursor, emscripten_browser_cursor_internal::cursor::nesw_resize); + set_cursor_if_necessary(bd, emscripten_browser_cursor_internal::cursor::nesw_resize); break; case ImGuiMouseCursor_ResizeNWSE: // When hovering over the bottom-right corner of a window - set_cursor_if_necessary(bd->CurrentCursor, emscripten_browser_cursor_internal::cursor::nwse_resize); + set_cursor_if_necessary(bd, emscripten_browser_cursor_internal::cursor::nwse_resize); break; case ImGuiMouseCursor_Hand: // (Unused by Dear ImGui functions. Use for e.g. hyperlinks) - set_cursor_if_necessary(bd->CurrentCursor, emscripten_browser_cursor_internal::cursor::pointer); + set_cursor_if_necessary(bd, emscripten_browser_cursor_internal::cursor::pointer); break; case ImGuiMouseCursor_NotAllowed: // When hovering something with disallowed interaction. Usually a crossed circle. - set_cursor_if_necessary(bd->CurrentCursor, emscripten_browser_cursor_internal::cursor::not_allowed); + set_cursor_if_necessary(bd, emscripten_browser_cursor_internal::cursor::not_allowed); break; } } else // mouse is away from the gui, hovering over some other part of the viewport { - if (bd->CursorToRestore != nullptr) - { - emscripten_browser_cursor_internal::set(bd->CursorToRestore); // restore the previous cursor state when leaving the imgui capture space - free(bd->CursorToRestore); - bd->CursorToRestore = nullptr; - bd->CurrentCursor = emscripten_browser_cursor_internal::cursor::invalid; // select an unused value for current cursor to force a set next time set_cursor_if_necessary is called - } + restore_cursor_if_necessary(bd); } } From 0b660012585c50a72883dc2043101cb50bf08e58 Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Thu, 2 Apr 2026 23:43:39 +0100 Subject: [PATCH 49/72] correct handling for MouseDrawCursor switches --- backends/imgui_impl_emscripten.cpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index 1b6975053..f67a1e521 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -107,6 +107,7 @@ struct ImGui_ImplEmscripten_Data float CssToImGuiScale{1.0f}; emscripten_browser_cursor_internal::cursor CurrentCursor{emscripten_browser_cursor_internal::cursor::invalid}; char* CursorToRestore{nullptr}; + bool LastMouseDrawCursor{false}; }; float get_target_device_pixel_ratio() @@ -435,14 +436,23 @@ void update_cursor(ImGui_ImplEmscripten_Data* bd) ImGuiIO& io{ImGui::GetIO()}; if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange) { - restore_cursor_if_necessary(bd); return; } - if (io.MouseDrawCursor) // hide the cursor for the entire window if imgui is handling cursor drawing - not just when imgui wants to capture the mouse + if (io.MouseDrawCursor) { - set_cursor_if_necessary(bd, emscripten_browser_cursor_internal::cursor::none); - return; + if(bd->LastMouseDrawCursor) return; + + set_cursor_if_necessary(bd, emscripten_browser_cursor_internal::cursor::none); // hide the cursor for the entire window if imgui is handling cursor drawing - not just when imgui wants to capture the mouse + bd->LastMouseDrawCursor = true; + } + else + { + if(bd->LastMouseDrawCursor) + { + restore_cursor_if_necessary(bd); + bd->LastMouseDrawCursor = false; + } } if (io.WantCaptureMouse) // mouse is hovering over the gui From 9e74bc4f1f6577b5ec566e046f8e53a2af8ed169 Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Thu, 2 Apr 2026 23:50:27 +0100 Subject: [PATCH 50/72] handle NoMouseCursorChange state transitions in more detail --- backends/imgui_impl_emscripten.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index f67a1e521..a713fec41 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -108,6 +108,7 @@ struct ImGui_ImplEmscripten_Data emscripten_browser_cursor_internal::cursor CurrentCursor{emscripten_browser_cursor_internal::cursor::invalid}; char* CursorToRestore{nullptr}; bool LastMouseDrawCursor{false}; + bool LastNoMouseCursorChange{false}; }; float get_target_device_pixel_ratio() @@ -436,19 +437,28 @@ void update_cursor(ImGui_ImplEmscripten_Data* bd) ImGuiIO& io{ImGui::GetIO()}; if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange) { + if (!bd->LastNoMouseCursorChange) + { + free(bd->CursorToRestore); + bd->CursorToRestore = nullptr; + bd->CurrentCursor = emscripten_browser_cursor_internal::cursor::invalid; + bd->LastMouseDrawCursor = false; + bd->LastNoMouseCursorChange = true; + } return; } + bd->LastNoMouseCursorChange = false; if (io.MouseDrawCursor) { - if(bd->LastMouseDrawCursor) return; + if (bd->LastMouseDrawCursor) return; set_cursor_if_necessary(bd, emscripten_browser_cursor_internal::cursor::none); // hide the cursor for the entire window if imgui is handling cursor drawing - not just when imgui wants to capture the mouse bd->LastMouseDrawCursor = true; } else { - if(bd->LastMouseDrawCursor) + if (bd->LastMouseDrawCursor) { restore_cursor_if_necessary(bd); bd->LastMouseDrawCursor = false; From a941ea2cbb55e6971aafadda47be546279f474e9 Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Thu, 3 Sep 2026 14:09:41 +0100 Subject: [PATCH 51/72] Backends: Emscripten: Match Dear ImGui initialization style --- backends/imgui_impl_emscripten.cpp | 68 +++++++++++++++--------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index a713fec41..8a0468b07 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -24,7 +24,7 @@ extern ImGuiID ImHashStr(char const* data, size_t data_size = 0, ImGuiID seed = 0); // Declared in imgui_internal.h. -float ImGui_ImplEmscripten_TargetDevicePixelRatio{1.0f}; +float ImGui_ImplEmscripten_TargetDevicePixelRatio = 1.0f; namespace { @@ -104,11 +104,11 @@ namespace { struct ImGui_ImplEmscripten_Data { - float CssToImGuiScale{1.0f}; - emscripten_browser_cursor_internal::cursor CurrentCursor{emscripten_browser_cursor_internal::cursor::invalid}; - char* CursorToRestore{nullptr}; - bool LastMouseDrawCursor{false}; - bool LastNoMouseCursorChange{false}; + float CssToImGuiScale = 1.0f; + emscripten_browser_cursor_internal::cursor CurrentCursor = emscripten_browser_cursor_internal::cursor::invalid; + char* CursorToRestore = nullptr; + bool LastMouseDrawCursor = false; + bool LastNoMouseCursorChange = false; }; float get_target_device_pixel_ratio() @@ -119,8 +119,8 @@ float get_target_device_pixel_ratio() void update_display_properties(ImGuiIO& io, ImGui_ImplEmscripten_Data* bd, float css_width, float css_height) { - float const target_device_pixel_ratio{get_target_device_pixel_ratio()}; - float const css_to_imgui_scale{static_cast(emscripten_get_device_pixel_ratio()) / target_device_pixel_ratio}; + float const target_device_pixel_ratio = get_target_device_pixel_ratio(); + float const css_to_imgui_scale = static_cast(emscripten_get_device_pixel_ratio()) / target_device_pixel_ratio; bd->CssToImGuiScale = css_to_imgui_scale; io.DisplaySize.x = css_width * css_to_imgui_scale; io.DisplaySize.y = css_height * css_to_imgui_scale; @@ -138,10 +138,10 @@ ImGui_ImplEmscripten_Data* ImGui_ImplEmscripten_GetBackendData() void ImGui_ImplEmscripten_Init() { // Initialise the Emscripten backend, setting input callbacks - ImGuiIO& io{ImGui::GetIO()}; + ImGuiIO& io = ImGui::GetIO(); IMGUI_CHECKVERSION(); IM_ASSERT(io.BackendPlatformUserData == nullptr && "Already initialized a platform backend!"); - ImGui_ImplEmscripten_Data* bd{IM_NEW(ImGui_ImplEmscripten_Data)()}; + ImGui_ImplEmscripten_Data* bd = IM_NEW(ImGui_ImplEmscripten_Data)(); io.BackendPlatformUserData = static_cast(bd); io.BackendPlatformName = "imgui_impl_emscripten"; io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; @@ -159,8 +159,8 @@ void ImGui_ImplEmscripten_Init() nullptr, // userData false, // useCapture [](int /*event_type*/, EmscriptenMouseEvent const* mouse_event, void* /*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_MOUSEMOVE - ImGui_ImplEmscripten_Data* bd{ImGui_ImplEmscripten_GetBackendData()}; - float const css_to_imgui_scale{bd ? bd->CssToImGuiScale : 1.0f}; + ImGui_ImplEmscripten_Data* bd = ImGui_ImplEmscripten_GetBackendData(); + float const css_to_imgui_scale = bd ? bd->CssToImGuiScale : 1.0f; ImGui::GetIO().AddMousePosEvent( static_cast(mouse_event->clientX) * css_to_imgui_scale, static_cast(mouse_event->clientY) * css_to_imgui_scale @@ -191,8 +191,8 @@ void ImGui_ImplEmscripten_Init() nullptr, // userData false, // useCapture [](int /*event_type*/, EmscriptenMouseEvent const* mouse_event, void* /*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_MOUSEENTER - ImGui_ImplEmscripten_Data* bd{ImGui_ImplEmscripten_GetBackendData()}; - float const css_to_imgui_scale{bd ? bd->CssToImGuiScale : 1.0f}; + ImGui_ImplEmscripten_Data* bd = ImGui_ImplEmscripten_GetBackendData(); + float const css_to_imgui_scale = bd ? bd->CssToImGuiScale : 1.0f; ImGui::GetIO().AddMousePosEvent( static_cast(mouse_event->clientX) * css_to_imgui_scale, static_cast(mouse_event->clientY) * css_to_imgui_scale @@ -205,7 +205,7 @@ void ImGui_ImplEmscripten_Init() nullptr, // userData false, // useCapture [](int /*event_type*/, EmscriptenMouseEvent const* /*mouse_event*/, void* /*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_MOUSELEAVE - ImGuiIO& io{ImGui::GetIO()}; + ImGuiIO& io = ImGui::GetIO(); io.AddMousePosEvent(-FLT_MAX, -FLT_MAX); // cursor is not in the window io.ClearInputKeys(); // clear pending input keys on mouse exit return true; // the event was consumed @@ -216,7 +216,7 @@ void ImGui_ImplEmscripten_Init() nullptr, // userData false, // useCapture [](int /*event_type*/, EmscriptenWheelEvent const* wheel_event, void* /*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_WHEEL - float scale{1.0f}; + float scale = 1.0f; switch (wheel_event->deltaMode) { case DOM_DELTA_PIXEL: // scrolling in pixels @@ -230,7 +230,7 @@ void ImGui_ImplEmscripten_Init() break; } // TODO: make scrolling speeds configurable - ImGuiIO& io{ImGui::GetIO()}; + ImGuiIO& io = ImGui::GetIO(); io.AddMouseWheelEvent( -static_cast(wheel_event->deltaX) * scale, -static_cast(wheel_event->deltaY) * scale @@ -243,8 +243,8 @@ void ImGui_ImplEmscripten_Init() nullptr, // userData false, // useCapture [](int /*event_type*/, EmscriptenKeyboardEvent const* key_event, void* /*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_KEYDOWN - const ImGuiKey key{translate_key(key_event->code)}; - ImGuiIO& io{ImGui::GetIO()}; + const ImGuiKey key = translate_key(key_event->code); + ImGuiIO& io = ImGui::GetIO(); io.AddKeyEvent(key, true); switch (key) // special cases for certain key events { @@ -281,8 +281,8 @@ void ImGui_ImplEmscripten_Init() nullptr, // userData false, // useCapture [](int /*event_type*/, EmscriptenKeyboardEvent const* key_event, void* /*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_KEYUP - const ImGuiKey key{translate_key(key_event->code)}; - ImGuiIO& io{ImGui::GetIO()}; + const ImGuiKey key = translate_key(key_event->code); + ImGuiIO& io = ImGui::GetIO(); io.AddKeyEvent(key, false); switch (key) // special cases for certain key events { @@ -313,7 +313,7 @@ void ImGui_ImplEmscripten_Init() nullptr, // userData false, // useCapture [](int /*event_type*/, EmscriptenKeyboardEvent const* key_event, void* /*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_KEYPRESS - ImGuiIO& io{ImGui::GetIO()}; + ImGuiIO& io = ImGui::GetIO(); io.AddInputCharactersUTF8(key_event->key); return io.WantCaptureKeyboard; // the event was consumed only if imgui wants to capture the keyboard } @@ -323,8 +323,8 @@ void ImGui_ImplEmscripten_Init() nullptr, // userData false, // useCapture [](int /*event_type*/, EmscriptenUiEvent const* event, void* /*data*/) { // event_type == EMSCRIPTEN_EVENT_RESIZE - ImGuiIO& io{ImGui::GetIO()}; - ImGui_ImplEmscripten_Data* bd{ImGui_ImplEmscripten_GetBackendData()}; + ImGuiIO& io = ImGui::GetIO(); + ImGui_ImplEmscripten_Data* bd = ImGui_ImplEmscripten_GetBackendData(); if (bd != nullptr) update_display_properties(io, bd, static_cast(event->windowInnerWidth), static_cast(event->windowInnerHeight)); return true; // the event was consumed } @@ -334,7 +334,7 @@ void ImGui_ImplEmscripten_Init() nullptr, // userData false, // useCapture [](int /*event_type*/, EmscriptenFocusEvent const* /*event*/, void* /*data*/) { // event_type == EMSCRIPTEN_EVENT_BLUR - ImGuiIO& io{ImGui::GetIO()}; + ImGuiIO& io = ImGui::GetIO(); io.AddFocusEvent(false); io.ClearInputKeys(); // clear pending input keys on focus loss return true; // the event was consumed @@ -345,7 +345,7 @@ void ImGui_ImplEmscripten_Init() nullptr, // userData false, // useCapture [](int /*event_type*/, EmscriptenFocusEvent const* /*event*/, void* /*data*/) { // event_type == EMSCRIPTEN_EVENT_FOCUS - ImGuiIO& io{ImGui::GetIO()}; + ImGuiIO& io = ImGui::GetIO(); io.AddFocusEvent(true); io.ClearInputKeys(); // clear pending input keys on focus gain - for example if you press tab to cycle back into the browser window return true; // the event was consumed @@ -356,7 +356,7 @@ void ImGui_ImplEmscripten_Init() nullptr, // userData false, // useCapture [](int /*event_type*/, EmscriptenFocusEvent const* /*event*/, void* /*data*/) { // event_type == EMSCRIPTEN_EVENT_FOCUSIN - ImGuiIO& io{ImGui::GetIO()}; + ImGuiIO& io = ImGui::GetIO(); io.AddFocusEvent(true); io.ClearInputKeys(); // clear pending input keys on focus gain return true; // the event was consumed @@ -367,7 +367,7 @@ void ImGui_ImplEmscripten_Init() nullptr, // userData false, // useCapture [](int /*event_type*/, EmscriptenFocusEvent const* /*event*/, void* /*data*/) { // event_type == EMSCRIPTEN_EVENT_FOCUSOUT - ImGuiIO& io{ImGui::GetIO()}; + ImGuiIO& io = ImGui::GetIO(); io.AddFocusEvent(false); io.ClearInputKeys(); // clear pending input keys on focus loss - for example if you press tab to cycle to another part of the UI return true; // the event was consumed @@ -379,7 +379,7 @@ void ImGui_ImplEmscripten_Init() void ImGui_ImplEmscripten_Shutdown() { - ImGui_ImplEmscripten_Data* bd{ImGui_ImplEmscripten_GetBackendData()}; + ImGui_ImplEmscripten_Data* bd = ImGui_ImplEmscripten_GetBackendData(); IM_ASSERT(bd != nullptr && "No platform backend to shutdown, or already shutdown?"); // Unset any callbacks set by Init @@ -405,7 +405,7 @@ void ImGui_ImplEmscripten_Shutdown() free(bd->CursorToRestore); } - ImGuiIO& io{ImGui::GetIO()}; + ImGuiIO& io = ImGui::GetIO(); io.BackendPlatformName = nullptr; io.BackendPlatformUserData = nullptr; io.BackendFlags &= ~ImGuiBackendFlags_HasMouseCursors; @@ -434,7 +434,7 @@ void set_cursor_if_necessary(ImGui_ImplEmscripten_Data* bd, emscripten_browser_c void update_cursor(ImGui_ImplEmscripten_Data* bd) { // Sync any cursor changes due to ImGui to the browser's cursor - ImGuiIO& io{ImGui::GetIO()}; + ImGuiIO& io = ImGui::GetIO(); if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange) { if (!bd->LastNoMouseCursorChange) @@ -511,7 +511,7 @@ void update_cursor(ImGui_ImplEmscripten_Data* bd) void ImGui_ImplEmscripten_NewFrame() { - ImGui_ImplEmscripten_Data* bd{ImGui_ImplEmscripten_GetBackendData()}; + ImGui_ImplEmscripten_Data* bd = ImGui_ImplEmscripten_GetBackendData(); IM_ASSERT(bd != nullptr && "Context or backend not initialized? Did you call ImGui_ImplEmscripten_Init()?"); // Update any state that needs to be polled @@ -593,7 +593,7 @@ constexpr ImGuiMouseButton translate_mousebutton(unsigned short emscripten_butto ImGuiStorage const& get_key_translation_storage() { static ImGuiStorage storage; - static bool is_initialized{false}; + static bool is_initialized = false; if (is_initialized) return storage; is_initialized = true; storage.Data.reserve(128); @@ -810,7 +810,7 @@ ImGuiKey translate_key(char const* emscripten_key) // Translate a W3C KeyboardEvent.code string into an ImGuiKey. if (emscripten_key == nullptr || emscripten_key[0] == '\0') return ImGuiKey_None; - ImGuiStorage const& storage{get_key_translation_storage()}; + ImGuiStorage const& storage = get_key_translation_storage(); return static_cast(storage.GetInt(ImHashStr(emscripten_key), ImGuiKey_None)); } From 9fc439dd6f40232cd3c2d1e74ef499a271217445 Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Thu, 3 Sep 2026 14:11:19 +0100 Subject: [PATCH 52/72] Backends: Emscripten: Match Dear ImGui C cast style --- backends/imgui_impl_emscripten.cpp | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index 8a0468b07..890c016d6 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -120,7 +120,7 @@ float get_target_device_pixel_ratio() void update_display_properties(ImGuiIO& io, ImGui_ImplEmscripten_Data* bd, float css_width, float css_height) { float const target_device_pixel_ratio = get_target_device_pixel_ratio(); - float const css_to_imgui_scale = static_cast(emscripten_get_device_pixel_ratio()) / target_device_pixel_ratio; + float const css_to_imgui_scale = (float)emscripten_get_device_pixel_ratio() / target_device_pixel_ratio; bd->CssToImGuiScale = css_to_imgui_scale; io.DisplaySize.x = css_width * css_to_imgui_scale; io.DisplaySize.y = css_height * css_to_imgui_scale; @@ -130,7 +130,7 @@ void update_display_properties(ImGuiIO& io, ImGui_ImplEmscripten_Data* bd, float // Backend data stored in io.BackendPlatformUserData to allow support for multiple Dear ImGui contexts ImGui_ImplEmscripten_Data* ImGui_ImplEmscripten_GetBackendData() { - return ImGui::GetCurrentContext() ? static_cast(ImGui::GetIO().BackendPlatformUserData) : nullptr; + return ImGui::GetCurrentContext() ? (ImGui_ImplEmscripten_Data*)ImGui::GetIO().BackendPlatformUserData : nullptr; } } // anonymous namespace @@ -142,7 +142,7 @@ void ImGui_ImplEmscripten_Init() IMGUI_CHECKVERSION(); IM_ASSERT(io.BackendPlatformUserData == nullptr && "Already initialized a platform backend!"); ImGui_ImplEmscripten_Data* bd = IM_NEW(ImGui_ImplEmscripten_Data)(); - io.BackendPlatformUserData = static_cast(bd); + io.BackendPlatformUserData = (void*)bd; io.BackendPlatformName = "imgui_impl_emscripten"; io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; @@ -150,8 +150,8 @@ void ImGui_ImplEmscripten_Init() update_display_properties( io, bd, - static_cast(EM_ASM_INT(return window.innerWidth;)), - static_cast(EM_ASM_INT(return window.innerHeight;)) + (float)EM_ASM_INT(return window.innerWidth;), + (float)EM_ASM_INT(return window.innerHeight;) ); emscripten_set_mousemove_callback( @@ -162,8 +162,8 @@ void ImGui_ImplEmscripten_Init() ImGui_ImplEmscripten_Data* bd = ImGui_ImplEmscripten_GetBackendData(); float const css_to_imgui_scale = bd ? bd->CssToImGuiScale : 1.0f; ImGui::GetIO().AddMousePosEvent( - static_cast(mouse_event->clientX) * css_to_imgui_scale, - static_cast(mouse_event->clientY) * css_to_imgui_scale + (float)mouse_event->clientX * css_to_imgui_scale, + (float)mouse_event->clientY * css_to_imgui_scale ); return true; // the event was consumed } @@ -194,8 +194,8 @@ void ImGui_ImplEmscripten_Init() ImGui_ImplEmscripten_Data* bd = ImGui_ImplEmscripten_GetBackendData(); float const css_to_imgui_scale = bd ? bd->CssToImGuiScale : 1.0f; ImGui::GetIO().AddMousePosEvent( - static_cast(mouse_event->clientX) * css_to_imgui_scale, - static_cast(mouse_event->clientY) * css_to_imgui_scale + (float)mouse_event->clientX * css_to_imgui_scale, + (float)mouse_event->clientY * css_to_imgui_scale ); return true; // the event was consumed } @@ -232,8 +232,8 @@ void ImGui_ImplEmscripten_Init() // TODO: make scrolling speeds configurable ImGuiIO& io = ImGui::GetIO(); io.AddMouseWheelEvent( - -static_cast(wheel_event->deltaX) * scale, - -static_cast(wheel_event->deltaY) * scale + -(float)wheel_event->deltaX * scale, + -(float)wheel_event->deltaY * scale ); return io.WantCaptureMouse; // consume the event when imgui wants to capture mouse input } @@ -325,7 +325,7 @@ void ImGui_ImplEmscripten_Init() [](int /*event_type*/, EmscriptenUiEvent const* event, void* /*data*/) { // event_type == EMSCRIPTEN_EVENT_RESIZE ImGuiIO& io = ImGui::GetIO(); ImGui_ImplEmscripten_Data* bd = ImGui_ImplEmscripten_GetBackendData(); - if (bd != nullptr) update_display_properties(io, bd, static_cast(event->windowInnerWidth), static_cast(event->windowInnerHeight)); + if (bd != nullptr) update_display_properties(io, bd, (float)event->windowInnerWidth, (float)event->windowInnerHeight); return true; // the event was consumed } ); @@ -524,9 +524,9 @@ namespace emscripten_browser_cursor_internal char* get_string() { // Return the current cursor setting as a newly-allocated string, caller must free it. - return reinterpret_cast(EM_ASM_PTR( + return (char*)EM_ASM_PTR( return stringToNewUTF8(document.body.style.cursor); - )); + ); } void set(cursor new_cursor) @@ -811,7 +811,7 @@ ImGuiKey translate_key(char const* emscripten_key) if (emscripten_key == nullptr || emscripten_key[0] == '\0') return ImGuiKey_None; ImGuiStorage const& storage = get_key_translation_storage(); - return static_cast(storage.GetInt(ImHashStr(emscripten_key), ImGuiKey_None)); + return (ImGuiKey)storage.GetInt(ImHashStr(emscripten_key), ImGuiKey_None); } } // anonymous namespace From 10a5ecc6af7d7bcbaad8a603fa1aab54121c7fde Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Thu, 3 Sep 2026 14:14:37 +0100 Subject: [PATCH 53/72] Backends: Emscripten: Standardise internal helper naming and linkage --- backends/imgui_impl_emscripten.cpp | 127 ++++++++++++----------------- 1 file changed, 53 insertions(+), 74 deletions(-) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index 890c016d6..5e69f8f1b 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -26,14 +26,10 @@ extern ImGuiID ImHashStr(char const* data, size_t data_size = 0, ImGuiID seed = float ImGui_ImplEmscripten_TargetDevicePixelRatio = 1.0f; -namespace { - // W3C UI Events KeyboardEvent.code translation helpers -ImGuiKey translate_key(char const* emscripten_key); -constexpr ImGuiMouseButton translate_mousebutton(unsigned short emscripten_button) __attribute__((__const__)); - -} // anonymous namespace +static ImGuiKey ImGui_ImplEmscripten_TranslateKey(char const* emscripten_key); +static constexpr ImGuiMouseButton ImGui_ImplEmscripten_TranslateMouseButton(unsigned short emscripten_button) __attribute__((__const__)); namespace emscripten_browser_cursor_internal { @@ -94,13 +90,11 @@ enum class cursor invalid }; -void set(cursor new_cursor); // set a new cursor from a cursor enum -char* get_string(); // read the current cursor setting as an owned string, caller must free() -void set(char const* new_cursor); // set the cursor from an arbitrary string - } // namespace emscripten_browser_cursor_internal -namespace { +static void ImGui_ImplEmscripten_SetBrowserCursor(emscripten_browser_cursor_internal::cursor new_cursor); // set a new cursor from a cursor enum +static char* ImGui_ImplEmscripten_GetBrowserCursor(); // read the current cursor setting as an owned string, caller must free() +static void ImGui_ImplEmscripten_SetBrowserCursor(char const* new_cursor); // set the cursor from an arbitrary string struct ImGui_ImplEmscripten_Data { @@ -111,15 +105,15 @@ struct ImGui_ImplEmscripten_Data bool LastNoMouseCursorChange = false; }; -float get_target_device_pixel_ratio() +static float ImGui_ImplEmscripten_GetTargetDevicePixelRatio() { IM_ASSERT(ImGui_ImplEmscripten_TargetDevicePixelRatio > 0.0f && "ImGui_ImplEmscripten_TargetDevicePixelRatio must be positive."); return ImGui_ImplEmscripten_TargetDevicePixelRatio > 0.0f ? ImGui_ImplEmscripten_TargetDevicePixelRatio : 1.0f; } -void update_display_properties(ImGuiIO& io, ImGui_ImplEmscripten_Data* bd, float css_width, float css_height) +static void ImGui_ImplEmscripten_UpdateDisplayProperties(ImGuiIO& io, ImGui_ImplEmscripten_Data* bd, float css_width, float css_height) { - float const target_device_pixel_ratio = get_target_device_pixel_ratio(); + float const target_device_pixel_ratio = ImGui_ImplEmscripten_GetTargetDevicePixelRatio(); float const css_to_imgui_scale = (float)emscripten_get_device_pixel_ratio() / target_device_pixel_ratio; bd->CssToImGuiScale = css_to_imgui_scale; io.DisplaySize.x = css_width * css_to_imgui_scale; @@ -128,13 +122,11 @@ void update_display_properties(ImGuiIO& io, ImGui_ImplEmscripten_Data* bd, float } // Backend data stored in io.BackendPlatformUserData to allow support for multiple Dear ImGui contexts -ImGui_ImplEmscripten_Data* ImGui_ImplEmscripten_GetBackendData() +static ImGui_ImplEmscripten_Data* ImGui_ImplEmscripten_GetBackendData() { return ImGui::GetCurrentContext() ? (ImGui_ImplEmscripten_Data*)ImGui::GetIO().BackendPlatformUserData : nullptr; } -} // anonymous namespace - void ImGui_ImplEmscripten_Init() { // Initialise the Emscripten backend, setting input callbacks @@ -147,7 +139,7 @@ void ImGui_ImplEmscripten_Init() io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // set up initial display size values - update_display_properties( + ImGui_ImplEmscripten_UpdateDisplayProperties( io, bd, (float)EM_ASM_INT(return window.innerWidth;), @@ -173,7 +165,7 @@ void ImGui_ImplEmscripten_Init() nullptr, // userData false, // useCapture [](int /*event_type*/, EmscriptenMouseEvent const* mouse_event, void* /*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_MOUSEDOWN - ImGui::GetIO().AddMouseButtonEvent(translate_mousebutton(mouse_event->button), true); // translated button, down + ImGui::GetIO().AddMouseButtonEvent(ImGui_ImplEmscripten_TranslateMouseButton(mouse_event->button), true); // translated button, down return true; // the event was consumed } ); @@ -182,7 +174,7 @@ void ImGui_ImplEmscripten_Init() nullptr, // userData false, // useCapture [](int /*event_type*/, EmscriptenMouseEvent const* mouse_event, void* /*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_MOUSEUP - ImGui::GetIO().AddMouseButtonEvent(translate_mousebutton(mouse_event->button), false); // translated button, up + ImGui::GetIO().AddMouseButtonEvent(ImGui_ImplEmscripten_TranslateMouseButton(mouse_event->button), false); // translated button, up return true; // the event was consumed } ); @@ -243,7 +235,7 @@ void ImGui_ImplEmscripten_Init() nullptr, // userData false, // useCapture [](int /*event_type*/, EmscriptenKeyboardEvent const* key_event, void* /*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_KEYDOWN - const ImGuiKey key = translate_key(key_event->code); + const ImGuiKey key = ImGui_ImplEmscripten_TranslateKey(key_event->code); ImGuiIO& io = ImGui::GetIO(); io.AddKeyEvent(key, true); switch (key) // special cases for certain key events @@ -281,7 +273,7 @@ void ImGui_ImplEmscripten_Init() nullptr, // userData false, // useCapture [](int /*event_type*/, EmscriptenKeyboardEvent const* key_event, void* /*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_KEYUP - const ImGuiKey key = translate_key(key_event->code); + const ImGuiKey key = ImGui_ImplEmscripten_TranslateKey(key_event->code); ImGuiIO& io = ImGui::GetIO(); io.AddKeyEvent(key, false); switch (key) // special cases for certain key events @@ -325,7 +317,7 @@ void ImGui_ImplEmscripten_Init() [](int /*event_type*/, EmscriptenUiEvent const* event, void* /*data*/) { // event_type == EMSCRIPTEN_EVENT_RESIZE ImGuiIO& io = ImGui::GetIO(); ImGui_ImplEmscripten_Data* bd = ImGui_ImplEmscripten_GetBackendData(); - if (bd != nullptr) update_display_properties(io, bd, (float)event->windowInnerWidth, (float)event->windowInnerHeight); + if (bd != nullptr) ImGui_ImplEmscripten_UpdateDisplayProperties(io, bd, (float)event->windowInnerWidth, (float)event->windowInnerHeight); return true; // the event was consumed } ); @@ -401,7 +393,7 @@ void ImGui_ImplEmscripten_Shutdown() if (bd->CursorToRestore != nullptr) { - emscripten_browser_cursor_internal::set(bd->CursorToRestore); // restore the previous cursor state if imgui still owns the cursor on shutdown + ImGui_ImplEmscripten_SetBrowserCursor(bd->CursorToRestore); // restore the previous cursor state if imgui still owns the cursor on shutdown free(bd->CursorToRestore); } @@ -412,26 +404,24 @@ void ImGui_ImplEmscripten_Shutdown() IM_DELETE(bd); } -namespace { - -void restore_cursor_if_necessary(ImGui_ImplEmscripten_Data* bd) +static void ImGui_ImplEmscripten_RestoreMouseCursor(ImGui_ImplEmscripten_Data* bd) { if (bd->CursorToRestore == nullptr) return; - emscripten_browser_cursor_internal::set(bd->CursorToRestore); // restore the previous cursor state when leaving imgui cursor ownership + ImGui_ImplEmscripten_SetBrowserCursor(bd->CursorToRestore); // restore the previous cursor state when leaving imgui cursor ownership free(bd->CursorToRestore); bd->CursorToRestore = nullptr; bd->CurrentCursor = emscripten_browser_cursor_internal::cursor::invalid; // select an unused value for current cursor to force a set next time } -void set_cursor_if_necessary(ImGui_ImplEmscripten_Data* bd, emscripten_browser_cursor_internal::cursor new_cursor) +static void ImGui_ImplEmscripten_SetMouseCursor(ImGui_ImplEmscripten_Data* bd, emscripten_browser_cursor_internal::cursor new_cursor) { if (new_cursor == bd->CurrentCursor) return; // don't do anything if the current cursor is already set - if (bd->CursorToRestore == nullptr) bd->CursorToRestore = emscripten_browser_cursor_internal::get_string(); // back up the existing cursor state when first taking cursor ownership + if (bd->CursorToRestore == nullptr) bd->CursorToRestore = ImGui_ImplEmscripten_GetBrowserCursor(); // back up the existing cursor state when first taking cursor ownership bd->CurrentCursor = new_cursor; - emscripten_browser_cursor_internal::set(new_cursor); + ImGui_ImplEmscripten_SetBrowserCursor(new_cursor); } -void update_cursor(ImGui_ImplEmscripten_Data* bd) +static void ImGui_ImplEmscripten_UpdateMouseCursor(ImGui_ImplEmscripten_Data* bd) { // Sync any cursor changes due to ImGui to the browser's cursor ImGuiIO& io = ImGui::GetIO(); @@ -453,14 +443,14 @@ void update_cursor(ImGui_ImplEmscripten_Data* bd) { if (bd->LastMouseDrawCursor) return; - set_cursor_if_necessary(bd, emscripten_browser_cursor_internal::cursor::none); // hide the cursor for the entire window if imgui is handling cursor drawing - not just when imgui wants to capture the mouse + ImGui_ImplEmscripten_SetMouseCursor(bd, emscripten_browser_cursor_internal::cursor::none); // hide the cursor for the entire window if imgui is handling cursor drawing - not just when imgui wants to capture the mouse bd->LastMouseDrawCursor = true; } else { if (bd->LastMouseDrawCursor) { - restore_cursor_if_necessary(bd); + ImGui_ImplEmscripten_RestoreMouseCursor(bd); bd->LastMouseDrawCursor = false; } } @@ -470,58 +460,53 @@ void update_cursor(ImGui_ImplEmscripten_Data* bd) switch (ImGui::GetMouseCursor()) { case ImGuiMouseCursor_None: - set_cursor_if_necessary(bd, emscripten_browser_cursor_internal::cursor::none); + ImGui_ImplEmscripten_SetMouseCursor(bd, emscripten_browser_cursor_internal::cursor::none); break; case ImGuiMouseCursor_Arrow: - set_cursor_if_necessary(bd, emscripten_browser_cursor_internal::cursor::cursor_default); + ImGui_ImplEmscripten_SetMouseCursor(bd, emscripten_browser_cursor_internal::cursor::cursor_default); break; case ImGuiMouseCursor_TextInput: // When hovering over InputText, etc. - set_cursor_if_necessary(bd, emscripten_browser_cursor_internal::cursor::text); + ImGui_ImplEmscripten_SetMouseCursor(bd, emscripten_browser_cursor_internal::cursor::text); break; case ImGuiMouseCursor_ResizeAll: // (Unused by Dear ImGui functions) - set_cursor_if_necessary(bd, emscripten_browser_cursor_internal::cursor::move); + ImGui_ImplEmscripten_SetMouseCursor(bd, emscripten_browser_cursor_internal::cursor::move); break; case ImGuiMouseCursor_ResizeNS: // When hovering over a horizontal border - set_cursor_if_necessary(bd, emscripten_browser_cursor_internal::cursor::ns_resize); + ImGui_ImplEmscripten_SetMouseCursor(bd, emscripten_browser_cursor_internal::cursor::ns_resize); break; case ImGuiMouseCursor_ResizeEW: // When hovering over a vertical border or a column - set_cursor_if_necessary(bd, emscripten_browser_cursor_internal::cursor::ew_resize); + ImGui_ImplEmscripten_SetMouseCursor(bd, emscripten_browser_cursor_internal::cursor::ew_resize); break; case ImGuiMouseCursor_ResizeNESW: // When hovering over the bottom-left corner of a window - set_cursor_if_necessary(bd, emscripten_browser_cursor_internal::cursor::nesw_resize); + ImGui_ImplEmscripten_SetMouseCursor(bd, emscripten_browser_cursor_internal::cursor::nesw_resize); break; case ImGuiMouseCursor_ResizeNWSE: // When hovering over the bottom-right corner of a window - set_cursor_if_necessary(bd, emscripten_browser_cursor_internal::cursor::nwse_resize); + ImGui_ImplEmscripten_SetMouseCursor(bd, emscripten_browser_cursor_internal::cursor::nwse_resize); break; case ImGuiMouseCursor_Hand: // (Unused by Dear ImGui functions. Use for e.g. hyperlinks) - set_cursor_if_necessary(bd, emscripten_browser_cursor_internal::cursor::pointer); + ImGui_ImplEmscripten_SetMouseCursor(bd, emscripten_browser_cursor_internal::cursor::pointer); break; case ImGuiMouseCursor_NotAllowed: // When hovering something with disallowed interaction. Usually a crossed circle. - set_cursor_if_necessary(bd, emscripten_browser_cursor_internal::cursor::not_allowed); + ImGui_ImplEmscripten_SetMouseCursor(bd, emscripten_browser_cursor_internal::cursor::not_allowed); break; } } else // mouse is away from the gui, hovering over some other part of the viewport { - restore_cursor_if_necessary(bd); + ImGui_ImplEmscripten_RestoreMouseCursor(bd); } } -} // anonymous namespace - void ImGui_ImplEmscripten_NewFrame() { ImGui_ImplEmscripten_Data* bd = ImGui_ImplEmscripten_GetBackendData(); IM_ASSERT(bd != nullptr && "Context or backend not initialized? Did you call ImGui_ImplEmscripten_Init()?"); // Update any state that needs to be polled - update_cursor(bd); + ImGui_ImplEmscripten_UpdateMouseCursor(bd); } -namespace emscripten_browser_cursor_internal -{ - -char* get_string() +static char* ImGui_ImplEmscripten_GetBrowserCursor() { // Return the current cursor setting as a newly-allocated string, caller must free it. return (char*)EM_ASM_PTR( @@ -529,47 +514,47 @@ char* get_string() ); } -void set(cursor new_cursor) +static void ImGui_ImplEmscripten_SetBrowserCursor(emscripten_browser_cursor_internal::cursor new_cursor) { // Set the cursor according to the given enum // Note, implementations omitted for cursors not used by imgui. For full implementation, use https://github.com/Armchair-Software/emscripten-browser-cursor switch (new_cursor) { - case cursor::none: + case emscripten_browser_cursor_internal::cursor::none: EM_ASM(document.body.style.cursor = 'none';); break; - case cursor::cursor_default: + case emscripten_browser_cursor_internal::cursor::cursor_default: default: EM_ASM(document.body.style.cursor = 'default';); break; - case cursor::pointer: + case emscripten_browser_cursor_internal::cursor::pointer: EM_ASM(document.body.style.cursor = 'pointer';); break; - case cursor::text: + case emscripten_browser_cursor_internal::cursor::text: EM_ASM(document.body.style.cursor = 'text';); break; - case cursor::move: + case emscripten_browser_cursor_internal::cursor::move: EM_ASM(document.body.style.cursor = 'move';); break; - case cursor::not_allowed: + case emscripten_browser_cursor_internal::cursor::not_allowed: EM_ASM(document.body.style.cursor = 'not-allowed';); break; - case cursor::ew_resize: + case emscripten_browser_cursor_internal::cursor::ew_resize: EM_ASM(document.body.style.cursor = 'ew-resize';); break; - case cursor::ns_resize: + case emscripten_browser_cursor_internal::cursor::ns_resize: EM_ASM(document.body.style.cursor = 'ns-resize';); break; - case cursor::nesw_resize: + case emscripten_browser_cursor_internal::cursor::nesw_resize: EM_ASM(document.body.style.cursor = 'nesw-resize';); break; - case cursor::nwse_resize: + case emscripten_browser_cursor_internal::cursor::nwse_resize: EM_ASM(document.body.style.cursor = 'nwse-resize';); break; } } -void set(char const* new_cursor) +static void ImGui_ImplEmscripten_SetBrowserCursor(char const* new_cursor) { // Set the cursor from an arbitrary string EM_ASM({ @@ -577,11 +562,7 @@ void set(char const* new_cursor) }, new_cursor); } -} // namespace emscripten_browser_cursor_internal - -namespace { - -constexpr ImGuiMouseButton translate_mousebutton(unsigned short emscripten_button) +static constexpr ImGuiMouseButton ImGui_ImplEmscripten_TranslateMouseButton(unsigned short emscripten_button) { // Translate an emscripten-provided integer describing a mouse button to an imgui mouse button if (emscripten_button == 1) return ImGuiMouseButton_Middle; // 1 = middle mouse button @@ -590,7 +571,7 @@ constexpr ImGuiMouseButton translate_mousebutton(unsigned short emscripten_butto return emscripten_button; // any other button translates 1:1 } -ImGuiStorage const& get_key_translation_storage() +static ImGuiStorage const& ImGui_ImplEmscripten_GetKeyTranslationStorage() { static ImGuiStorage storage; static bool is_initialized = false; @@ -805,15 +786,13 @@ ImGuiStorage const& get_key_translation_storage() return storage; } -ImGuiKey translate_key(char const* emscripten_key) +static ImGuiKey ImGui_ImplEmscripten_TranslateKey(char const* emscripten_key) { // Translate a W3C KeyboardEvent.code string into an ImGuiKey. if (emscripten_key == nullptr || emscripten_key[0] == '\0') return ImGuiKey_None; - ImGuiStorage const& storage = get_key_translation_storage(); + ImGuiStorage const& storage = ImGui_ImplEmscripten_GetKeyTranslationStorage(); return (ImGuiKey)storage.GetInt(ImHashStr(emscripten_key), ImGuiKey_None); } -} // anonymous namespace - #endif // IMGUI_DISABLE From 30be530f9e2e4eac127990e3a5754b114635f8a5 Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Thu, 3 Sep 2026 14:24:06 +0100 Subject: [PATCH 54/72] Backends: Emscripten: Build key lookup from static mapping table --- backends/imgui_impl_emscripten.cpp | 420 +++++++++++++++-------------- 1 file changed, 215 insertions(+), 205 deletions(-) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index 5e69f8f1b..7cb2a4387 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -573,215 +573,225 @@ static constexpr ImGuiMouseButton ImGui_ImplEmscripten_TranslateMouseButton(unsi static ImGuiStorage const& ImGui_ImplEmscripten_GetKeyTranslationStorage() { + struct ImGui_ImplEmscripten_KeyTranslation + { + const char* EmscriptenKey; + ImGuiKey Key; + }; + static const ImGui_ImplEmscripten_KeyTranslation key_translations[] = + { + // main character keys + { "Backquote", ImGuiKey_GraveAccent }, + { "Backslash", ImGuiKey_Backslash }, + { "BracketLeft", ImGuiKey_LeftBracket }, + { "BracketRight", ImGuiKey_RightBracket }, + { "Comma", ImGuiKey_Comma }, + { "Digit0", ImGuiKey_0 }, + { "Digit1", ImGuiKey_1 }, + { "Digit2", ImGuiKey_2 }, + { "Digit3", ImGuiKey_3 }, + { "Digit4", ImGuiKey_4 }, + { "Digit5", ImGuiKey_5 }, + { "Digit6", ImGuiKey_6 }, + { "Digit7", ImGuiKey_7 }, + { "Digit8", ImGuiKey_8 }, + { "Digit9", ImGuiKey_9 }, + { "Equal", ImGuiKey_Equal }, + { "IntlBackslash", ImGuiKey_Backslash }, // Mapping to generic backslash + { "IntlRo", ImGuiKey_Slash }, // Closest match for non-standard layouts + { "IntlYen", ImGuiKey_Backslash }, // Closest match for non-standard layouts + { "KeyA", ImGuiKey_A }, + { "KeyB", ImGuiKey_B }, + { "KeyC", ImGuiKey_C }, + { "KeyD", ImGuiKey_D }, + { "KeyE", ImGuiKey_E }, + { "KeyF", ImGuiKey_F }, + { "KeyG", ImGuiKey_G }, + { "KeyH", ImGuiKey_H }, + { "KeyI", ImGuiKey_I }, + { "KeyJ", ImGuiKey_J }, + { "KeyK", ImGuiKey_K }, + { "KeyL", ImGuiKey_L }, + { "KeyM", ImGuiKey_M }, + { "KeyN", ImGuiKey_N }, + { "KeyO", ImGuiKey_O }, + { "KeyP", ImGuiKey_P }, + { "KeyQ", ImGuiKey_Q }, + { "KeyR", ImGuiKey_R }, + { "KeyS", ImGuiKey_S }, + { "KeyT", ImGuiKey_T }, + { "KeyU", ImGuiKey_U }, + { "KeyV", ImGuiKey_V }, + { "KeyW", ImGuiKey_W }, + { "KeyX", ImGuiKey_X }, + { "KeyY", ImGuiKey_Y }, + { "KeyZ", ImGuiKey_Z }, + { "Minus", ImGuiKey_Minus }, + { "Period", ImGuiKey_Period }, + { "Quote", ImGuiKey_Apostrophe }, + { "Semicolon", ImGuiKey_Semicolon }, + { "Slash", ImGuiKey_Slash }, + + // control keys + { "AltLeft", ImGuiKey_LeftAlt }, + { "AltRight", ImGuiKey_RightAlt }, + { "Backspace", ImGuiKey_Backspace }, + { "CapsLock", ImGuiKey_CapsLock }, + { "ContextMenu", ImGuiKey_Menu }, + { "ControlLeft", ImGuiKey_LeftCtrl }, + { "ControlRight", ImGuiKey_RightCtrl }, + { "Enter", ImGuiKey_Enter }, + { "MetaLeft", ImGuiKey_LeftSuper }, + { "MetaRight", ImGuiKey_RightSuper }, + { "ShiftLeft", ImGuiKey_LeftShift }, + { "ShiftRight", ImGuiKey_RightShift }, + { "Space", ImGuiKey_Space }, + { "Tab", ImGuiKey_Tab }, + + // navigation key group + { "Delete", ImGuiKey_Delete }, + { "End", ImGuiKey_End }, + //{ "Help", ImGuiKey_PrintScreen }, // Best approximation + { "Home", ImGuiKey_Home }, + { "Insert", ImGuiKey_Insert }, + { "PageDown", ImGuiKey_PageDown }, + { "PageUp", ImGuiKey_PageUp }, + + // arrow key group + { "ArrowDown", ImGuiKey_DownArrow }, + { "ArrowLeft", ImGuiKey_LeftArrow }, + { "ArrowRight", ImGuiKey_RightArrow }, + { "ArrowUp", ImGuiKey_UpArrow }, + + // browser key group + { "BrowserBack", ImGuiKey_AppBack }, // Pass through so the embedding app can decide + //{ "BrowserFavorites", ImGuiKey_None }, // No direct mapping + { "BrowserForward", ImGuiKey_AppForward }, // Pass through so the embedding app can decide + //{ "BrowserHome", ImGuiKey_None }, // No direct mapping + //{ "BrowserRefresh", ImGuiKey_None }, // No direct mapping + //{ "BrowserSearch", ImGuiKey_None }, // No direct mapping + //{ "BrowserStop", ImGuiKey_None }, // No direct mapping + + // number pad group + { "NumLock", ImGuiKey_NumLock }, + { "Numpad0", ImGuiKey_Keypad0 }, + { "Numpad1", ImGuiKey_Keypad1 }, + { "Numpad2", ImGuiKey_Keypad2 }, + { "Numpad3", ImGuiKey_Keypad3 }, + { "Numpad4", ImGuiKey_Keypad4 }, + { "Numpad5", ImGuiKey_Keypad5 }, + { "Numpad6", ImGuiKey_Keypad6 }, + { "Numpad7", ImGuiKey_Keypad7 }, + { "Numpad8", ImGuiKey_Keypad8 }, + { "Numpad9", ImGuiKey_Keypad9 }, + { "NumpadAdd", ImGuiKey_KeypadAdd }, + { "NumpadBackspace", ImGuiKey_Backspace }, // No direct mapping; backspace functionality + //{ "NumpadClear", ImGuiKey_None }, // No defined Dear ImGui mapping + //{ "NumpadClearEntry", ImGuiKey_None }, // No defined Dear ImGui mapping + { "NumpadComma", ImGuiKey_KeypadDecimal }, // Closest match + { "NumpadDecimal", ImGuiKey_KeypadDecimal }, + { "NumpadDivide", ImGuiKey_KeypadDivide }, + { "NumpadEnter", ImGuiKey_KeypadEnter }, + { "NumpadEqual", ImGuiKey_KeypadEqual }, + { "NumpadHash", ImGuiKey_Backslash }, // Mapped to generic backslash for telephone-style '#' + //{ "NumpadMemoryAdd", ImGuiKey_None }, // No defined mapping + //{ "NumpadMemoryClear", ImGuiKey_None }, // No defined mapping + //{ "NumpadMemoryRecall", ImGuiKey_None }, // No defined mapping + //{ "NumpadMemoryStore", ImGuiKey_None }, // No defined mapping + //{ "NumpadMemorySubtract", ImGuiKey_None }, // No defined mapping + { "NumpadMultiply", ImGuiKey_KeypadMultiply }, + { "NumpadParenLeft", ImGuiKey_LeftBracket }, // Closest available + { "NumpadParenRight", ImGuiKey_RightBracket }, // Closest available + { "NumpadStar", ImGuiKey_KeypadMultiply }, // Same as multiply + { "NumpadSubtract", ImGuiKey_KeypadSubtract }, + + // top row key group + { "Escape", ImGuiKey_Escape }, + { "F1", ImGuiKey_F1 }, + { "F2", ImGuiKey_F2 }, + { "F3", ImGuiKey_F3 }, + { "F4", ImGuiKey_F4 }, + { "F5", ImGuiKey_F5 }, + { "F6", ImGuiKey_F6 }, + { "F7", ImGuiKey_F7 }, + { "F8", ImGuiKey_F8 }, + { "F9", ImGuiKey_F9 }, + { "F10", ImGuiKey_F10 }, + { "F11", ImGuiKey_F11 }, + { "F12", ImGuiKey_F12 }, + { "F13", ImGuiKey_F13 }, + { "F14", ImGuiKey_F14 }, + { "F15", ImGuiKey_F15 }, + { "F16", ImGuiKey_F16 }, + { "F17", ImGuiKey_F17 }, + { "F18", ImGuiKey_F18 }, + { "F19", ImGuiKey_F19 }, + { "F20", ImGuiKey_F20 }, + { "F21", ImGuiKey_F21 }, + { "F22", ImGuiKey_F22 }, + { "F23", ImGuiKey_F23 }, + { "F24", ImGuiKey_F24 }, + //{ "Fn", ImGuiKey_None }, // No direct mapping + //{ "FnLock", ImGuiKey_None }, // No direct mapping + { "PrintScreen", ImGuiKey_PrintScreen }, + { "ScrollLock", ImGuiKey_ScrollLock }, + { "Pause", ImGuiKey_Pause }, + + // clipboard/editing keys without direct mapping + //{ "Abort", ImGuiKey_None }, + //{ "Again", ImGuiKey_None }, + //{ "Convert", ImGuiKey_None }, + //{ "Copy", ImGuiKey_None }, + //{ "Cut", ImGuiKey_None }, + //{ "Find", ImGuiKey_None }, + //{ "Open", ImGuiKey_None }, + //{ "Paste", ImGuiKey_None }, + //{ "Props", ImGuiKey_None }, + //{ "Resume", ImGuiKey_None }, + //{ "Select", ImGuiKey_None }, + //{ "Undo", ImGuiKey_None }, + + // IME and international keys without direct mapping + //{ "Hiragana", ImGuiKey_None }, + //{ "KanaMode", ImGuiKey_None }, + //{ "Katakana", ImGuiKey_None }, + //{ "Lang1", ImGuiKey_None }, + //{ "Lang2", ImGuiKey_None }, + //{ "NonConvert", ImGuiKey_None }, + + // media and launcher keys without direct mapping + //{ "AudioVolumeDown", ImGuiKey_None }, + //{ "AudioVolumeMute", ImGuiKey_None }, + //{ "AudioVolumeUp", ImGuiKey_None }, + //{ "LaunchApp1", ImGuiKey_None }, + //{ "LaunchApp2", ImGuiKey_None }, + //{ "LaunchMail", ImGuiKey_None }, + //{ "MediaPlayPause", ImGuiKey_None }, + //{ "MediaSelect", ImGuiKey_None }, + //{ "MediaStop", ImGuiKey_None }, + //{ "MediaTrackNext", ImGuiKey_None }, + //{ "MediaTrackPrevious", ImGuiKey_None }, + + // system keys without direct mapping + //{ "Eject", ImGuiKey_None }, + //{ "Hyper", ImGuiKey_None }, + //{ "Power", ImGuiKey_None }, + //{ "Sleep", ImGuiKey_None }, + //{ "Super", ImGuiKey_None }, + //{ "Suspend", ImGuiKey_None }, + //{ "Turbo", ImGuiKey_None }, + //{ "Unidentified", ImGuiKey_None }, + //{ "WakeUp", ImGuiKey_None }, + }; + static ImGuiStorage storage; static bool is_initialized = false; if (is_initialized) return storage; is_initialized = true; - storage.Data.reserve(128); - - // main character keys - storage.Data.push_back(ImGuiStoragePair(ImHashStr("Backquote"), ImGuiKey_GraveAccent)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("Backslash"), ImGuiKey_Backslash)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("BracketLeft"), ImGuiKey_LeftBracket)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("BracketRight"), ImGuiKey_RightBracket)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("Comma"), ImGuiKey_Comma)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("Digit0"), ImGuiKey_0)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("Digit1"), ImGuiKey_1)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("Digit2"), ImGuiKey_2)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("Digit3"), ImGuiKey_3)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("Digit4"), ImGuiKey_4)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("Digit5"), ImGuiKey_5)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("Digit6"), ImGuiKey_6)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("Digit7"), ImGuiKey_7)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("Digit8"), ImGuiKey_8)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("Digit9"), ImGuiKey_9)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("Equal"), ImGuiKey_Equal)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("IntlBackslash"), ImGuiKey_Backslash)); // Mapping to generic backslash - storage.Data.push_back(ImGuiStoragePair(ImHashStr("IntlRo"), ImGuiKey_Slash)); // Closest match for non-standard layouts - storage.Data.push_back(ImGuiStoragePair(ImHashStr("IntlYen"), ImGuiKey_Backslash)); // Closest match for non-standard layouts - storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyA"), ImGuiKey_A)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyB"), ImGuiKey_B)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyC"), ImGuiKey_C)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyD"), ImGuiKey_D)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyE"), ImGuiKey_E)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyF"), ImGuiKey_F)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyG"), ImGuiKey_G)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyH"), ImGuiKey_H)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyI"), ImGuiKey_I)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyJ"), ImGuiKey_J)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyK"), ImGuiKey_K)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyL"), ImGuiKey_L)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyM"), ImGuiKey_M)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyN"), ImGuiKey_N)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyO"), ImGuiKey_O)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyP"), ImGuiKey_P)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyQ"), ImGuiKey_Q)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyR"), ImGuiKey_R)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyS"), ImGuiKey_S)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyT"), ImGuiKey_T)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyU"), ImGuiKey_U)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyV"), ImGuiKey_V)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyW"), ImGuiKey_W)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyX"), ImGuiKey_X)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyY"), ImGuiKey_Y)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("KeyZ"), ImGuiKey_Z)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("Minus"), ImGuiKey_Minus)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("Period"), ImGuiKey_Period)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("Quote"), ImGuiKey_Apostrophe)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("Semicolon"), ImGuiKey_Semicolon)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("Slash"), ImGuiKey_Slash)); - - // control keys - storage.Data.push_back(ImGuiStoragePair(ImHashStr("AltLeft"), ImGuiKey_LeftAlt)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("AltRight"), ImGuiKey_RightAlt)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("Backspace"), ImGuiKey_Backspace)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("CapsLock"), ImGuiKey_CapsLock)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("ContextMenu"), ImGuiKey_Menu)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("ControlLeft"), ImGuiKey_LeftCtrl)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("ControlRight"), ImGuiKey_RightCtrl)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("Enter"), ImGuiKey_Enter)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("MetaLeft"), ImGuiKey_LeftSuper)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("MetaRight"), ImGuiKey_RightSuper)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("ShiftLeft"), ImGuiKey_LeftShift)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("ShiftRight"), ImGuiKey_RightShift)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("Space"), ImGuiKey_Space)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("Tab"), ImGuiKey_Tab)); - - // navigation key group - storage.Data.push_back(ImGuiStoragePair(ImHashStr("Delete"), ImGuiKey_Delete)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("End"), ImGuiKey_End)); - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Help"), ImGuiKey_PrintScreen)); // Best approximation - storage.Data.push_back(ImGuiStoragePair(ImHashStr("Home"), ImGuiKey_Home)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("Insert"), ImGuiKey_Insert)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("PageDown"), ImGuiKey_PageDown)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("PageUp"), ImGuiKey_PageUp)); - - // arrow key group - storage.Data.push_back(ImGuiStoragePair(ImHashStr("ArrowDown"), ImGuiKey_DownArrow)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("ArrowLeft"), ImGuiKey_LeftArrow)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("ArrowRight"), ImGuiKey_RightArrow)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("ArrowUp"), ImGuiKey_UpArrow)); - - // browser key group - storage.Data.push_back(ImGuiStoragePair(ImHashStr("BrowserBack"), ImGuiKey_AppBack)); // Pass through so the embedding app can decide - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("BrowserFavorites"), ImGuiKey_None)); // No direct mapping - storage.Data.push_back(ImGuiStoragePair(ImHashStr("BrowserForward"), ImGuiKey_AppForward)); // Pass through so the embedding app can decide - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("BrowserHome"), ImGuiKey_None)); // No direct mapping - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("BrowserRefresh"), ImGuiKey_None)); // No direct mapping - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("BrowserSearch"), ImGuiKey_None)); // No direct mapping - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("BrowserStop"), ImGuiKey_None)); // No direct mapping - - // number pad group - storage.Data.push_back(ImGuiStoragePair(ImHashStr("NumLock"), ImGuiKey_NumLock)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("Numpad0"), ImGuiKey_Keypad0)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("Numpad1"), ImGuiKey_Keypad1)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("Numpad2"), ImGuiKey_Keypad2)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("Numpad3"), ImGuiKey_Keypad3)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("Numpad4"), ImGuiKey_Keypad4)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("Numpad5"), ImGuiKey_Keypad5)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("Numpad6"), ImGuiKey_Keypad6)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("Numpad7"), ImGuiKey_Keypad7)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("Numpad8"), ImGuiKey_Keypad8)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("Numpad9"), ImGuiKey_Keypad9)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("NumpadAdd"), ImGuiKey_KeypadAdd)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("NumpadBackspace"), ImGuiKey_Backspace)); // No direct mapping; backspace functionality - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("NumpadClear"), ImGuiKey_None)); // No defined Dear ImGui mapping - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("NumpadClearEntry"), ImGuiKey_None)); // No defined Dear ImGui mapping - storage.Data.push_back(ImGuiStoragePair(ImHashStr("NumpadComma"), ImGuiKey_KeypadDecimal)); // Closest match - storage.Data.push_back(ImGuiStoragePair(ImHashStr("NumpadDecimal"), ImGuiKey_KeypadDecimal)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("NumpadDivide"), ImGuiKey_KeypadDivide)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("NumpadEnter"), ImGuiKey_KeypadEnter)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("NumpadEqual"), ImGuiKey_KeypadEqual)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("NumpadHash"), ImGuiKey_Backslash)); // Mapped to generic backslash for telephone-style '#' - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("NumpadMemoryAdd"), ImGuiKey_None)); // No defined mapping - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("NumpadMemoryClear"), ImGuiKey_None)); // No defined mapping - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("NumpadMemoryRecall"), ImGuiKey_None)); // No defined mapping - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("NumpadMemoryStore"), ImGuiKey_None)); // No defined mapping - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("NumpadMemorySubtract"), ImGuiKey_None)); // No defined mapping - storage.Data.push_back(ImGuiStoragePair(ImHashStr("NumpadMultiply"), ImGuiKey_KeypadMultiply)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("NumpadParenLeft"), ImGuiKey_LeftBracket)); // Closest available - storage.Data.push_back(ImGuiStoragePair(ImHashStr("NumpadParenRight"), ImGuiKey_RightBracket)); // Closest available - storage.Data.push_back(ImGuiStoragePair(ImHashStr("NumpadStar"), ImGuiKey_KeypadMultiply)); // Same as multiply - storage.Data.push_back(ImGuiStoragePair(ImHashStr("NumpadSubtract"), ImGuiKey_KeypadSubtract)); - - // top row key group - storage.Data.push_back(ImGuiStoragePair(ImHashStr("Escape"), ImGuiKey_Escape)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("F1"), ImGuiKey_F1)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("F2"), ImGuiKey_F2)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("F3"), ImGuiKey_F3)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("F4"), ImGuiKey_F4)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("F5"), ImGuiKey_F5)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("F6"), ImGuiKey_F6)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("F7"), ImGuiKey_F7)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("F8"), ImGuiKey_F8)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("F9"), ImGuiKey_F9)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("F10"), ImGuiKey_F10)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("F11"), ImGuiKey_F11)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("F12"), ImGuiKey_F12)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("F13"), ImGuiKey_F13)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("F14"), ImGuiKey_F14)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("F15"), ImGuiKey_F15)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("F16"), ImGuiKey_F16)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("F17"), ImGuiKey_F17)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("F18"), ImGuiKey_F18)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("F19"), ImGuiKey_F19)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("F20"), ImGuiKey_F20)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("F21"), ImGuiKey_F21)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("F22"), ImGuiKey_F22)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("F23"), ImGuiKey_F23)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("F24"), ImGuiKey_F24)); - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Fn"), ImGuiKey_None)); // No direct mapping - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("FnLock"), ImGuiKey_None)); // No direct mapping - storage.Data.push_back(ImGuiStoragePair(ImHashStr("PrintScreen"), ImGuiKey_PrintScreen)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("ScrollLock"), ImGuiKey_ScrollLock)); - storage.Data.push_back(ImGuiStoragePair(ImHashStr("Pause"), ImGuiKey_Pause)); - - // clipboard/editing keys without direct mapping - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Abort"), ImGuiKey_None)); - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Again"), ImGuiKey_None)); - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Convert"), ImGuiKey_None)); - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Copy"), ImGuiKey_None)); - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Cut"), ImGuiKey_None)); - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Find"), ImGuiKey_None)); - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Open"), ImGuiKey_None)); - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Paste"), ImGuiKey_None)); - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Props"), ImGuiKey_None)); - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Resume"), ImGuiKey_None)); - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Select"), ImGuiKey_None)); - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Undo"), ImGuiKey_None)); - - // IME and international keys without direct mapping - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Hiragana"), ImGuiKey_None)); - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("KanaMode"), ImGuiKey_None)); - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Katakana"), ImGuiKey_None)); - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Lang1"), ImGuiKey_None)); - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Lang2"), ImGuiKey_None)); - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("NonConvert"), ImGuiKey_None)); - - // media and launcher keys without direct mapping - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("AudioVolumeDown"), ImGuiKey_None)); - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("AudioVolumeMute"), ImGuiKey_None)); - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("AudioVolumeUp"), ImGuiKey_None)); - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("LaunchApp1"), ImGuiKey_None)); - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("LaunchApp2"), ImGuiKey_None)); - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("LaunchMail"), ImGuiKey_None)); - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("MediaPlayPause"), ImGuiKey_None)); - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("MediaSelect"), ImGuiKey_None)); - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("MediaStop"), ImGuiKey_None)); - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("MediaTrackNext"), ImGuiKey_None)); - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("MediaTrackPrevious"), ImGuiKey_None)); - - // system keys without direct mapping - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Eject"), ImGuiKey_None)); - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Hyper"), ImGuiKey_None)); - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Power"), ImGuiKey_None)); - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Sleep"), ImGuiKey_None)); - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Super"), ImGuiKey_None)); - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Suspend"), ImGuiKey_None)); - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Turbo"), ImGuiKey_None)); - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("Unidentified"), ImGuiKey_None)); - //storage.Data.push_back(ImGuiStoragePair(ImHashStr("WakeUp"), ImGuiKey_None)); - + storage.Data.reserve(IM_ARRAYSIZE(key_translations)); + for (int n = 0; n != IM_ARRAYSIZE(key_translations); ++n) { + storage.Data.push_back(ImGuiStoragePair(ImHashStr(key_translations[n].EmscriptenKey), key_translations[n].Key)); + } storage.BuildSortByKey(); return storage; } From 1ea794bdb0ce6005e553bd380c4edc4942b9b27e Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Thu, 3 Sep 2026 14:27:18 +0100 Subject: [PATCH 55/72] update changelog --- backends/imgui_impl_emscripten.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index 7cb2a4387..7b807022b 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -4,6 +4,7 @@ // // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-09-03: Updated coding style and simplified key translation table setup. // 2026-04-02: Inputs: Replaced custom KeyboardEvent.code parser with ImHashStr()/ImGuiStorage lookup to match Dear ImGui backend style. // 2026-03-31: Emscripten: Added configurable TargetDevicePixelRatio to control how browser device pixels map to Dear ImGui pixels. // 2026-03-31: Inputs: Added BrowserBack/Forward and F13-F24 key mappings. From 40ed02df53bdbef62ed7f06ccc21f643aa208067 Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Thu, 3 Sep 2026 14:31:37 +0100 Subject: [PATCH 56/72] Examples: Emscripten+WebGPU: Match other example initialisation style --- examples/example_emscripten_wgpu/main.cpp | 54 +++++++++++------------ 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/examples/example_emscripten_wgpu/main.cpp b/examples/example_emscripten_wgpu/main.cpp index e5f916c76..95770f94c 100644 --- a/examples/example_emscripten_wgpu/main.cpp +++ b/examples/example_emscripten_wgpu/main.cpp @@ -26,11 +26,11 @@ #endif // Data -static WGPUInstance wgpu_instance{nullptr}; -static WGPUDevice wgpu_device{nullptr}; -static WGPUSurface wgpu_surface{nullptr}; -static WGPUQueue wgpu_queue{nullptr}; -static WGPUSurfaceConfiguration wgpu_surface_configuration{}; +static WGPUInstance wgpu_instance = nullptr; +static WGPUDevice wgpu_device = nullptr; +static WGPUSurface wgpu_surface = nullptr; +static WGPUQueue wgpu_queue = nullptr; +static WGPUSurfaceConfiguration wgpu_surface_configuration = {}; static int wgpu_surface_width = 1280; static int wgpu_surface_height = 800; @@ -93,30 +93,30 @@ static WGPUDevice RequestDevice(wgpu::Instance& instance, wgpu::Adapter& adapter static bool InitWGPU() { - WGPUTextureFormat preferred_fmt{WGPUTextureFormat_Undefined}; + WGPUTextureFormat preferred_fmt = WGPUTextureFormat_Undefined; - wgpu::InstanceDescriptor instance_desc{}; + wgpu::InstanceDescriptor instance_desc = {}; static constexpr wgpu::InstanceFeatureName timedWaitAny = wgpu::InstanceFeatureName::TimedWaitAny; instance_desc.requiredFeatureCount = 1; instance_desc.requiredFeatures = &timedWaitAny; - wgpu::Instance instance{wgpu::CreateInstance(&instance_desc)}; + wgpu::Instance instance = wgpu::CreateInstance(&instance_desc); wgpu::Adapter adapter = RequestAdapter(instance); ImGui_ImplWGPU_DebugPrintAdapterInfo(adapter.Get()); wgpu_device = RequestDevice(instance, adapter); - wgpu::EmscriptenSurfaceSourceCanvasHTMLSelector canvas_desc{}; + wgpu::EmscriptenSurfaceSourceCanvasHTMLSelector canvas_desc = {}; canvas_desc.selector = "#canvas"; - wgpu::SurfaceDescriptor surface_desc{}; + wgpu::SurfaceDescriptor surface_desc = {}; surface_desc.nextInChain = &canvas_desc; wgpu_surface = instance.CreateSurface(&surface_desc).MoveToCHandle(); if (!wgpu_surface) return false; wgpu_instance = instance.MoveToCHandle(); - WGPUSurfaceCapabilities surface_capabilities{}; + WGPUSurfaceCapabilities surface_capabilities = {}; wgpuSurfaceGetCapabilities(wgpu_surface, adapter.Get(), &surface_capabilities); preferred_fmt = surface_capabilities.formats[0]; @@ -136,7 +136,7 @@ static bool InitWGPU() static void GetFramebufferSizeFromDisplaySize(int width, int height, int* framebuffer_width, int* framebuffer_height) { - ImGuiIO& io{ImGui::GetIO()}; + ImGuiIO& io = ImGui::GetIO(); *framebuffer_width = static_cast(width * io.DisplayFramebufferScale.x + 0.5f); *framebuffer_height = static_cast(height * io.DisplayFramebufferScale.y + 0.5f); } @@ -145,8 +145,8 @@ static void ResizeSurface(int width, int height) { if (width <= 0 || height <= 0) return; - int framebuffer_width{0}; - int framebuffer_height{0}; + int framebuffer_width = 0; + int framebuffer_height = 0; GetFramebufferSizeFromDisplaySize(width, height, &framebuffer_width, &framebuffer_height); if (framebuffer_width <= 0 || framebuffer_height <= 0) return; @@ -164,7 +164,7 @@ int main(int, char**) // Setup Dear ImGui context IMGUI_CHECKVERSION(); ImGui::CreateContext(); - ImGuiIO& io{ImGui::GetIO()}; (void)io; + ImGuiIO& io = ImGui::GetIO(); (void)io; io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls @@ -205,9 +205,9 @@ int main(int, char**) #endif // Our state - bool show_demo_window{true}; - bool show_another_window{false}; - ImVec4 clear_color{0.45f, 0.55f, 0.60f, 1.00f}; + bool show_demo_window = true; + bool show_another_window = false; + ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); // Main loop // For an Emscripten build we are disabling file-system access, so let's not attempt to do a fopen() of the imgui.ini file. @@ -223,8 +223,8 @@ int main(int, char**) if (width <= 0 || height <= 0) continue; - int framebuffer_width{0}; - int framebuffer_height{0}; + int framebuffer_width = 0; + int framebuffer_height = 0; GetFramebufferSizeFromDisplaySize(width, height, &framebuffer_width, &framebuffer_height); if (framebuffer_width != wgpu_surface_width || framebuffer_height != wgpu_surface_height) ResizeSurface(width, height); @@ -254,8 +254,8 @@ int main(int, char**) // 2. Show a simple window that we create ourselves. We use a Begin/End pair to create a named window. { - static float f{0.0f}; - static int counter{0}; + static float f = 0.0f; + static int counter = 0; ImGui::Begin("Hello, world!"); // Create a window called "Hello, world!" and append into it. @@ -287,7 +287,7 @@ int main(int, char**) // Rendering ImGui::Render(); - WGPUTextureViewDescriptor view_desc{}; + WGPUTextureViewDescriptor view_desc = {}; view_desc.format = wgpu_surface_configuration.format; view_desc.dimension = WGPUTextureViewDimension_2D; view_desc.mipLevelCount = WGPU_MIP_LEVEL_COUNT_UNDEFINED; @@ -296,26 +296,26 @@ int main(int, char**) WGPUTextureView texture_view = wgpuTextureCreateView(surface_texture.texture, &view_desc); - WGPURenderPassColorAttachment color_attachments{}; + WGPURenderPassColorAttachment color_attachments = {}; color_attachments.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED; color_attachments.loadOp = WGPULoadOp_Clear; color_attachments.storeOp = WGPUStoreOp_Store; color_attachments.clearValue = { clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w }; color_attachments.view = texture_view; - WGPURenderPassDescriptor render_pass_desc{}; + WGPURenderPassDescriptor render_pass_desc = {}; render_pass_desc.colorAttachmentCount = 1; render_pass_desc.colorAttachments = &color_attachments; render_pass_desc.depthStencilAttachment = nullptr; - WGPUCommandEncoderDescriptor enc_desc{}; + WGPUCommandEncoderDescriptor enc_desc = {}; WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(wgpu_device, &enc_desc); WGPURenderPassEncoder pass = wgpuCommandEncoderBeginRenderPass(encoder, &render_pass_desc); ImGui_ImplWGPU_RenderDrawData(ImGui::GetDrawData(), pass); wgpuRenderPassEncoderEnd(pass); - WGPUCommandBufferDescriptor cmd_buffer_desc{}; + WGPUCommandBufferDescriptor cmd_buffer_desc = {}; WGPUCommandBuffer cmd_buffer = wgpuCommandEncoderFinish(encoder, &cmd_buffer_desc); wgpuQueueSubmit(wgpu_queue, 1, &cmd_buffer); From b7e7477fe8e4ec1388274ca77355c7c1080d5f84 Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Thu, 3 Sep 2026 14:32:39 +0100 Subject: [PATCH 57/72] Examples: Emscripten+WebGPU: Match project C cast style --- examples/example_emscripten_wgpu/main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/example_emscripten_wgpu/main.cpp b/examples/example_emscripten_wgpu/main.cpp index 95770f94c..84e609dc6 100644 --- a/examples/example_emscripten_wgpu/main.cpp +++ b/examples/example_emscripten_wgpu/main.cpp @@ -137,8 +137,8 @@ static bool InitWGPU() static void GetFramebufferSizeFromDisplaySize(int width, int height, int* framebuffer_width, int* framebuffer_height) { ImGuiIO& io = ImGui::GetIO(); - *framebuffer_width = static_cast(width * io.DisplayFramebufferScale.x + 0.5f); - *framebuffer_height = static_cast(height * io.DisplayFramebufferScale.y + 0.5f); + *framebuffer_width = (int)(width * io.DisplayFramebufferScale.x + 0.5f); + *framebuffer_height = (int)(height * io.DisplayFramebufferScale.y + 0.5f); } static void ResizeSurface(int width, int height) From 4a63b476649b0d421ee79d9957f941c7b23d022c Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Thu, 3 Sep 2026 14:35:41 +0100 Subject: [PATCH 58/72] Examples: Emscripten+WebGPU: Align WebGPU setup with other examples --- examples/example_emscripten_wgpu/main.cpp | 194 +++++++++++----------- 1 file changed, 99 insertions(+), 95 deletions(-) diff --git a/examples/example_emscripten_wgpu/main.cpp b/examples/example_emscripten_wgpu/main.cpp index 84e609dc6..c30a03211 100644 --- a/examples/example_emscripten_wgpu/main.cpp +++ b/examples/example_emscripten_wgpu/main.cpp @@ -38,101 +38,6 @@ static int wgpu_surface_height = 800; static bool InitWGPU(); static void GetFramebufferSizeFromDisplaySize(int width, int height, int* framebuffer_width, int* framebuffer_height); static void ResizeSurface(int width, int height); -static WGPUAdapter RequestAdapter(wgpu::Instance& instance); -static WGPUDevice RequestDevice(wgpu::Instance& instance, wgpu::Adapter& adapter); - -static WGPUAdapter RequestAdapter(wgpu::Instance& instance) -{ - wgpu::Adapter acquired_adapter; - wgpu::RequestAdapterOptions adapter_options; - auto onRequestAdapter = [&](wgpu::RequestAdapterStatus status, wgpu::Adapter adapter, wgpu::StringView message) - { - if (status != wgpu::RequestAdapterStatus::Success) - { - printf("Failed to get an adapter: %s\n", message.data); - return; - } - acquired_adapter = std::move(adapter); - }; - - // Synchronously (wait until) acquire Adapter - wgpu::Future wait_adapter_func { instance.RequestAdapter(&adapter_options, wgpu::CallbackMode::WaitAnyOnly, onRequestAdapter) }; - wgpu::WaitStatus wait_status_adapter = instance.WaitAny(wait_adapter_func, UINT64_MAX); - IM_ASSERT(acquired_adapter != nullptr && wait_status_adapter == wgpu::WaitStatus::Success && "Error on Adapter request"); - return acquired_adapter.MoveToCHandle(); -} - -static WGPUDevice RequestDevice(wgpu::Instance& instance, wgpu::Adapter& adapter) -{ - // Set device callback functions - wgpu::DeviceDescriptor device_desc; - device_desc.SetDeviceLostCallback(wgpu::CallbackMode::AllowSpontaneous, - [](const wgpu::Device&, wgpu::DeviceLostReason type, wgpu::StringView msg) { fprintf(stderr, "%s error: %s\n", ImGui_ImplWGPU_GetDeviceLostReasonName((WGPUDeviceLostReason)type), msg.data); } - ); - device_desc.SetUncapturedErrorCallback( - [](const wgpu::Device&, wgpu::ErrorType type, wgpu::StringView msg) { fprintf(stderr, "%s error: %s\n", ImGui_ImplWGPU_GetErrorTypeName((WGPUErrorType)type), msg.data); } - ); - - wgpu::Device acquired_device; - auto onRequestDevice = [&](wgpu::RequestDeviceStatus status, wgpu::Device local_device, wgpu::StringView message) - { - if (status != wgpu::RequestDeviceStatus::Success) - { - printf("Failed to get a device: %s\n", message.data); - return; - } - acquired_device = std::move(local_device); - }; - - // Synchronously (wait until) get Device - wgpu::Future wait_device_func { adapter.RequestDevice(&device_desc, wgpu::CallbackMode::WaitAnyOnly, onRequestDevice) }; - wgpu::WaitStatus wait_status_device = instance.WaitAny(wait_device_func, UINT64_MAX); - IM_ASSERT(acquired_device != nullptr && wait_status_device == wgpu::WaitStatus::Success && "Error on Device request"); - return acquired_device.MoveToCHandle(); -} - -static bool InitWGPU() -{ - WGPUTextureFormat preferred_fmt = WGPUTextureFormat_Undefined; - - wgpu::InstanceDescriptor instance_desc = {}; - static constexpr wgpu::InstanceFeatureName timedWaitAny = wgpu::InstanceFeatureName::TimedWaitAny; - instance_desc.requiredFeatureCount = 1; - instance_desc.requiredFeatures = &timedWaitAny; - wgpu::Instance instance = wgpu::CreateInstance(&instance_desc); - - wgpu::Adapter adapter = RequestAdapter(instance); - ImGui_ImplWGPU_DebugPrintAdapterInfo(adapter.Get()); - - wgpu_device = RequestDevice(instance, adapter); - - wgpu::EmscriptenSurfaceSourceCanvasHTMLSelector canvas_desc = {}; - canvas_desc.selector = "#canvas"; - - wgpu::SurfaceDescriptor surface_desc = {}; - surface_desc.nextInChain = &canvas_desc; - wgpu_surface = instance.CreateSurface(&surface_desc).MoveToCHandle(); - if (!wgpu_surface) return false; - - wgpu_instance = instance.MoveToCHandle(); - - WGPUSurfaceCapabilities surface_capabilities = {}; - wgpuSurfaceGetCapabilities(wgpu_surface, adapter.Get(), &surface_capabilities); - preferred_fmt = surface_capabilities.formats[0]; - - wgpu_surface_configuration.presentMode = WGPUPresentMode_Fifo; - wgpu_surface_configuration.alphaMode = WGPUCompositeAlphaMode_Auto; - wgpu_surface_configuration.usage = WGPUTextureUsage_RenderAttachment; - wgpu_surface_configuration.width = wgpu_surface_width; - wgpu_surface_configuration.height = wgpu_surface_height; - wgpu_surface_configuration.device = wgpu_device; - wgpu_surface_configuration.format = preferred_fmt; - - wgpuSurfaceConfigure(wgpu_surface, &wgpu_surface_configuration); - wgpu_queue = wgpuDeviceGetQueue(wgpu_device); - - return true; -} static void GetFramebufferSizeFromDisplaySize(int width, int height, int* framebuffer_width, int* framebuffer_height) { @@ -339,3 +244,102 @@ int main(int, char**) return 0; } + +static WGPUAdapter RequestAdapter(wgpu::Instance& instance) +{ + wgpu::Adapter acquired_adapter; + wgpu::RequestAdapterOptions adapter_options; + auto onRequestAdapter = [&](wgpu::RequestAdapterStatus status, wgpu::Adapter adapter, wgpu::StringView message) + { + if (status != wgpu::RequestAdapterStatus::Success) + { + printf("Failed to get an adapter: %s\n", message.data); + return; + } + acquired_adapter = std::move(adapter); + }; + + // Synchronously (wait until) acquire Adapter + wgpu::Future waitAdapterFunc { instance.RequestAdapter(&adapter_options, wgpu::CallbackMode::WaitAnyOnly, onRequestAdapter) }; + wgpu::WaitStatus waitStatusAdapter = instance.WaitAny(waitAdapterFunc, UINT64_MAX); + IM_ASSERT(acquired_adapter != nullptr && waitStatusAdapter == wgpu::WaitStatus::Success && "Error on Adapter request"); + return acquired_adapter.MoveToCHandle(); +} + +static WGPUDevice RequestDevice(wgpu::Instance& instance, wgpu::Adapter& adapter) +{ + // Set device callback functions + wgpu::DeviceDescriptor device_desc; + device_desc.SetDeviceLostCallback(wgpu::CallbackMode::AllowSpontaneous, + [](const wgpu::Device&, wgpu::DeviceLostReason type, wgpu::StringView msg) { fprintf(stderr, "%s error: %s\n", ImGui_ImplWGPU_GetDeviceLostReasonName((WGPUDeviceLostReason)type), msg.data); } + ); + device_desc.SetUncapturedErrorCallback( + [](const wgpu::Device&, wgpu::ErrorType type, wgpu::StringView msg) { fprintf(stderr, "%s error: %s\n", ImGui_ImplWGPU_GetErrorTypeName((WGPUErrorType)type), msg.data); } + ); + + wgpu::Device acquired_device; + auto onRequestDevice = [&](wgpu::RequestDeviceStatus status, wgpu::Device local_device, wgpu::StringView message) + { + if (status != wgpu::RequestDeviceStatus::Success) + { + printf("Failed to get a device: %s\n", message.data); + return; + } + acquired_device = std::move(local_device); + }; + + // Synchronously (wait until) get Device + wgpu::Future waitDeviceFunc { adapter.RequestDevice(&device_desc, wgpu::CallbackMode::WaitAnyOnly, onRequestDevice) }; + wgpu::WaitStatus waitStatusDevice = instance.WaitAny(waitDeviceFunc, UINT64_MAX); + IM_ASSERT(acquired_device != nullptr && waitStatusDevice == wgpu::WaitStatus::Success && "Error on Device request"); + return acquired_device.MoveToCHandle(); +} + +static bool InitWGPU() +{ + WGPUTextureFormat preferred_fmt = WGPUTextureFormat_Undefined; // acquired from SurfaceCapabilities + + // Google DAWN backend: Adapter and Device acquisition, Surface creation + wgpu::InstanceDescriptor instance_desc = {}; + static constexpr wgpu::InstanceFeatureName timedWaitAny = wgpu::InstanceFeatureName::TimedWaitAny; + instance_desc.requiredFeatureCount = 1; + instance_desc.requiredFeatures = &timedWaitAny; + wgpu::Instance instance = wgpu::CreateInstance(&instance_desc); + + wgpu::Adapter adapter = RequestAdapter(instance); + ImGui_ImplWGPU_DebugPrintAdapterInfo(adapter.Get()); + + wgpu_device = RequestDevice(instance, adapter); + + // Create the surface. + wgpu::EmscriptenSurfaceSourceCanvasHTMLSelector canvas_desc = {}; + canvas_desc.selector = "#canvas"; + + wgpu::SurfaceDescriptor surface_desc = {}; + surface_desc.nextInChain = &canvas_desc; + wgpu::Surface surface = instance.CreateSurface(&surface_desc); + if (!surface) + return false; + + // Moving Dawn objects into WGPU handles + wgpu_instance = instance.MoveToCHandle(); + wgpu_surface = surface.MoveToCHandle(); + + WGPUSurfaceCapabilities surface_capabilities = {}; + wgpuSurfaceGetCapabilities(wgpu_surface, adapter.Get(), &surface_capabilities); + + preferred_fmt = surface_capabilities.formats[0]; + + wgpu_surface_configuration.presentMode = WGPUPresentMode_Fifo; + wgpu_surface_configuration.alphaMode = WGPUCompositeAlphaMode_Auto; + wgpu_surface_configuration.usage = WGPUTextureUsage_RenderAttachment; + wgpu_surface_configuration.width = wgpu_surface_width; + wgpu_surface_configuration.height = wgpu_surface_height; + wgpu_surface_configuration.device = wgpu_device; + wgpu_surface_configuration.format = preferred_fmt; + + wgpuSurfaceConfigure(wgpu_surface, &wgpu_surface_configuration); + wgpu_queue = wgpuDeviceGetQueue(wgpu_device); + + return true; +} From 30a7ec6fd6f1fe4cf14f45f5c745ef9fc2c46523 Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Thu, 3 Sep 2026 14:40:16 +0100 Subject: [PATCH 59/72] Examples: Emscripten+WebGPU: Align demo window with other examples --- examples/example_emscripten_wgpu/main.cpp | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/examples/example_emscripten_wgpu/main.cpp b/examples/example_emscripten_wgpu/main.cpp index c30a03211..41d47b85d 100644 --- a/examples/example_emscripten_wgpu/main.cpp +++ b/examples/example_emscripten_wgpu/main.cpp @@ -162,17 +162,17 @@ int main(int, char**) static float f = 0.0f; static int counter = 0; - ImGui::Begin("Hello, world!"); // Create a window called "Hello, world!" and append into it. + ImGui::Begin("Hello, world!"); // Create a window called "Hello, world!" and append into it. - ImGui::Text("This is some useful text."); // Display some text (you can use a format strings too) - ImGui::Text("Platform backend: imgui_impl_emscripten"); - ImGui::Checkbox("Demo Window", &show_demo_window); // Edit bools storing our window open/close state + ImGui::Text("This is some useful text."); // Display some text (you can use a format strings too) + ImGui::Checkbox("Demo Window", &show_demo_window); // Edit bools storing our window open/close state ImGui::Checkbox("Another Window", &show_another_window); - ImGui::SliderFloat("float", &f, 0.0f, 1.0f); // Edit 1 float using a slider from 0.0f to 1.0f - ImGui::ColorEdit3("clear color", (float*)&clear_color); // Edit 3 floats representing a color + ImGui::SliderFloat("float", &f, 0.0f, 1.0f); // Edit 1 float using a slider from 0.0f to 1.0f + ImGui::ColorEdit3("clear color", (float*)&clear_color); // Edit 3 floats representing a color - if (ImGui::Button("Button")) ++counter; // Buttons return true when clicked (most widgets return true when edited/activated) + if (ImGui::Button("Button")) // Buttons return true when clicked (most widgets return true when edited/activated) + counter++; ImGui::SameLine(); ImGui::Text("counter = %d", counter); @@ -183,9 +183,10 @@ int main(int, char**) // 3. Show another simple window. if (show_another_window) { - ImGui::Begin("Another Window", &show_another_window); // Pass a pointer to our bool variable (the window will have a closing button that will clear the bool when clicked) + ImGui::Begin("Another Window", &show_another_window); // Pass a pointer to our bool variable (the window will have a closing button that will clear the bool when clicked) ImGui::Text("Hello from another window!"); - if (ImGui::Button("Close Me")) show_another_window = false; + if (ImGui::Button("Close Me")) + show_another_window = false; ImGui::End(); } From d4747a8e3d708f795495cf80bb62ee908a8d6eab Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Thu, 3 Sep 2026 14:44:28 +0100 Subject: [PATCH 60/72] Examples: Emscripten+WebGPU: Match other example control flow formatting --- examples/example_emscripten_wgpu/main.cpp | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/examples/example_emscripten_wgpu/main.cpp b/examples/example_emscripten_wgpu/main.cpp index 41d47b85d..0d77a9ae6 100644 --- a/examples/example_emscripten_wgpu/main.cpp +++ b/examples/example_emscripten_wgpu/main.cpp @@ -48,12 +48,14 @@ static void GetFramebufferSizeFromDisplaySize(int width, int height, int* frameb static void ResizeSurface(int width, int height) { - if (width <= 0 || height <= 0) return; + if (width <= 0 || height <= 0) + return; int framebuffer_width = 0; int framebuffer_height = 0; GetFramebufferSizeFromDisplaySize(width, height, &framebuffer_width, &framebuffer_height); - if (framebuffer_width <= 0 || framebuffer_height <= 0) return; + if (framebuffer_width <= 0 || framebuffer_height <= 0) + return; emscripten_set_canvas_element_size("#canvas", framebuffer_width, framebuffer_height); wgpu_surface_configuration.width = wgpu_surface_width = framebuffer_width; @@ -64,7 +66,8 @@ static void ResizeSurface(int width, int height) // Main code int main(int, char**) { - if (!InitWGPU()) return 1; + if (!InitWGPU()) + return 1; // Setup Dear ImGui context IMGUI_CHECKVERSION(); @@ -79,7 +82,8 @@ int main(int, char**) // Setup Platform/Renderer backends ImGui_ImplEmscripten_Init(); - if (io.DisplaySize.x > 0.0f && io.DisplaySize.y > 0.0f) ResizeSurface((int)io.DisplaySize.x, (int)io.DisplaySize.y); + if (io.DisplaySize.x > 0.0f && io.DisplaySize.y > 0.0f) + ResizeSurface((int)io.DisplaySize.x, (int)io.DisplaySize.y); ImGui_ImplWGPU_InitInfo init_info; init_info.Device = wgpu_device; @@ -131,7 +135,8 @@ int main(int, char**) int framebuffer_width = 0; int framebuffer_height = 0; GetFramebufferSizeFromDisplaySize(width, height, &framebuffer_width, &framebuffer_height); - if (framebuffer_width != wgpu_surface_width || framebuffer_height != wgpu_surface_height) ResizeSurface(width, height); + if (framebuffer_width != wgpu_surface_width || framebuffer_height != wgpu_surface_height) + ResizeSurface(width, height); // Check surface status for error. If texture is not optimal, try to reconfigure the surface. WGPUSurfaceTexture surface_texture; @@ -143,7 +148,8 @@ int main(int, char**) } if (ImGui_ImplWGPU_IsSurfaceStatusSubOptimal(surface_texture.status)) { - if (surface_texture.texture) wgpuTextureRelease(surface_texture.texture); + if (surface_texture.texture) + wgpuTextureRelease(surface_texture.texture); ResizeSurface(width, height); continue; } From db34cdad81b0570d2d8cc1ff06e9d79cd80c3cf6 Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Thu, 3 Sep 2026 14:45:14 +0100 Subject: [PATCH 61/72] Examples: Emscripten+WebGPU: Align configuration comments with other examples --- examples/example_emscripten_wgpu/main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/example_emscripten_wgpu/main.cpp b/examples/example_emscripten_wgpu/main.cpp index 0d77a9ae6..568ee087c 100644 --- a/examples/example_emscripten_wgpu/main.cpp +++ b/examples/example_emscripten_wgpu/main.cpp @@ -73,8 +73,8 @@ int main(int, char**) IMGUI_CHECKVERSION(); ImGui::CreateContext(); ImGuiIO& io = ImGui::GetIO(); (void)io; - io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls - io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls + io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls // Setup Dear ImGui style ImGui::StyleColorsDark(); From dae858d80f504d671342488a270a23b5e3218892 Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Thu, 3 Sep 2026 15:02:50 +0100 Subject: [PATCH 62/72] Examples: Add emscripten opengl3 example --- .github/workflows/build.yml | 7 + .gitignore | 1 + docs/EXAMPLES.md | 4 + .../Makefile.emscripten | 94 ++++++++ examples/example_emscripten_opengl3/README.md | 23 ++ examples/example_emscripten_opengl3/main.cpp | 210 ++++++++++++++++++ 6 files changed, 339 insertions(+) create mode 100644 examples/example_emscripten_opengl3/Makefile.emscripten create mode 100644 examples/example_emscripten_opengl3/README.md create mode 100644 examples/example_emscripten_opengl3/main.cpp diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9bc81bf81..ff844e66f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -626,6 +626,13 @@ jobs: popd make -C examples/example_sdl2_opengl3 -f Makefile.emscripten + - name: Build example_emscripten_opengl3 + run: | + pushd emsdk-master + source ./emsdk_env.sh + popd + make -C examples/example_emscripten_opengl3 -f Makefile.emscripten + # This build compiles example_glfw_wgpu using Makefile.emscripten and Emscripten GLFW built-in implementation (-sUSE_GLFW=3) # This ensures 2 things: the make build works, and the GLFW built-in implementation is tested - name: Build example_glfw_wgpu with Emscripten/Makefile diff --git a/.gitignore b/.gitignore index e853afbd9..5fdc7e71c 100644 --- a/.gitignore +++ b/.gitignore @@ -43,6 +43,7 @@ examples/*/*.dSYM examples/*.o.tmp examples/*.out.js examples/*.out.wasm +examples/example_emscripten_opengl3/web/* examples/example_emscripten_wgpu/web/* examples/example_glfw_opengl3/web/* examples/example_glfw_wgpu/web/* diff --git a/docs/EXAMPLES.md b/docs/EXAMPLES.md index 7cf73d214..221ea4ff1 100644 --- a/docs/EXAMPLES.md +++ b/docs/EXAMPLES.md @@ -71,6 +71,10 @@ OSX + OpenGL2 example.
(NB: imgui_impl_osx.mm is currently not as feature complete as other platforms backends. You may prefer to use the GLFW Or SDL backends, which will also support Windows and Linux.) +[example_emscripten_opengl3/](https://github.com/ocornut/imgui/blob/master/examples/example_emscripten_opengl3/)
+Emscripten (browser) + OpenGL3/ES2/ES3 example. Uses imgui_impl_emscripten for platform input and imgui_impl_opengl3 for rendering.
+= main.cpp + imgui_impl_emscripten.cpp + imgui_impl_opengl3.cpp + [example_emscripten_wgpu/](https://github.com/ocornut/imgui/blob/master/examples/example_emscripten_wgpu/)
Emscripten (browser) + WebGPU example. Uses imgui_impl_emscripten for platform input and imgui_impl_wgpu for rendering.
= main.cpp + imgui_impl_emscripten.cpp + imgui_impl_wgpu.cpp diff --git a/examples/example_emscripten_opengl3/Makefile.emscripten b/examples/example_emscripten_opengl3/Makefile.emscripten new file mode 100644 index 000000000..65526d320 --- /dev/null +++ b/examples/example_emscripten_opengl3/Makefile.emscripten @@ -0,0 +1,94 @@ +# +# Makefile to use with emscripten +# See https://emscripten.org/docs/getting_started/downloads.html +# for installation instructions. +# +# This Makefile assumes you have loaded emscripten's environment. +# (On Windows, you may need to execute emsdk_env.bat or encmdprompt.bat ahead) +# +# Running `make -f Makefile.emscripten` will produce three files: +# - web/index.html +# - web/index.js +# - web/index.wasm +# +# All three are needed to run the demo. + +CC = emcc +CXX = em++ +WEB_DIR = web +EXE = $(WEB_DIR)/index.html +IMGUI_DIR = ../.. +SOURCES = main.cpp +SOURCES += $(IMGUI_DIR)/backends/imgui_impl_emscripten.cpp $(IMGUI_DIR)/backends/imgui_impl_opengl3.cpp +SOURCES += $(IMGUI_DIR)/imgui.cpp $(IMGUI_DIR)/imgui_demo.cpp $(IMGUI_DIR)/imgui_draw.cpp $(IMGUI_DIR)/imgui_tables.cpp $(IMGUI_DIR)/imgui_widgets.cpp +OBJS = $(addsuffix .o, $(basename $(notdir $(SOURCES)))) +UNAME_S := $(shell uname -s) +CPPFLAGS = +LDFLAGS = +EMS = + +##--------------------------------------------------------------------- +## EMSCRIPTEN OPTIONS +##--------------------------------------------------------------------- + +# ("EMS" options gets added to both CPPFLAGS and LDFLAGS, whereas some options are for linker only) +EMS += -s DISABLE_EXCEPTION_CATCHING=1 +LDFLAGS += -s WASM=1 -s ALLOW_MEMORY_GROWTH=1 -s NO_EXIT_RUNTIME=0 -s ASSERTIONS=1 + +# Use WebGL 1 by default. Set USE_WEBGL2=1 to use OpenGL ES 3 and WebGL 2 instead. +USE_WEBGL2 ?= 0 +ifeq ($(USE_WEBGL2), 1) +CPPFLAGS += -DIMGUI_IMPL_OPENGL_ES3 +LDFLAGS += -s MIN_WEBGL_VERSION=2 -s MAX_WEBGL_VERSION=2 +endif + +# Build as single file (binary text encoded in .html file) +#LDFLAGS += -sSINGLE_FILE + +# Emscripten allows preloading a file or folder to be accessible at runtime. +# The Makefile for this example project suggests embedding the misc/fonts/ folder into our application, it will then be accessible as "/fonts" +# See documentation for more details: https://emscripten.org/docs/porting/files/packaging_files.html +# (Default value is 0. Set to 1 to enable file-system and include the misc/fonts/ folder as part of the build.) +USE_FILE_SYSTEM ?= 0 +ifeq ($(USE_FILE_SYSTEM), 0) +LDFLAGS += -s NO_FILESYSTEM=1 +CPPFLAGS += -DIMGUI_DISABLE_FILE_FUNCTIONS +endif +ifeq ($(USE_FILE_SYSTEM), 1) +LDFLAGS += --no-heap-copy --preload-file ../../misc/fonts@/fonts +endif + +##--------------------------------------------------------------------- +## FINAL BUILD FLAGS +##--------------------------------------------------------------------- + +CPPFLAGS += -I$(IMGUI_DIR) -I$(IMGUI_DIR)/backends +#CPPFLAGS += -g +CPPFLAGS += -Wall -Wformat -Os $(EMS) +LDFLAGS += --shell-file ../libs/emscripten/shell_minimal.html +LDFLAGS += $(EMS) + +##--------------------------------------------------------------------- +## BUILD RULES +##--------------------------------------------------------------------- + +%.o:%.cpp + $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $< + +%.o:$(IMGUI_DIR)/%.cpp + $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $< + +%.o:$(IMGUI_DIR)/backends/%.cpp + $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $< + +all: $(EXE) + @echo Build complete for $(EXE) + +$(WEB_DIR): + mkdir $@ + +$(EXE): $(OBJS) $(WEB_DIR) + $(CXX) -o $@ $(OBJS) $(LDFLAGS) + +clean: + rm -f $(EXE) $(OBJS) $(WEB_DIR)/*.js $(WEB_DIR)/*.wasm $(WEB_DIR)/*.wasm.pre diff --git a/examples/example_emscripten_opengl3/README.md b/examples/example_emscripten_opengl3/README.md new file mode 100644 index 000000000..201974919 --- /dev/null +++ b/examples/example_emscripten_opengl3/README.md @@ -0,0 +1,23 @@ +## How to Build + +- You need to install Emscripten from https://emscripten.org/docs/getting_started/downloads.html, and have the environment variables set, as described in https://emscripten.org/docs/getting_started/downloads.html#installation-instructions + +- Depending on your configuration, in Windows you may need to run `emsdk/emsdk_env.bat` in your console to access the Emscripten command-line tools. + +- You may also refer to our [Continuous Integration setup](https://github.com/ocornut/imgui/tree/master/.github/workflows) for Emscripten setup. + +- Then build using `make -f Makefile.emscripten` while in the `example_emscripten_opengl3/` directory. + +- This example is browser-only. It uses `imgui_impl_emscripten` for platform input and `imgui_impl_opengl3` for rendering. + +- The default build targets OpenGL ES 2 and WebGL 1. Build with `make -f Makefile.emscripten USE_WEBGL2=1` to target OpenGL ES 3 and WebGL 2 instead. + +## How to Run + +To run on a local machine: +- `emrun web/index.html` will spawn a temporary local webserver and open the example in your browser. See https://emscripten.org/docs/compiling/Running-html-files-with-emrun.html for details. +- Otherwise, generally you will need a local webserver: + - Quoting [https://emscripten.org/docs/getting_started](https://emscripten.org/docs/getting_started/Tutorial.html#generating-html):
+_"Unfortunately several browsers (including Chrome, Safari, and Internet Explorer) do not support file:// [XHR](https://emscripten.org/docs/site/glossary.html#term-xhr) requests, and can't load extra files needed by the HTML (like a .wasm file, or packaged file data as mentioned lower down). For these browsers you'll need to serve the files using a [local webserver](https://emscripten.org/docs/getting_started/FAQ.html#faq-local-webserver) and then open http://localhost:8000/hello.html."_ + - You may use Python 3 builtin webserver: `python -m http.server -d web`. + - You may use Python 2 builtin webserver: `cd web && python -m SimpleHTTPServer`. diff --git a/examples/example_emscripten_opengl3/main.cpp b/examples/example_emscripten_opengl3/main.cpp new file mode 100644 index 000000000..a92e4e962 --- /dev/null +++ b/examples/example_emscripten_opengl3/main.cpp @@ -0,0 +1,210 @@ +// Dear ImGui: standalone example application for using Emscripten + OpenGL 3 +// - This uses imgui_impl_emscripten for platform inputs and imgui_impl_opengl3 for rendering. +// - Emscripten is required to build this example. See https://emscripten.org. +// - WebGL 1 is used by default. Define IMGUI_IMPL_OPENGL_ES3 and enable WebGL 2 to use WebGL 2 instead. + +// 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 + +#include "imgui.h" +#include "imgui_impl_emscripten.h" +#include "imgui_impl_opengl3.h" +#include +#if defined(IMGUI_IMPL_OPENGL_ES2) +#include +#elif defined(IMGUI_IMPL_OPENGL_ES3) +#include +#endif +#include +#include +#include "../libs/emscripten/emscripten_mainloop_stub.h" + +// Data +static int gl_framebuffer_width = 0; +static int gl_framebuffer_height = 0; + +// Forward declarations +static void GetFramebufferSizeFromDisplaySize(int width, int height, int* framebuffer_width, int* framebuffer_height); +static void ResizeCanvas(int width, int height); + +static void GetFramebufferSizeFromDisplaySize(int width, int height, int* framebuffer_width, int* framebuffer_height) +{ + ImGuiIO& io = ImGui::GetIO(); + *framebuffer_width = (int)(width * io.DisplayFramebufferScale.x + 0.5f); + *framebuffer_height = (int)(height * io.DisplayFramebufferScale.y + 0.5f); +} + +static void ResizeCanvas(int width, int height) +{ + if (width <= 0 || height <= 0) + return; + + int framebuffer_width = 0; + int framebuffer_height = 0; + GetFramebufferSizeFromDisplaySize(width, height, &framebuffer_width, &framebuffer_height); + if (framebuffer_width <= 0 || framebuffer_height <= 0) + return; + + emscripten_set_canvas_element_size("#canvas", framebuffer_width, framebuffer_height); + gl_framebuffer_width = framebuffer_width; + gl_framebuffer_height = framebuffer_height; +} + +// Main code +int main(int, char**) +{ + // Decide GL+GLSL versions + EmscriptenWebGLContextAttributes context_attributes; + emscripten_webgl_init_context_attributes(&context_attributes); +#if defined(IMGUI_IMPL_OPENGL_ES2) + // GL ES 2.0 + GLSL 100 (WebGL 1.0) + const char* glsl_version = "#version 100"; + context_attributes.majorVersion = 1; + context_attributes.minorVersion = 0; +#elif defined(IMGUI_IMPL_OPENGL_ES3) + // GL ES 3.0 + GLSL 300 es (WebGL 2.0) + const char* glsl_version = "#version 300 es"; + context_attributes.majorVersion = 2; + context_attributes.minorVersion = 0; +#endif + + // Create window with graphics context + EMSCRIPTEN_WEBGL_CONTEXT_HANDLE gl_context = emscripten_webgl_create_context("#canvas", &context_attributes); + if (gl_context == 0) + { + fprintf(stderr, "Failed to create WebGL context.\n"); + return 1; + } + if (emscripten_webgl_make_context_current(gl_context) != EMSCRIPTEN_RESULT_SUCCESS) + { + fprintf(stderr, "Failed to make WebGL context current.\n"); + emscripten_webgl_destroy_context(gl_context); + return 1; + } + + // Setup Dear ImGui context + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); (void)io; + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls + io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls + + // Setup Dear ImGui style + ImGui::StyleColorsDark(); + //ImGui::StyleColorsLight(); + + // Setup Platform/Renderer backends + ImGui_ImplEmscripten_Init(); + if (io.DisplaySize.x > 0.0f && io.DisplaySize.y > 0.0f) + ResizeCanvas((int)io.DisplaySize.x, (int)io.DisplaySize.y); + ImGui_ImplOpenGL3_Init(glsl_version); + + // Load Fonts + // - If fonts are not explicitly loaded, Dear ImGui will select an embedded font: either AddFontDefaultVector() or AddFontDefaultBitmap(). + // This selection is based on (style.FontSizeBase * style.FontScaleMain * style.FontScaleDpi) reaching a small threshold. + // - You can load multiple fonts and use ImGui::PushFont()/PopFont() to select them. + // - If a file cannot be loaded, AddFont functions will return a nullptr. Please handle those errors in your code (e.g. use an assertion, display an error and quit). + // - Read 'docs/FONTS.md' for more instructions and details. + // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use FreeType for higher quality font rendering. + // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! + // - Our Emscripten build process allows embedding fonts to be accessible at runtime from the "fonts/" folder. See Makefile.emscripten for details. + //style.FontSizeBase = 20.0f; + //io.Fonts->AddFontDefaultVector(); + //io.Fonts->AddFontDefaultBitmap(); +#ifndef IMGUI_DISABLE_FILE_FUNCTIONS + //io.Fonts->AddFontFromFileTTF("fonts/segoeui.ttf"); + //io.Fonts->AddFontFromFileTTF("fonts/DroidSans.ttf"); + //io.Fonts->AddFontFromFileTTF("fonts/Roboto-Medium.ttf"); + //io.Fonts->AddFontFromFileTTF("fonts/Cousine-Regular.ttf"); + //ImFont* font = io.Fonts->AddFontFromFileTTF("fonts/ArialUni.ttf"); + //IM_ASSERT(font != nullptr); +#endif + + // Our state + bool show_demo_window = true; + bool show_another_window = false; + ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); + + // Main loop + // For an Emscripten build we are disabling file-system access, so let's not attempt to do a fopen() of the imgui.ini file. + // You may manually call LoadIniSettingsFromMemory() to load settings from your own storage. + io.IniFilename = nullptr; + EMSCRIPTEN_MAINLOOP_BEGIN + { + // Input handling is callback-driven via imgui_impl_emscripten, so there is no event pump here. + + // React to changes in browser window size. + int width = (int)io.DisplaySize.x; + int height = (int)io.DisplaySize.y; + if (width <= 0 || height <= 0) + continue; + + int framebuffer_width = 0; + int framebuffer_height = 0; + GetFramebufferSizeFromDisplaySize(width, height, &framebuffer_width, &framebuffer_height); + if (framebuffer_width != gl_framebuffer_width || framebuffer_height != gl_framebuffer_height) + ResizeCanvas(width, height); + + // Start the Dear ImGui frame + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplEmscripten_NewFrame(); + ImGui::NewFrame(); + + // 1. Show the big demo window (Most of the sample code is in ImGui::ShowDemoWindow()! You can browse its code to learn more about Dear ImGui!). + if (show_demo_window) + ImGui::ShowDemoWindow(&show_demo_window); + + // 2. Show a simple window that we create ourselves. We use a Begin/End pair to create a named window. + { + static float f = 0.0f; + static int counter = 0; + + ImGui::Begin("Hello, world!"); // Create a window called "Hello, world!" and append into it. + + ImGui::Text("This is some useful text."); // Display some text (you can use a format strings too) + ImGui::Checkbox("Demo Window", &show_demo_window); // Edit bools storing our window open/close state + ImGui::Checkbox("Another Window", &show_another_window); + + ImGui::SliderFloat("float", &f, 0.0f, 1.0f); // Edit 1 float using a slider from 0.0f to 1.0f + ImGui::ColorEdit3("clear color", (float*)&clear_color); // Edit 3 floats representing a color + + if (ImGui::Button("Button")) // Buttons return true when clicked (most widgets return true when edited/activated) + counter++; + ImGui::SameLine(); + ImGui::Text("counter = %d", counter); + + ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); + ImGui::End(); + } + + // 3. Show another simple window. + if (show_another_window) + { + ImGui::Begin("Another Window", &show_another_window); // Pass a pointer to our bool variable (the window will have a closing button that will clear the bool when clicked) + ImGui::Text("Hello from another window!"); + if (ImGui::Button("Close Me")) + show_another_window = false; + ImGui::End(); + } + + // Rendering + ImGui::Render(); + glViewport(0, 0, framebuffer_width, framebuffer_height); + glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w); + glClear(GL_COLOR_BUFFER_BIT); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + } + EMSCRIPTEN_MAINLOOP_END; + + // Cleanup + ImGui_ImplOpenGL3_Shutdown(); + ImGui_ImplEmscripten_Shutdown(); + ImGui::DestroyContext(); + + emscripten_webgl_destroy_context(gl_context); + + return 0; +} From cf7b6f9402a1d40bdc4d4a06403f8558c6a31d47 Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Thu, 3 Sep 2026 15:03:23 +0100 Subject: [PATCH 63/72] CI: Add emscripten wgpu example to CI build --- .github/workflows/build.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ff844e66f..49212a4de 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -633,6 +633,13 @@ jobs: popd make -C examples/example_emscripten_opengl3 -f Makefile.emscripten + - name: Build example_emscripten_wgpu + run: | + pushd emsdk-master + source ./emsdk_env.sh + popd + make -C examples/example_emscripten_wgpu -f Makefile.emscripten + # This build compiles example_glfw_wgpu using Makefile.emscripten and Emscripten GLFW built-in implementation (-sUSE_GLFW=3) # This ensures 2 things: the make build works, and the GLFW built-in implementation is tested - name: Build example_glfw_wgpu with Emscripten/Makefile From df7092172fe9db658dbb65845a2976da874cd216 Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Fri, 4 Sep 2026 12:29:01 +0100 Subject: [PATCH 64/72] adjust brace style for consistency --- backends/imgui_impl_emscripten.cpp | 45 ++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index 7b807022b..25c4e5f50 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -151,7 +151,8 @@ void ImGui_ImplEmscripten_Init() EMSCRIPTEN_EVENT_TARGET_WINDOW, // target nullptr, // userData false, // useCapture - [](int /*event_type*/, EmscriptenMouseEvent const* mouse_event, void* /*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_MOUSEMOVE + [](int /*event_type*/, EmscriptenMouseEvent const* mouse_event, void* /*data*/) // callback, event_type == EMSCRIPTEN_EVENT_MOUSEMOVE + { ImGui_ImplEmscripten_Data* bd = ImGui_ImplEmscripten_GetBackendData(); float const css_to_imgui_scale = bd ? bd->CssToImGuiScale : 1.0f; ImGui::GetIO().AddMousePosEvent( @@ -165,7 +166,8 @@ void ImGui_ImplEmscripten_Init() EMSCRIPTEN_EVENT_TARGET_WINDOW, // target nullptr, // userData false, // useCapture - [](int /*event_type*/, EmscriptenMouseEvent const* mouse_event, void* /*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_MOUSEDOWN + [](int /*event_type*/, EmscriptenMouseEvent const* mouse_event, void* /*data*/) // callback, event_type == EMSCRIPTEN_EVENT_MOUSEDOWN + { ImGui::GetIO().AddMouseButtonEvent(ImGui_ImplEmscripten_TranslateMouseButton(mouse_event->button), true); // translated button, down return true; // the event was consumed } @@ -174,7 +176,8 @@ void ImGui_ImplEmscripten_Init() EMSCRIPTEN_EVENT_TARGET_WINDOW, // target nullptr, // userData false, // useCapture - [](int /*event_type*/, EmscriptenMouseEvent const* mouse_event, void* /*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_MOUSEUP + [](int /*event_type*/, EmscriptenMouseEvent const* mouse_event, void* /*data*/) // callback, event_type == EMSCRIPTEN_EVENT_MOUSEUP + { ImGui::GetIO().AddMouseButtonEvent(ImGui_ImplEmscripten_TranslateMouseButton(mouse_event->button), false); // translated button, up return true; // the event was consumed } @@ -183,7 +186,8 @@ void ImGui_ImplEmscripten_Init() EMSCRIPTEN_EVENT_TARGET_DOCUMENT, // target - WINDOW doesn't produce mouseenter events nullptr, // userData false, // useCapture - [](int /*event_type*/, EmscriptenMouseEvent const* mouse_event, void* /*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_MOUSEENTER + [](int /*event_type*/, EmscriptenMouseEvent const* mouse_event, void* /*data*/) // callback, event_type == EMSCRIPTEN_EVENT_MOUSEENTER + { ImGui_ImplEmscripten_Data* bd = ImGui_ImplEmscripten_GetBackendData(); float const css_to_imgui_scale = bd ? bd->CssToImGuiScale : 1.0f; ImGui::GetIO().AddMousePosEvent( @@ -197,7 +201,8 @@ void ImGui_ImplEmscripten_Init() EMSCRIPTEN_EVENT_TARGET_DOCUMENT, // target - WINDOW doesn't produce mouseenter events nullptr, // userData false, // useCapture - [](int /*event_type*/, EmscriptenMouseEvent const* /*mouse_event*/, void* /*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_MOUSELEAVE + [](int /*event_type*/, EmscriptenMouseEvent const* /*mouse_event*/, void* /*data*/) // callback, event_type == EMSCRIPTEN_EVENT_MOUSELEAVE + { ImGuiIO& io = ImGui::GetIO(); io.AddMousePosEvent(-FLT_MAX, -FLT_MAX); // cursor is not in the window io.ClearInputKeys(); // clear pending input keys on mouse exit @@ -208,7 +213,8 @@ void ImGui_ImplEmscripten_Init() EMSCRIPTEN_EVENT_TARGET_WINDOW, // target nullptr, // userData false, // useCapture - [](int /*event_type*/, EmscriptenWheelEvent const* wheel_event, void* /*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_WHEEL + [](int /*event_type*/, EmscriptenWheelEvent const* wheel_event, void* /*data*/) // callback, event_type == EMSCRIPTEN_EVENT_WHEEL + { float scale = 1.0f; switch (wheel_event->deltaMode) { @@ -235,7 +241,8 @@ void ImGui_ImplEmscripten_Init() EMSCRIPTEN_EVENT_TARGET_WINDOW, // target nullptr, // userData false, // useCapture - [](int /*event_type*/, EmscriptenKeyboardEvent const* key_event, void* /*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_KEYDOWN + [](int /*event_type*/, EmscriptenKeyboardEvent const* key_event, void* /*data*/) // callback, event_type == EMSCRIPTEN_EVENT_KEYDOWN + { const ImGuiKey key = ImGui_ImplEmscripten_TranslateKey(key_event->code); ImGuiIO& io = ImGui::GetIO(); io.AddKeyEvent(key, true); @@ -273,7 +280,8 @@ void ImGui_ImplEmscripten_Init() EMSCRIPTEN_EVENT_TARGET_WINDOW, // target nullptr, // userData false, // useCapture - [](int /*event_type*/, EmscriptenKeyboardEvent const* key_event, void* /*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_KEYUP + [](int /*event_type*/, EmscriptenKeyboardEvent const* key_event, void* /*data*/) // callback, event_type == EMSCRIPTEN_EVENT_KEYUP + { const ImGuiKey key = ImGui_ImplEmscripten_TranslateKey(key_event->code); ImGuiIO& io = ImGui::GetIO(); io.AddKeyEvent(key, false); @@ -305,7 +313,8 @@ void ImGui_ImplEmscripten_Init() EMSCRIPTEN_EVENT_TARGET_WINDOW, // target nullptr, // userData false, // useCapture - [](int /*event_type*/, EmscriptenKeyboardEvent const* key_event, void* /*data*/) { // callback, event_type == EMSCRIPTEN_EVENT_KEYPRESS + [](int /*event_type*/, EmscriptenKeyboardEvent const* key_event, void* /*data*/) // callback, event_type == EMSCRIPTEN_EVENT_KEYPRESS + { ImGuiIO& io = ImGui::GetIO(); io.AddInputCharactersUTF8(key_event->key); return io.WantCaptureKeyboard; // the event was consumed only if imgui wants to capture the keyboard @@ -315,7 +324,8 @@ void ImGui_ImplEmscripten_Init() EMSCRIPTEN_EVENT_TARGET_WINDOW, // target nullptr, // userData false, // useCapture - [](int /*event_type*/, EmscriptenUiEvent const* event, void* /*data*/) { // event_type == EMSCRIPTEN_EVENT_RESIZE + [](int /*event_type*/, EmscriptenUiEvent const* event, void* /*data*/) // event_type == EMSCRIPTEN_EVENT_RESIZE + { ImGuiIO& io = ImGui::GetIO(); ImGui_ImplEmscripten_Data* bd = ImGui_ImplEmscripten_GetBackendData(); if (bd != nullptr) ImGui_ImplEmscripten_UpdateDisplayProperties(io, bd, (float)event->windowInnerWidth, (float)event->windowInnerHeight); @@ -326,7 +336,8 @@ void ImGui_ImplEmscripten_Init() EMSCRIPTEN_EVENT_TARGET_WINDOW, // target nullptr, // userData false, // useCapture - [](int /*event_type*/, EmscriptenFocusEvent const* /*event*/, void* /*data*/) { // event_type == EMSCRIPTEN_EVENT_BLUR + [](int /*event_type*/, EmscriptenFocusEvent const* /*event*/, void* /*data*/) // event_type == EMSCRIPTEN_EVENT_BLUR + { ImGuiIO& io = ImGui::GetIO(); io.AddFocusEvent(false); io.ClearInputKeys(); // clear pending input keys on focus loss @@ -337,7 +348,8 @@ void ImGui_ImplEmscripten_Init() EMSCRIPTEN_EVENT_TARGET_WINDOW, // target nullptr, // userData false, // useCapture - [](int /*event_type*/, EmscriptenFocusEvent const* /*event*/, void* /*data*/) { // event_type == EMSCRIPTEN_EVENT_FOCUS + [](int /*event_type*/, EmscriptenFocusEvent const* /*event*/, void* /*data*/) // event_type == EMSCRIPTEN_EVENT_FOCUS + { ImGuiIO& io = ImGui::GetIO(); io.AddFocusEvent(true); io.ClearInputKeys(); // clear pending input keys on focus gain - for example if you press tab to cycle back into the browser window @@ -348,7 +360,8 @@ void ImGui_ImplEmscripten_Init() EMSCRIPTEN_EVENT_TARGET_WINDOW, // target nullptr, // userData false, // useCapture - [](int /*event_type*/, EmscriptenFocusEvent const* /*event*/, void* /*data*/) { // event_type == EMSCRIPTEN_EVENT_FOCUSIN + [](int /*event_type*/, EmscriptenFocusEvent const* /*event*/, void* /*data*/) // event_type == EMSCRIPTEN_EVENT_FOCUSIN + { ImGuiIO& io = ImGui::GetIO(); io.AddFocusEvent(true); io.ClearInputKeys(); // clear pending input keys on focus gain @@ -359,7 +372,8 @@ void ImGui_ImplEmscripten_Init() EMSCRIPTEN_EVENT_TARGET_WINDOW, // target nullptr, // userData false, // useCapture - [](int /*event_type*/, EmscriptenFocusEvent const* /*event*/, void* /*data*/) { // event_type == EMSCRIPTEN_EVENT_FOCUSOUT + [](int /*event_type*/, EmscriptenFocusEvent const* /*event*/, void* /*data*/) // event_type == EMSCRIPTEN_EVENT_FOCUSOUT + { ImGuiIO& io = ImGui::GetIO(); io.AddFocusEvent(false); io.ClearInputKeys(); // clear pending input keys on focus loss - for example if you press tab to cycle to another part of the UI @@ -790,7 +804,8 @@ static ImGuiStorage const& ImGui_ImplEmscripten_GetKeyTranslationStorage() if (is_initialized) return storage; is_initialized = true; storage.Data.reserve(IM_ARRAYSIZE(key_translations)); - for (int n = 0; n != IM_ARRAYSIZE(key_translations); ++n) { + for (int n = 0; n != IM_ARRAYSIZE(key_translations); ++n) + { storage.Data.push_back(ImGuiStoragePair(ImHashStr(key_translations[n].EmscriptenKey), key_translations[n].Key)); } storage.BuildSortByKey(); From ca7b9ca3d5cf98d9b30a5bf49ca20ae0c0f4c1e8 Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Fri, 4 Sep 2026 12:42:22 +0100 Subject: [PATCH 65/72] Backends: Emscripten: Align enum and comment style --- backends/imgui_impl_emscripten.cpp | 165 ++++++++++++++--------------- 1 file changed, 80 insertions(+), 85 deletions(-) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index 25c4e5f50..a23864768 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -32,75 +32,70 @@ float ImGui_ImplEmscripten_TargetDevicePixelRatio = 1.0f; static ImGuiKey ImGui_ImplEmscripten_TranslateKey(char const* emscripten_key); static constexpr ImGuiMouseButton ImGui_ImplEmscripten_TranslateMouseButton(unsigned short emscripten_button) __attribute__((__const__)); -namespace emscripten_browser_cursor_internal -{ - // Browser cursor helpers, adapted from https://github.com/Armchair-Software/emscripten-browser-cursor -enum class cursor +enum ImGui_ImplEmscripten_Cursor { // General - cursor_auto, // The UA will determine the cursor to display based on the current context. E.g., equivalent to text when hovering text. - cursor_default, // The platform-dependent default cursor. Typically an arrow. - none, // No cursor is rendered. + ImGui_ImplEmscripten_Cursor_Auto, // The UA will determine the cursor to display based on the current context. E.g., equivalent to text when hovering text. + ImGui_ImplEmscripten_Cursor_Default, // The platform-dependent default cursor. Typically an arrow. + ImGui_ImplEmscripten_Cursor_None, // No cursor is rendered. // Links & status - context_menu, // cursor slightly obscuring a menu icon - A context menu is available. - help, // cursor next to a question mark - Help information is available. - pointer, // right hand with an index finger pointing up - The cursor is a pointer that indicates a link. Typically an image of a pointing hand. - progress, // cursor and hour glass - The program is busy in the background, but the user can still interact with the interface (in contrast to wait). - wait, // hour glass - The program is busy, and the user can't interact with the interface (in contrast to progress). Sometimes an image of an hourglass or a watch. + ImGui_ImplEmscripten_Cursor_ContextMenu, // cursor slightly obscuring a menu icon - A context menu is available. + ImGui_ImplEmscripten_Cursor_Help, // cursor next to a question mark - Help information is available. + ImGui_ImplEmscripten_Cursor_Pointer, // right hand with an index finger pointing up - The cursor is a pointer that indicates a link. Typically an image of a pointing hand. + ImGui_ImplEmscripten_Cursor_Progress, // cursor and hour glass - The program is busy in the background, but the user can still interact with the interface (in contrast to wait). + ImGui_ImplEmscripten_Cursor_Wait, // hour glass - The program is busy, and the user can't interact with the interface (in contrast to progress). Sometimes an image of an hourglass or a watch. // Selection - cell, // plus symbol - The table cell or set of cells can be selected. - crosshair, // crosshair - Cross cursor, often used to indicate selection in a bitmap. - text, // vertical i-beam - The text can be selected. Typically the shape of an I-beam. - vertical_text, // horizontal i-beam - The vertical text can be selected. Typically the shape of a sideways I-beam. + ImGui_ImplEmscripten_Cursor_Cell, // plus symbol - The table cell or set of cells can be selected. + ImGui_ImplEmscripten_Cursor_Crosshair, // crosshair - Cross cursor, often used to indicate selection in a bitmap. + ImGui_ImplEmscripten_Cursor_Text, // vertical i-beam - The text can be selected. Typically the shape of an I-beam. + ImGui_ImplEmscripten_Cursor_VerticalText, // horizontal i-beam - The vertical text can be selected. Typically the shape of a sideways I-beam. // Drag & drop - alias, // cursor next to a folder icon with a curved arrow pointing up and to the right - An alias or shortcut is to be created. - copy, // cursor next to a smaller folder icon with a plus sign - Something is to be copied. - move, // plus sign made of two thin lines, with small arrows facing out - Something is to be moved. - no_drop, // cursor next to circle with a line through it - An item may not be dropped at the current location. - not_allowed, // circle with a line through it - The requested action will not be carried out. - grab, // fully opened hand - Something can be grabbed (dragged to be moved). - grabbing, // closed hand - Something is being grabbed (dragged to be moved). + ImGui_ImplEmscripten_Cursor_Alias, // cursor next to a folder icon with a curved arrow pointing up and to the right - An alias or shortcut is to be created. + ImGui_ImplEmscripten_Cursor_Copy, // cursor next to a smaller folder icon with a plus sign - Something is to be copied. + ImGui_ImplEmscripten_Cursor_Move, // plus sign made of two thin lines, with small arrows facing out - Something is to be moved. + ImGui_ImplEmscripten_Cursor_NoDrop, // cursor next to circle with a line through it - An item may not be dropped at the current location. + ImGui_ImplEmscripten_Cursor_NotAllowed, // circle with a line through it - The requested action will not be carried out. + ImGui_ImplEmscripten_Cursor_Grab, // fully opened hand - Something can be grabbed (dragged to be moved). + ImGui_ImplEmscripten_Cursor_Grabbing, // closed hand - Something is being grabbed (dragged to be moved). // Resizing & scrolling - all_scroll, // dot with four triangles around it - Something can be scrolled in any direction (panned). - col_resize, // The item/column can be resized horizontally. Often rendered as arrows pointing left and right with a vertical bar separating them. - row_resize, // The item/row can be resized vertically. Often rendered as arrows pointing up and down with a horizontal bar separating them. - n_resize, // arrow pointing up - Some edge is to be moved. For example, the se-resize cursor is used when the movement starts from the south-east corner of the box. - e_resize, // arrow pointing right - s_resize, // arrow pointing down - w_resize, // arrow pointing left - ne_resize, // arrow pointing top-right - nw_resize, // arrow pointing top-left - se_resize, // arrow pointing bottom-right - sw_resize, // arrow pointing bottom-left - ew_resize, // arrow pointing left and right - Bidirectional resize cursor. - ns_resize, // arrow pointing up and down - nesw_resize, // arrow pointing both to the top-right and bottom-left - nwse_resize, // arrow pointing both to the top-left and bottom-right + ImGui_ImplEmscripten_Cursor_AllScroll, // dot with four triangles around it - Something can be scrolled in any direction (panned). + ImGui_ImplEmscripten_Cursor_ColResize, // The item/column can be resized horizontally. Often rendered as arrows pointing left and right with a vertical bar separating them. + ImGui_ImplEmscripten_Cursor_RowResize, // The item/row can be resized vertically. Often rendered as arrows pointing up and down with a horizontal bar separating them. + ImGui_ImplEmscripten_Cursor_NResize, // arrow pointing up - Some edge is to be moved. For example, the se-resize cursor is used when the movement starts from the south-east corner of the box. + ImGui_ImplEmscripten_Cursor_EResize, // arrow pointing right + ImGui_ImplEmscripten_Cursor_SResize, // arrow pointing down + ImGui_ImplEmscripten_Cursor_WResize, // arrow pointing left + ImGui_ImplEmscripten_Cursor_NEResize, // arrow pointing top-right + ImGui_ImplEmscripten_Cursor_NWResize, // arrow pointing top-left + ImGui_ImplEmscripten_Cursor_SEResize, // arrow pointing bottom-right + ImGui_ImplEmscripten_Cursor_SWResize, // arrow pointing bottom-left + ImGui_ImplEmscripten_Cursor_EWResize, // arrow pointing left and right - Bidirectional resize cursor. + ImGui_ImplEmscripten_Cursor_NSResize, // arrow pointing up and down + ImGui_ImplEmscripten_Cursor_NESWResize, // arrow pointing both to the top-right and bottom-left + ImGui_ImplEmscripten_Cursor_NWSEResize, // arrow pointing both to the top-left and bottom-right // Zooming - zoom_in, // magnifying glass with a plus sign - Something can be zoomed (magnified) in or out. - zoom_out, + ImGui_ImplEmscripten_Cursor_ZoomIn, // magnifying glass with a plus sign - Something can be zoomed (magnified) in or out. + ImGui_ImplEmscripten_Cursor_ZoomOut, // Special invalid value - invalid + ImGui_ImplEmscripten_Cursor_Invalid }; -} // namespace emscripten_browser_cursor_internal - -static void ImGui_ImplEmscripten_SetBrowserCursor(emscripten_browser_cursor_internal::cursor new_cursor); // set a new cursor from a cursor enum +static void ImGui_ImplEmscripten_SetBrowserCursor(ImGui_ImplEmscripten_Cursor new_cursor); // set a new cursor from a cursor enum static char* ImGui_ImplEmscripten_GetBrowserCursor(); // read the current cursor setting as an owned string, caller must free() static void ImGui_ImplEmscripten_SetBrowserCursor(char const* new_cursor); // set the cursor from an arbitrary string struct ImGui_ImplEmscripten_Data { float CssToImGuiScale = 1.0f; - emscripten_browser_cursor_internal::cursor CurrentCursor = emscripten_browser_cursor_internal::cursor::invalid; + ImGui_ImplEmscripten_Cursor CurrentCursor = ImGui_ImplEmscripten_Cursor_Invalid; char* CursorToRestore = nullptr; bool LastMouseDrawCursor = false; bool LastNoMouseCursorChange = false; @@ -139,7 +134,7 @@ void ImGui_ImplEmscripten_Init() io.BackendPlatformName = "imgui_impl_emscripten"; io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; - // set up initial display size values + // Set up initial display size values ImGui_ImplEmscripten_UpdateDisplayProperties( io, bd, @@ -228,7 +223,7 @@ void ImGui_ImplEmscripten_Init() scale = 80.0f; break; } - // TODO: make scrolling speeds configurable + // TODO: Make scrolling speeds configurable ImGuiIO& io = ImGui::GetIO(); io.AddMouseWheelEvent( -(float)wheel_event->deltaX * scale, @@ -264,7 +259,7 @@ void ImGui_ImplEmscripten_Init() case ImGuiKey_RightSuper: io.AddKeyEvent(ImGuiMod_Super, true); break; - // TODO: case ImGuiKey_Menu: do we want to do anything with this? + // TODO: case ImGuiKey_Menu: Do we want to do anything with this? case ImGuiKey_Tab: // consuming tab prevents the user tabbing to other parts of the browser interface outside the window content return io.WantCaptureKeyboard; // the event was consumed only if imgui wants to capture the keyboard case ImGuiKey_Enter: // consuming enter prevents the word "Enter" appearing in text input via the keypress callback @@ -381,7 +376,7 @@ void ImGui_ImplEmscripten_Init() } ); - // TODO: touch events + // TODO: Touch events } void ImGui_ImplEmscripten_Shutdown() @@ -404,7 +399,7 @@ void ImGui_ImplEmscripten_Shutdown() emscripten_set_focus_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr); emscripten_set_focusin_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr); emscripten_set_focusout_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr); - // TODO: touch events + // TODO: Touch events if (bd->CursorToRestore != nullptr) { @@ -425,10 +420,10 @@ static void ImGui_ImplEmscripten_RestoreMouseCursor(ImGui_ImplEmscripten_Data* b ImGui_ImplEmscripten_SetBrowserCursor(bd->CursorToRestore); // restore the previous cursor state when leaving imgui cursor ownership free(bd->CursorToRestore); bd->CursorToRestore = nullptr; - bd->CurrentCursor = emscripten_browser_cursor_internal::cursor::invalid; // select an unused value for current cursor to force a set next time + bd->CurrentCursor = ImGui_ImplEmscripten_Cursor_Invalid; // select an unused value for current cursor to force a set next time } -static void ImGui_ImplEmscripten_SetMouseCursor(ImGui_ImplEmscripten_Data* bd, emscripten_browser_cursor_internal::cursor new_cursor) +static void ImGui_ImplEmscripten_SetMouseCursor(ImGui_ImplEmscripten_Data* bd, ImGui_ImplEmscripten_Cursor new_cursor) { if (new_cursor == bd->CurrentCursor) return; // don't do anything if the current cursor is already set if (bd->CursorToRestore == nullptr) bd->CursorToRestore = ImGui_ImplEmscripten_GetBrowserCursor(); // back up the existing cursor state when first taking cursor ownership @@ -446,7 +441,7 @@ static void ImGui_ImplEmscripten_UpdateMouseCursor(ImGui_ImplEmscripten_Data* bd { free(bd->CursorToRestore); bd->CursorToRestore = nullptr; - bd->CurrentCursor = emscripten_browser_cursor_internal::cursor::invalid; + bd->CurrentCursor = ImGui_ImplEmscripten_Cursor_Invalid; bd->LastMouseDrawCursor = false; bd->LastNoMouseCursorChange = true; } @@ -458,7 +453,7 @@ static void ImGui_ImplEmscripten_UpdateMouseCursor(ImGui_ImplEmscripten_Data* bd { if (bd->LastMouseDrawCursor) return; - ImGui_ImplEmscripten_SetMouseCursor(bd, emscripten_browser_cursor_internal::cursor::none); // hide the cursor for the entire window if imgui is handling cursor drawing - not just when imgui wants to capture the mouse + ImGui_ImplEmscripten_SetMouseCursor(bd, ImGui_ImplEmscripten_Cursor_None); // hide the cursor for the entire window if imgui is handling cursor drawing - not just when imgui wants to capture the mouse bd->LastMouseDrawCursor = true; } else @@ -475,34 +470,34 @@ static void ImGui_ImplEmscripten_UpdateMouseCursor(ImGui_ImplEmscripten_Data* bd switch (ImGui::GetMouseCursor()) { case ImGuiMouseCursor_None: - ImGui_ImplEmscripten_SetMouseCursor(bd, emscripten_browser_cursor_internal::cursor::none); + ImGui_ImplEmscripten_SetMouseCursor(bd, ImGui_ImplEmscripten_Cursor_None); break; case ImGuiMouseCursor_Arrow: - ImGui_ImplEmscripten_SetMouseCursor(bd, emscripten_browser_cursor_internal::cursor::cursor_default); + ImGui_ImplEmscripten_SetMouseCursor(bd, ImGui_ImplEmscripten_Cursor_Default); break; case ImGuiMouseCursor_TextInput: // When hovering over InputText, etc. - ImGui_ImplEmscripten_SetMouseCursor(bd, emscripten_browser_cursor_internal::cursor::text); + ImGui_ImplEmscripten_SetMouseCursor(bd, ImGui_ImplEmscripten_Cursor_Text); break; case ImGuiMouseCursor_ResizeAll: // (Unused by Dear ImGui functions) - ImGui_ImplEmscripten_SetMouseCursor(bd, emscripten_browser_cursor_internal::cursor::move); + ImGui_ImplEmscripten_SetMouseCursor(bd, ImGui_ImplEmscripten_Cursor_Move); break; case ImGuiMouseCursor_ResizeNS: // When hovering over a horizontal border - ImGui_ImplEmscripten_SetMouseCursor(bd, emscripten_browser_cursor_internal::cursor::ns_resize); + ImGui_ImplEmscripten_SetMouseCursor(bd, ImGui_ImplEmscripten_Cursor_NSResize); break; case ImGuiMouseCursor_ResizeEW: // When hovering over a vertical border or a column - ImGui_ImplEmscripten_SetMouseCursor(bd, emscripten_browser_cursor_internal::cursor::ew_resize); + ImGui_ImplEmscripten_SetMouseCursor(bd, ImGui_ImplEmscripten_Cursor_EWResize); break; case ImGuiMouseCursor_ResizeNESW: // When hovering over the bottom-left corner of a window - ImGui_ImplEmscripten_SetMouseCursor(bd, emscripten_browser_cursor_internal::cursor::nesw_resize); + ImGui_ImplEmscripten_SetMouseCursor(bd, ImGui_ImplEmscripten_Cursor_NESWResize); break; case ImGuiMouseCursor_ResizeNWSE: // When hovering over the bottom-right corner of a window - ImGui_ImplEmscripten_SetMouseCursor(bd, emscripten_browser_cursor_internal::cursor::nwse_resize); + ImGui_ImplEmscripten_SetMouseCursor(bd, ImGui_ImplEmscripten_Cursor_NWSEResize); break; case ImGuiMouseCursor_Hand: // (Unused by Dear ImGui functions. Use for e.g. hyperlinks) - ImGui_ImplEmscripten_SetMouseCursor(bd, emscripten_browser_cursor_internal::cursor::pointer); + ImGui_ImplEmscripten_SetMouseCursor(bd, ImGui_ImplEmscripten_Cursor_Pointer); break; case ImGuiMouseCursor_NotAllowed: // When hovering something with disallowed interaction. Usually a crossed circle. - ImGui_ImplEmscripten_SetMouseCursor(bd, emscripten_browser_cursor_internal::cursor::not_allowed); + ImGui_ImplEmscripten_SetMouseCursor(bd, ImGui_ImplEmscripten_Cursor_NotAllowed); break; } } @@ -529,41 +524,41 @@ static char* ImGui_ImplEmscripten_GetBrowserCursor() ); } -static void ImGui_ImplEmscripten_SetBrowserCursor(emscripten_browser_cursor_internal::cursor new_cursor) +static void ImGui_ImplEmscripten_SetBrowserCursor(ImGui_ImplEmscripten_Cursor new_cursor) { // Set the cursor according to the given enum // Note, implementations omitted for cursors not used by imgui. For full implementation, use https://github.com/Armchair-Software/emscripten-browser-cursor switch (new_cursor) { - case emscripten_browser_cursor_internal::cursor::none: + case ImGui_ImplEmscripten_Cursor_None: EM_ASM(document.body.style.cursor = 'none';); break; - case emscripten_browser_cursor_internal::cursor::cursor_default: + case ImGui_ImplEmscripten_Cursor_Default: default: EM_ASM(document.body.style.cursor = 'default';); break; - case emscripten_browser_cursor_internal::cursor::pointer: + case ImGui_ImplEmscripten_Cursor_Pointer: EM_ASM(document.body.style.cursor = 'pointer';); break; - case emscripten_browser_cursor_internal::cursor::text: + case ImGui_ImplEmscripten_Cursor_Text: EM_ASM(document.body.style.cursor = 'text';); break; - case emscripten_browser_cursor_internal::cursor::move: + case ImGui_ImplEmscripten_Cursor_Move: EM_ASM(document.body.style.cursor = 'move';); break; - case emscripten_browser_cursor_internal::cursor::not_allowed: + case ImGui_ImplEmscripten_Cursor_NotAllowed: EM_ASM(document.body.style.cursor = 'not-allowed';); break; - case emscripten_browser_cursor_internal::cursor::ew_resize: + case ImGui_ImplEmscripten_Cursor_EWResize: EM_ASM(document.body.style.cursor = 'ew-resize';); break; - case emscripten_browser_cursor_internal::cursor::ns_resize: + case ImGui_ImplEmscripten_Cursor_NSResize: EM_ASM(document.body.style.cursor = 'ns-resize';); break; - case emscripten_browser_cursor_internal::cursor::nesw_resize: + case ImGui_ImplEmscripten_Cursor_NESWResize: EM_ASM(document.body.style.cursor = 'nesw-resize';); break; - case emscripten_browser_cursor_internal::cursor::nwse_resize: + case ImGui_ImplEmscripten_Cursor_NWSEResize: EM_ASM(document.body.style.cursor = 'nwse-resize';); break; } @@ -595,7 +590,7 @@ static ImGuiStorage const& ImGui_ImplEmscripten_GetKeyTranslationStorage() }; static const ImGui_ImplEmscripten_KeyTranslation key_translations[] = { - // main character keys + // Main character keys { "Backquote", ImGuiKey_GraveAccent }, { "Backslash", ImGuiKey_Backslash }, { "BracketLeft", ImGuiKey_LeftBracket }, @@ -647,7 +642,7 @@ static ImGuiStorage const& ImGui_ImplEmscripten_GetKeyTranslationStorage() { "Semicolon", ImGuiKey_Semicolon }, { "Slash", ImGuiKey_Slash }, - // control keys + // Control keys { "AltLeft", ImGuiKey_LeftAlt }, { "AltRight", ImGuiKey_RightAlt }, { "Backspace", ImGuiKey_Backspace }, @@ -663,7 +658,7 @@ static ImGuiStorage const& ImGui_ImplEmscripten_GetKeyTranslationStorage() { "Space", ImGuiKey_Space }, { "Tab", ImGuiKey_Tab }, - // navigation key group + // Navigation key group { "Delete", ImGuiKey_Delete }, { "End", ImGuiKey_End }, //{ "Help", ImGuiKey_PrintScreen }, // Best approximation @@ -672,13 +667,13 @@ static ImGuiStorage const& ImGui_ImplEmscripten_GetKeyTranslationStorage() { "PageDown", ImGuiKey_PageDown }, { "PageUp", ImGuiKey_PageUp }, - // arrow key group + // Arrow key group { "ArrowDown", ImGuiKey_DownArrow }, { "ArrowLeft", ImGuiKey_LeftArrow }, { "ArrowRight", ImGuiKey_RightArrow }, { "ArrowUp", ImGuiKey_UpArrow }, - // browser key group + // Browser key group { "BrowserBack", ImGuiKey_AppBack }, // Pass through so the embedding app can decide //{ "BrowserFavorites", ImGuiKey_None }, // No direct mapping { "BrowserForward", ImGuiKey_AppForward }, // Pass through so the embedding app can decide @@ -687,7 +682,7 @@ static ImGuiStorage const& ImGui_ImplEmscripten_GetKeyTranslationStorage() //{ "BrowserSearch", ImGuiKey_None }, // No direct mapping //{ "BrowserStop", ImGuiKey_None }, // No direct mapping - // number pad group + // Number pad group { "NumLock", ImGuiKey_NumLock }, { "Numpad0", ImGuiKey_Keypad0 }, { "Numpad1", ImGuiKey_Keypad1 }, @@ -720,7 +715,7 @@ static ImGuiStorage const& ImGui_ImplEmscripten_GetKeyTranslationStorage() { "NumpadStar", ImGuiKey_KeypadMultiply }, // Same as multiply { "NumpadSubtract", ImGuiKey_KeypadSubtract }, - // top row key group + // Top row key group { "Escape", ImGuiKey_Escape }, { "F1", ImGuiKey_F1 }, { "F2", ImGuiKey_F2 }, @@ -752,7 +747,7 @@ static ImGuiStorage const& ImGui_ImplEmscripten_GetKeyTranslationStorage() { "ScrollLock", ImGuiKey_ScrollLock }, { "Pause", ImGuiKey_Pause }, - // clipboard/editing keys without direct mapping + // Clipboard/editing keys without direct mapping //{ "Abort", ImGuiKey_None }, //{ "Again", ImGuiKey_None }, //{ "Convert", ImGuiKey_None }, @@ -774,7 +769,7 @@ static ImGuiStorage const& ImGui_ImplEmscripten_GetKeyTranslationStorage() //{ "Lang2", ImGuiKey_None }, //{ "NonConvert", ImGuiKey_None }, - // media and launcher keys without direct mapping + // Media and launcher keys without direct mapping //{ "AudioVolumeDown", ImGuiKey_None }, //{ "AudioVolumeMute", ImGuiKey_None }, //{ "AudioVolumeUp", ImGuiKey_None }, @@ -787,7 +782,7 @@ static ImGuiStorage const& ImGui_ImplEmscripten_GetKeyTranslationStorage() //{ "MediaTrackNext", ImGuiKey_None }, //{ "MediaTrackPrevious", ImGuiKey_None }, - // system keys without direct mapping + // System keys without direct mapping //{ "Eject", ImGuiKey_None }, //{ "Hyper", ImGuiKey_None }, //{ "Power", ImGuiKey_None }, From 796b15e89485f22947fb44dc5d47b450f75797c0 Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Fri, 4 Sep 2026 12:47:34 +0100 Subject: [PATCH 66/72] return bool true from Init for consistency with other backends (although no other outcome is possible) --- backends/imgui_impl_emscripten.cpp | 4 +++- backends/imgui_impl_emscripten.h | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index a23864768..d7a1db793 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -123,7 +123,7 @@ static ImGui_ImplEmscripten_Data* ImGui_ImplEmscripten_GetBackendData() return ImGui::GetCurrentContext() ? (ImGui_ImplEmscripten_Data*)ImGui::GetIO().BackendPlatformUserData : nullptr; } -void ImGui_ImplEmscripten_Init() +bool ImGui_ImplEmscripten_Init() { // Initialise the Emscripten backend, setting input callbacks ImGuiIO& io = ImGui::GetIO(); @@ -377,6 +377,8 @@ void ImGui_ImplEmscripten_Init() ); // TODO: Touch events + + return true; } void ImGui_ImplEmscripten_Shutdown() diff --git a/backends/imgui_impl_emscripten.h b/backends/imgui_impl_emscripten.h index 6814c6f6c..3b416bb21 100644 --- a/backends/imgui_impl_emscripten.h +++ b/backends/imgui_impl_emscripten.h @@ -45,7 +45,7 @@ extern IMGUI_IMPL_API float ImGui_ImplEmscripten_TargetDevicePixelRatio; // Initialise the Emscripten backend, setting input callbacks. This should be called after ImGui::CreateContext(); -IMGUI_IMPL_API void ImGui_ImplEmscripten_Init(); +IMGUI_IMPL_API bool ImGui_ImplEmscripten_Init(); // Shut down the Emscripten backend. This unsets all Emscripten input callbacks set by Init. // Note it'll also unset any Emscripten input callbacks set elsewhere in the program! From cfa0d9f4e2259a462a37ffe21f6455bc12525f6f Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Fri, 4 Sep 2026 13:01:50 +0100 Subject: [PATCH 67/72] Backends: Emscripten: Add runtime device pixel ratio setter --- backends/imgui_impl_emscripten.cpp | 33 +++++++++++++++++++++--------- backends/imgui_impl_emscripten.h | 10 ++++----- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index d7a1db793..744d20900 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -4,6 +4,7 @@ // // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-09-04: Emscripten: Replaced TargetDevicePixelRatio global with an Init() parameter and runtime setter. // 2026-09-03: Updated coding style and simplified key translation table setup. // 2026-04-02: Inputs: Replaced custom KeyboardEvent.code parser with ImHashStr()/ImGuiStorage lookup to match Dear ImGui backend style. // 2026-03-31: Emscripten: Added configurable TargetDevicePixelRatio to control how browser device pixels map to Dear ImGui pixels. @@ -25,8 +26,6 @@ extern ImGuiID ImHashStr(char const* data, size_t data_size = 0, ImGuiID seed = 0); // Declared in imgui_internal.h. -float ImGui_ImplEmscripten_TargetDevicePixelRatio = 1.0f; - // W3C UI Events KeyboardEvent.code translation helpers static ImGuiKey ImGui_ImplEmscripten_TranslateKey(char const* emscripten_key); @@ -94,6 +93,7 @@ static void ImGui_ImplEmscripten_SetBrowserCursor(char const* new_cursor); struct ImGui_ImplEmscripten_Data { + float TargetDevicePixelRatio = 1.0f; float CssToImGuiScale = 1.0f; ImGui_ImplEmscripten_Cursor CurrentCursor = ImGui_ImplEmscripten_Cursor_Invalid; char* CursorToRestore = nullptr; @@ -101,15 +101,9 @@ struct ImGui_ImplEmscripten_Data bool LastNoMouseCursorChange = false; }; -static float ImGui_ImplEmscripten_GetTargetDevicePixelRatio() -{ - IM_ASSERT(ImGui_ImplEmscripten_TargetDevicePixelRatio > 0.0f && "ImGui_ImplEmscripten_TargetDevicePixelRatio must be positive."); - return ImGui_ImplEmscripten_TargetDevicePixelRatio > 0.0f ? ImGui_ImplEmscripten_TargetDevicePixelRatio : 1.0f; -} - static void ImGui_ImplEmscripten_UpdateDisplayProperties(ImGuiIO& io, ImGui_ImplEmscripten_Data* bd, float css_width, float css_height) { - float const target_device_pixel_ratio = ImGui_ImplEmscripten_GetTargetDevicePixelRatio(); + float const target_device_pixel_ratio = bd->TargetDevicePixelRatio; float const css_to_imgui_scale = (float)emscripten_get_device_pixel_ratio() / target_device_pixel_ratio; bd->CssToImGuiScale = css_to_imgui_scale; io.DisplaySize.x = css_width * css_to_imgui_scale; @@ -123,13 +117,16 @@ static ImGui_ImplEmscripten_Data* ImGui_ImplEmscripten_GetBackendData() return ImGui::GetCurrentContext() ? (ImGui_ImplEmscripten_Data*)ImGui::GetIO().BackendPlatformUserData : nullptr; } -bool ImGui_ImplEmscripten_Init() +bool ImGui_ImplEmscripten_Init(float target_device_pixel_ratio) { // Initialise the Emscripten backend, setting input callbacks ImGuiIO& io = ImGui::GetIO(); IMGUI_CHECKVERSION(); IM_ASSERT(io.BackendPlatformUserData == nullptr && "Already initialized a platform backend!"); + IM_ASSERT(target_device_pixel_ratio > 0.0f && "Target device pixel ratio must be positive."); + if (target_device_pixel_ratio <= 0.0f) return false; ImGui_ImplEmscripten_Data* bd = IM_NEW(ImGui_ImplEmscripten_Data)(); + bd->TargetDevicePixelRatio = target_device_pixel_ratio; io.BackendPlatformUserData = (void*)bd; io.BackendPlatformName = "imgui_impl_emscripten"; io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; @@ -381,6 +378,22 @@ bool ImGui_ImplEmscripten_Init() return true; } +void ImGui_ImplEmscripten_SetTargetDevicePixelRatio(float target_device_pixel_ratio) +{ + ImGui_ImplEmscripten_Data* bd = ImGui_ImplEmscripten_GetBackendData(); + IM_ASSERT(bd != nullptr && "Context or backend not initialized? Did you call ImGui_ImplEmscripten_Init()?"); + IM_ASSERT(target_device_pixel_ratio > 0.0f && "Target device pixel ratio must be positive."); + if (bd == nullptr || target_device_pixel_ratio <= 0.0f || target_device_pixel_ratio == bd->TargetDevicePixelRatio) return; + + bd->TargetDevicePixelRatio = target_device_pixel_ratio; + ImGui_ImplEmscripten_UpdateDisplayProperties( + ImGui::GetIO(), + bd, + (float)EM_ASM_INT(return window.innerWidth;), + (float)EM_ASM_INT(return window.innerHeight;) + ); +} + void ImGui_ImplEmscripten_Shutdown() { ImGui_ImplEmscripten_Data* bd = ImGui_ImplEmscripten_GetBackendData(); diff --git a/backends/imgui_impl_emscripten.h b/backends/imgui_impl_emscripten.h index 3b416bb21..c81e867f3 100644 --- a/backends/imgui_impl_emscripten.h +++ b/backends/imgui_impl_emscripten.h @@ -40,12 +40,12 @@ #error The imgui_impl_emscripten backend requires Emscripten. #endif -// Controls how many device pixels Dear ImGui should target per Dear ImGui pixel. -// Default 1.0f gives 1:1 device-pixel rendering. Set before Init() if you want a different scaling policy. -extern IMGUI_IMPL_API float ImGui_ImplEmscripten_TargetDevicePixelRatio; - // Initialise the Emscripten backend, setting input callbacks. This should be called after ImGui::CreateContext(); -IMGUI_IMPL_API bool ImGui_ImplEmscripten_Init(); +// The target_device_pixel_ratio parameter controls how many device pixels Dear ImGui should target per Dear ImGui pixel. Default 1.0f gives 1:1 device-pixel rendering. +IMGUI_IMPL_API bool ImGui_ImplEmscripten_Init(float target_device_pixel_ratio = 1.0f); + +// Change the target device pixel ratio at runtime and update display properties immediately. +IMGUI_IMPL_API void ImGui_ImplEmscripten_SetTargetDevicePixelRatio(float target_device_pixel_ratio); // Shut down the Emscripten backend. This unsets all Emscripten input callbacks set by Init. // Note it'll also unset any Emscripten input callbacks set elsewhere in the program! From ec5643bda697243f9273f6c04e2a93a3bd056fd0 Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Sat, 5 Sep 2026 17:28:51 +0100 Subject: [PATCH 68/72] Backends: Emscripten: derive display size from canvas framebuffer, per-axis mouse coordinate scaling --- backends/imgui_impl_emscripten.cpp | 63 ++++++++++++++++++------------ 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index 744d20900..52f24cd0c 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -4,6 +4,7 @@ // // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-09-05: Emscripten: Use the canvas framebuffer dimensions for display sizing and exact per-axis mouse coordinate scaling. // 2026-09-04: Emscripten: Replaced TargetDevicePixelRatio global with an Init() parameter and runtime setter. // 2026-09-03: Updated coding style and simplified key translation table setup. // 2026-04-02: Inputs: Replaced custom KeyboardEvent.code parser with ImHashStr()/ImGuiStorage lookup to match Dear ImGui backend style. @@ -94,21 +95,38 @@ static void ImGui_ImplEmscripten_SetBrowserCursor(char const* new_cursor); struct ImGui_ImplEmscripten_Data { float TargetDevicePixelRatio = 1.0f; - float CssToImGuiScale = 1.0f; + ImVec2 CssToImGuiScale = ImVec2(1.0f, 1.0f); + ImVec2 CanvasFramebufferSize = ImVec2(0.0f, 0.0f); ImGui_ImplEmscripten_Cursor CurrentCursor = ImGui_ImplEmscripten_Cursor_Invalid; char* CursorToRestore = nullptr; bool LastMouseDrawCursor = false; bool LastNoMouseCursorChange = false; }; -static void ImGui_ImplEmscripten_UpdateDisplayProperties(ImGuiIO& io, ImGui_ImplEmscripten_Data* bd, float css_width, float css_height) +static ImVec2 ImGui_ImplEmscripten_GetCanvasFramebufferSize() { + return ImVec2( + (float)EM_ASM_INT({ return Module["canvas"] ? Module["canvas"].width : 0; }), + (float)EM_ASM_INT({ return Module["canvas"] ? Module["canvas"].height : 0; })); +} + +static void ImGui_ImplEmscripten_UpdateDisplayProperties(ImGuiIO& io, ImGui_ImplEmscripten_Data* bd) +{ + double const css_width = EM_ASM_DOUBLE({ return Module["canvas"] ? Module["canvas"].getBoundingClientRect().width : window.innerWidth; }); + double const css_height = EM_ASM_DOUBLE({ return Module["canvas"] ? Module["canvas"].getBoundingClientRect().height : window.innerHeight; }); float const target_device_pixel_ratio = bd->TargetDevicePixelRatio; - float const css_to_imgui_scale = (float)emscripten_get_device_pixel_ratio() / target_device_pixel_ratio; - bd->CssToImGuiScale = css_to_imgui_scale; - io.DisplaySize.x = css_width * css_to_imgui_scale; - io.DisplaySize.y = css_height * css_to_imgui_scale; + ImVec2 framebuffer_size = ImGui_ImplEmscripten_GetCanvasFramebufferSize(); + if (framebuffer_size.x <= 0.0f || framebuffer_size.y <= 0.0f) + { + double const device_pixel_ratio = emscripten_get_device_pixel_ratio(); + framebuffer_size = ImVec2((float)(int)(css_width * device_pixel_ratio + 0.5), (float)(int)(css_height * device_pixel_ratio + 0.5)); + } + bd->CanvasFramebufferSize = framebuffer_size; + io.DisplaySize.x = framebuffer_size.x / target_device_pixel_ratio; + io.DisplaySize.y = framebuffer_size.y / target_device_pixel_ratio; io.DisplayFramebufferScale = ImVec2(target_device_pixel_ratio, target_device_pixel_ratio); + bd->CssToImGuiScale.x = css_width > 0.0 ? (float)((double)io.DisplaySize.x / css_width) : 1.0f; + bd->CssToImGuiScale.y = css_height > 0.0 ? (float)((double)io.DisplaySize.y / css_height) : 1.0f; } // Backend data stored in io.BackendPlatformUserData to allow support for multiple Dear ImGui contexts @@ -132,12 +150,7 @@ bool ImGui_ImplEmscripten_Init(float target_device_pixel_ratio) io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // Set up initial display size values - ImGui_ImplEmscripten_UpdateDisplayProperties( - io, - bd, - (float)EM_ASM_INT(return window.innerWidth;), - (float)EM_ASM_INT(return window.innerHeight;) - ); + ImGui_ImplEmscripten_UpdateDisplayProperties(io, bd); emscripten_set_mousemove_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, // target @@ -146,10 +159,10 @@ bool ImGui_ImplEmscripten_Init(float target_device_pixel_ratio) [](int /*event_type*/, EmscriptenMouseEvent const* mouse_event, void* /*data*/) // callback, event_type == EMSCRIPTEN_EVENT_MOUSEMOVE { ImGui_ImplEmscripten_Data* bd = ImGui_ImplEmscripten_GetBackendData(); - float const css_to_imgui_scale = bd ? bd->CssToImGuiScale : 1.0f; + ImVec2 const css_to_imgui_scale = bd ? bd->CssToImGuiScale : ImVec2(1.0f, 1.0f); ImGui::GetIO().AddMousePosEvent( - (float)mouse_event->clientX * css_to_imgui_scale, - (float)mouse_event->clientY * css_to_imgui_scale + (float)mouse_event->clientX * css_to_imgui_scale.x, + (float)mouse_event->clientY * css_to_imgui_scale.y ); return true; // the event was consumed } @@ -181,10 +194,10 @@ bool ImGui_ImplEmscripten_Init(float target_device_pixel_ratio) [](int /*event_type*/, EmscriptenMouseEvent const* mouse_event, void* /*data*/) // callback, event_type == EMSCRIPTEN_EVENT_MOUSEENTER { ImGui_ImplEmscripten_Data* bd = ImGui_ImplEmscripten_GetBackendData(); - float const css_to_imgui_scale = bd ? bd->CssToImGuiScale : 1.0f; + ImVec2 const css_to_imgui_scale = bd ? bd->CssToImGuiScale : ImVec2(1.0f, 1.0f); ImGui::GetIO().AddMousePosEvent( - (float)mouse_event->clientX * css_to_imgui_scale, - (float)mouse_event->clientY * css_to_imgui_scale + (float)mouse_event->clientX * css_to_imgui_scale.x, + (float)mouse_event->clientY * css_to_imgui_scale.y ); return true; // the event was consumed } @@ -316,11 +329,11 @@ bool ImGui_ImplEmscripten_Init(float target_device_pixel_ratio) EMSCRIPTEN_EVENT_TARGET_WINDOW, // target nullptr, // userData false, // useCapture - [](int /*event_type*/, EmscriptenUiEvent const* event, void* /*data*/) // event_type == EMSCRIPTEN_EVENT_RESIZE + [](int /*event_type*/, EmscriptenUiEvent const* /*event*/, void* /*data*/) // event_type == EMSCRIPTEN_EVENT_RESIZE { ImGuiIO& io = ImGui::GetIO(); ImGui_ImplEmscripten_Data* bd = ImGui_ImplEmscripten_GetBackendData(); - if (bd != nullptr) ImGui_ImplEmscripten_UpdateDisplayProperties(io, bd, (float)event->windowInnerWidth, (float)event->windowInnerHeight); + if (bd != nullptr) ImGui_ImplEmscripten_UpdateDisplayProperties(io, bd); return true; // the event was consumed } ); @@ -386,12 +399,7 @@ void ImGui_ImplEmscripten_SetTargetDevicePixelRatio(float target_device_pixel_ra if (bd == nullptr || target_device_pixel_ratio <= 0.0f || target_device_pixel_ratio == bd->TargetDevicePixelRatio) return; bd->TargetDevicePixelRatio = target_device_pixel_ratio; - ImGui_ImplEmscripten_UpdateDisplayProperties( - ImGui::GetIO(), - bd, - (float)EM_ASM_INT(return window.innerWidth;), - (float)EM_ASM_INT(return window.innerHeight;) - ); + ImGui_ImplEmscripten_UpdateDisplayProperties(ImGui::GetIO(), bd); } void ImGui_ImplEmscripten_Shutdown() @@ -528,6 +536,9 @@ void ImGui_ImplEmscripten_NewFrame() IM_ASSERT(bd != nullptr && "Context or backend not initialized? Did you call ImGui_ImplEmscripten_Init()?"); // Update any state that needs to be polled + ImVec2 const canvas_framebuffer_size = ImGui_ImplEmscripten_GetCanvasFramebufferSize(); + if (canvas_framebuffer_size.x != bd->CanvasFramebufferSize.x || canvas_framebuffer_size.y != bd->CanvasFramebufferSize.y) + ImGui_ImplEmscripten_UpdateDisplayProperties(ImGui::GetIO(), bd); ImGui_ImplEmscripten_UpdateMouseCursor(bd); } From da9e7b6e5ecc58630af58cdde62fdec3eb24b2af Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Sun, 6 Sep 2026 15:51:09 +0100 Subject: [PATCH 69/72] expose a getter for css to imgui scale ratio --- backends/imgui_impl_emscripten.cpp | 8 ++++++++ backends/imgui_impl_emscripten.h | 3 +++ 2 files changed, 11 insertions(+) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index 52f24cd0c..a6c358d08 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -4,6 +4,7 @@ // // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-09-06: Emscripten: Added ImGui_ImplEmscripten_GetCssToImGuiScale() helper. // 2026-09-05: Emscripten: Use the canvas framebuffer dimensions for display sizing and exact per-axis mouse coordinate scaling. // 2026-09-04: Emscripten: Replaced TargetDevicePixelRatio global with an Init() parameter and runtime setter. // 2026-09-03: Updated coding style and simplified key translation table setup. @@ -402,6 +403,13 @@ void ImGui_ImplEmscripten_SetTargetDevicePixelRatio(float target_device_pixel_ra ImGui_ImplEmscripten_UpdateDisplayProperties(ImGui::GetIO(), bd); } +ImVec2 ImGui_ImplEmscripten_GetCssToImGuiScale() +{ + ImGui_ImplEmscripten_Data* bd = ImGui_ImplEmscripten_GetBackendData(); + IM_ASSERT(bd != nullptr && "Context or backend not initialized? Did you call ImGui_ImplEmscripten_Init()?"); + return bd != nullptr ? bd->CssToImGuiScale : ImVec2(1.0f, 1.0f); +} + void ImGui_ImplEmscripten_Shutdown() { ImGui_ImplEmscripten_Data* bd = ImGui_ImplEmscripten_GetBackendData(); diff --git a/backends/imgui_impl_emscripten.h b/backends/imgui_impl_emscripten.h index c81e867f3..357017517 100644 --- a/backends/imgui_impl_emscripten.h +++ b/backends/imgui_impl_emscripten.h @@ -47,6 +47,9 @@ IMGUI_IMPL_API bool ImGui_ImplEmscripten_Init(float target_device_pixel_ratio = // Change the target device pixel ratio at runtime and update display properties immediately. IMGUI_IMPL_API void ImGui_ImplEmscripten_SetTargetDevicePixelRatio(float target_device_pixel_ratio); +// Return the scale used to convert browser CSS pixel coordinates to Dear ImGui coordinates. Valid after Init(). +IMGUI_IMPL_API ImVec2 ImGui_ImplEmscripten_GetCssToImGuiScale(); + // Shut down the Emscripten backend. This unsets all Emscripten input callbacks set by Init. // Note it'll also unset any Emscripten input callbacks set elsewhere in the program! // Note also there is no obligation to ever call this, unless you intend to reset the backend (i.e. with ImGui::DestroyContext()). From e69fd222c2935c9ccd70f4f50f9f4f9f2a8b29e2 Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Sun, 6 Sep 2026 15:55:11 +0100 Subject: [PATCH 70/72] reformat comments --- backends/imgui_impl_emscripten.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index a6c358d08..6aa3e5677 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -4,19 +4,19 @@ // // CHANGELOG // (minor and older changes stripped away, please see git history for details) -// 2026-09-06: Emscripten: Added ImGui_ImplEmscripten_GetCssToImGuiScale() helper. -// 2026-09-05: Emscripten: Use the canvas framebuffer dimensions for display sizing and exact per-axis mouse coordinate scaling. -// 2026-09-04: Emscripten: Replaced TargetDevicePixelRatio global with an Init() parameter and runtime setter. +// 2026-09-06: DPI: Added ImGui_ImplEmscripten_GetCssToImGuiScale() getter. +// 2026-09-05: DPI: Use the canvas framebuffer dimensions for display sizing and exact per-axis mouse coordinate scaling. +// 2026-09-04: Replaced TargetDevicePixelRatio global with an Init() parameter and runtime setter. // 2026-09-03: Updated coding style and simplified key translation table setup. // 2026-04-02: Inputs: Replaced custom KeyboardEvent.code parser with ImHashStr()/ImGuiStorage lookup to match Dear ImGui backend style. -// 2026-03-31: Emscripten: Added configurable TargetDevicePixelRatio to control how browser device pixels map to Dear ImGui pixels. +// 2026-03-31: Added configurable TargetDevicePixelRatio to control how browser device pixels map to Dear ImGui pixels. // 2026-03-31: Inputs: Added BrowserBack/Forward and F13-F24 key mappings. -// 2026-03-31: Emscripten: Moved cursor state into backend userdata and replaced cursor restore storage with owned C strings. +// 2026-03-31: Moved cursor state into backend userdata and replaced cursor restore storage with owned C strings. // 2024-12-09: Inputs: Added special handling for modifier keys to also generate modifier key events. // 2024-12-08: Inputs: Prevent "Delete" key from getting printed in text input. // 2024-12-06: Inputs: Added special handling for Tab and Enter event capture. // 2024-12-06: Inputs: Handle blur and focus events correctly, focusin and focusout aren't enough. -// 2024-12-06: Emscripten: Don't rely on devicePixelRatio for WebGPU framebuffer sizing; CSS->ImGui scaling may still use it. +// 2024-12-06: Don't rely on devicePixelRatio for WebGPU framebuffer sizing; CSS->ImGui scaling may still use it. // 2024-11-22: Initial version by Eugene Hopkinson. (#8178) #include "imgui.h" From 7ba43170f83436d38b4050b1afb726f9c7c8fdd4 Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Sun, 6 Sep 2026 15:56:56 +0100 Subject: [PATCH 71/72] expose translate button and key functions as public --- backends/imgui_impl_emscripten.cpp | 10 +++------- backends/imgui_impl_emscripten.h | 4 ++++ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index 6aa3e5677..03a682472 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -4,6 +4,7 @@ // // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-09-06: Inputs: Exposed mouse button and key translation functions as public. // 2026-09-06: DPI: Added ImGui_ImplEmscripten_GetCssToImGuiScale() getter. // 2026-09-05: DPI: Use the canvas framebuffer dimensions for display sizing and exact per-axis mouse coordinate scaling. // 2026-09-04: Replaced TargetDevicePixelRatio global with an Init() parameter and runtime setter. @@ -28,11 +29,6 @@ extern ImGuiID ImHashStr(char const* data, size_t data_size = 0, ImGuiID seed = 0); // Declared in imgui_internal.h. -// W3C UI Events KeyboardEvent.code translation helpers - -static ImGuiKey ImGui_ImplEmscripten_TranslateKey(char const* emscripten_key); -static constexpr ImGuiMouseButton ImGui_ImplEmscripten_TranslateMouseButton(unsigned short emscripten_button) __attribute__((__const__)); - // Browser cursor helpers, adapted from https://github.com/Armchair-Software/emscripten-browser-cursor enum ImGui_ImplEmscripten_Cursor @@ -606,7 +602,7 @@ static void ImGui_ImplEmscripten_SetBrowserCursor(char const* new_cursor) }, new_cursor); } -static constexpr ImGuiMouseButton ImGui_ImplEmscripten_TranslateMouseButton(unsigned short emscripten_button) +ImGuiMouseButton ImGui_ImplEmscripten_TranslateMouseButton(unsigned short emscripten_button) { // Translate an emscripten-provided integer describing a mouse button to an imgui mouse button if (emscripten_button == 1) return ImGuiMouseButton_Middle; // 1 = middle mouse button @@ -841,7 +837,7 @@ static ImGuiStorage const& ImGui_ImplEmscripten_GetKeyTranslationStorage() return storage; } -static ImGuiKey ImGui_ImplEmscripten_TranslateKey(char const* emscripten_key) +ImGuiKey ImGui_ImplEmscripten_TranslateKey(const char* emscripten_key) { // Translate a W3C KeyboardEvent.code string into an ImGuiKey. if (emscripten_key == nullptr || emscripten_key[0] == '\0') return ImGuiKey_None; diff --git a/backends/imgui_impl_emscripten.h b/backends/imgui_impl_emscripten.h index 357017517..b7a8a707e 100644 --- a/backends/imgui_impl_emscripten.h +++ b/backends/imgui_impl_emscripten.h @@ -50,6 +50,10 @@ IMGUI_IMPL_API void ImGui_ImplEmscripten_SetTargetDevicePixelRatio(float target_ // Return the scale used to convert browser CSS pixel coordinates to Dear ImGui coordinates. Valid after Init(). IMGUI_IMPL_API ImVec2 ImGui_ImplEmscripten_GetCssToImGuiScale(); +// Input translation helpers. +IMGUI_IMPL_API ImGuiMouseButton ImGui_ImplEmscripten_TranslateMouseButton(unsigned short emscripten_button); +IMGUI_IMPL_API ImGuiKey ImGui_ImplEmscripten_TranslateKey(const char* emscripten_key); + // Shut down the Emscripten backend. This unsets all Emscripten input callbacks set by Init. // Note it'll also unset any Emscripten input callbacks set elsewhere in the program! // Note also there is no obligation to ever call this, unless you intend to reset the backend (i.e. with ImGui::DestroyContext()). From d88b253cd77ef5069d0f1da969fb31973894309a Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Sun, 6 Sep 2026 16:02:23 +0100 Subject: [PATCH 72/72] add public function to update display properties at will --- backends/imgui_impl_emscripten.cpp | 8 ++++++++ backends/imgui_impl_emscripten.h | 3 +++ 2 files changed, 11 insertions(+) diff --git a/backends/imgui_impl_emscripten.cpp b/backends/imgui_impl_emscripten.cpp index 03a682472..59f9beb7e 100644 --- a/backends/imgui_impl_emscripten.cpp +++ b/backends/imgui_impl_emscripten.cpp @@ -4,6 +4,7 @@ // // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-09-06: DPI: Added public ImGui_ImplEmscripten_UpdateDisplayProperties() wrapper. // 2026-09-06: Inputs: Exposed mouse button and key translation functions as public. // 2026-09-06: DPI: Added ImGui_ImplEmscripten_GetCssToImGuiScale() getter. // 2026-09-05: DPI: Use the canvas framebuffer dimensions for display sizing and exact per-axis mouse coordinate scaling. @@ -406,6 +407,13 @@ ImVec2 ImGui_ImplEmscripten_GetCssToImGuiScale() return bd != nullptr ? bd->CssToImGuiScale : ImVec2(1.0f, 1.0f); } +void ImGui_ImplEmscripten_UpdateDisplayProperties() +{ + ImGui_ImplEmscripten_Data* bd = ImGui_ImplEmscripten_GetBackendData(); + IM_ASSERT(bd != nullptr && "Context or backend not initialized? Did you call ImGui_ImplEmscripten_Init()?"); + if (bd != nullptr) ImGui_ImplEmscripten_UpdateDisplayProperties(ImGui::GetIO(), bd); +} + void ImGui_ImplEmscripten_Shutdown() { ImGui_ImplEmscripten_Data* bd = ImGui_ImplEmscripten_GetBackendData(); diff --git a/backends/imgui_impl_emscripten.h b/backends/imgui_impl_emscripten.h index b7a8a707e..7589e2669 100644 --- a/backends/imgui_impl_emscripten.h +++ b/backends/imgui_impl_emscripten.h @@ -50,6 +50,9 @@ IMGUI_IMPL_API void ImGui_ImplEmscripten_SetTargetDevicePixelRatio(float target_ // Return the scale used to convert browser CSS pixel coordinates to Dear ImGui coordinates. Valid after Init(). IMGUI_IMPL_API ImVec2 ImGui_ImplEmscripten_GetCssToImGuiScale(); +// Refresh display size and scaling properties immediately. Call after changing the canvas framebuffer or CSS size. +IMGUI_IMPL_API void ImGui_ImplEmscripten_UpdateDisplayProperties(); + // Input translation helpers. IMGUI_IMPL_API ImGuiMouseButton ImGui_ImplEmscripten_TranslateMouseButton(unsigned short emscripten_button); IMGUI_IMPL_API ImGuiKey ImGui_ImplEmscripten_TranslateKey(const char* emscripten_key);