From 5220c14f4b880dfe6b61ee105dedcc59d02c0d29 Mon Sep 17 00:00:00 2001 From: omar Date: Wed, 11 Mar 2026 21:16:49 +0100 Subject: [PATCH 01/14] Docs: update readme. --- docs/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/README.md b/docs/README.md index a409b9301..283dd479b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -55,8 +55,8 @@ if (ImGui::Button("Save")) ImGui::InputText("string", buf, IM_COUNTOF(buf)); ImGui::SliderFloat("float", &f, 0.0f, 1.0f); ``` -sample code output (dark) -sample code output (light) +sample code output (dark) +sample code output (light) ```cpp // Create a window called "My First Tool", with a menu bar. @@ -150,10 +150,10 @@ Officially maintained backends (in repository): - Frameworks: AGS/Adventure Game Studio, Amethyst, Blender, bsf, Cinder, Cocos2d-x, Defold, Diligent Engine, Ebiten, Flexium, GML/Game Maker Studio, GLEQ, Godot, GTK3, Irrlicht Engine, JUCE, LÖVE+LUA, Mach Engine, Magnum, Marmalade, Monogame, NanoRT, nCine, Nim Game Lib, Nintendo 3DS/Switch/WiiU (homebrew), Ogre, openFrameworks, OSG/OpenSceneGraph, Orx, Photoshop, px_render, Qt/QtDirect3D, raylib, SFML, Sokol, Unity, Unreal Engine 4/5, UWP, vtk, VulkanHpp, VulkanSceneGraph, Win32 GDI, WxWidgets. - Many bindings are auto-generated (by good old [cimgui](https://github.com/cimgui/cimgui) or our newer [dear_bindings](https://github.com/dearimgui/dear_bindings)), you can use their metadata output to generate bindings for other languages. -Useful extensions - [Useful Extensions/Widgets](https://github.com/ocornut/imgui/wiki/Useful-Extensions) wiki page: -- Automation/testing, Text editors, node editors, timeline editors, plotting, software renderers, remote network access, memory editors, gizmos, etc. Notable and well supported extensions include [ImPlot](https://github.com/epezent/implot) and [Dear ImGui Test Engine](https://github.com/ocornut/imgui_test_engine). + +[![Useful extensions thumbnails](https://github.com/user-attachments/assets/e6b0aa7c-bf53-41c5-ac69-bea3098b1dee)](https://github.com/ocornut/imgui/wiki/Useful-Extensions) +- Automation/testing, Text editors, node editors, timeline editors, plotting, software renderers, remote network access, memory editors, gizmos, etc. Notable and well supported extensions include [ImPlot](https://github.com/epezent/implot), [ImPlot3d](https://github.com/brenocq/implot3d) and [Dear ImGui Test Engine](https://github.com/ocornut/imgui_test_engine). Also see [Wiki](https://github.com/ocornut/imgui/wiki) for more links and ideas. From 7546f1eb1688c1e3bc40ca1c1c4376a49510bf9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20B=C3=B6hme?= Date: Wed, 11 Jun 2025 17:06:31 +0200 Subject: [PATCH 02/14] Backends: Vulkan: ImGui_ImplVulkan_AddTexture() skips updating descriptor_set if failing to allocate. (#8677) Reduce error surface to the check_vk_result() call. --- backends/imgui_impl_vulkan.cpp | 1 + docs/CHANGELOG.txt | 2 ++ 2 files changed, 3 insertions(+) diff --git a/backends/imgui_impl_vulkan.cpp b/backends/imgui_impl_vulkan.cpp index 7bfd48a34..1b4e81c2f 100644 --- a/backends/imgui_impl_vulkan.cpp +++ b/backends/imgui_impl_vulkan.cpp @@ -1376,6 +1376,7 @@ VkDescriptorSet ImGui_ImplVulkan_AddTexture(VkSampler sampler, VkImageView image } // Update the Descriptor Set: + if (descriptor_set != VK_NULL_HANDLE) { VkDescriptorImageInfo desc_image[1] = {}; desc_image[0].sampler = sampler; diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 176b7e6b6..43a7f788e 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -107,6 +107,8 @@ Other Changes: - hidden scrollbar in Firefox. - Vulkan: added ImGui_ImplVulkan_PipelineInfo::ExtraDynamicStates[] to allow specifying extra dynamic states to add when creating the VkPipeline. (#9211) [@DziubanMaciej] + - Vulkan: ImGui_ImplVulkan_AddTexture() skips updating descriptor_set if failing + to allocate one. (#8677) [@micb25] - WebGPU: fixed undefined behaviors in example code for requesting adapter and device. (#9246, #9256) [@r-lyeh] - GLFW/SDL2/SDL3+WebGPU: removed suport for Emscripten <4.0.10. (#9281) [@ypujante] From 1fbab15c0ab06392299d3d0374082010741174e5 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 11 Mar 2026 21:59:44 +0100 Subject: [PATCH 03/14] Focus: fixed fallback "Debug" window temporarily taking focus and setting io.WantCaptureKeyboard for a frame. (#9243) --- docs/CHANGELOG.txt | 2 ++ imgui.cpp | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 43a7f788e..d812e8b99 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -92,6 +92,8 @@ Other Changes: - Implemented a custom tweak to extend hit-testing bounding box when window is sitting at the edge of a viewport (e.g. fullscreen or docked window), so that e.g. mouse the mouse at the extreme of the screen will reach the scrollbar. (#9276) +- Focus: fixed fallback "Debug" window temporarily taking focus and setting io.WantCaptureKeyboard + for one frame on e.g. application boot if no other windows are submitted. (#9243) - Demo: fixed IMGUI_DEMO_MARKER locations for examples applets. (#9261, #3689) [@pthom] - Backends: - SDLGPU3: removed unnecessary call to SDL_WaitForGPUIdle when releasing diff --git a/imgui.cpp b/imgui.cpp index 3638f6bfb..d716a5a32 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -5985,10 +5985,14 @@ void ImGui::EndFrame() } g.WantTextInputNextFrame = ime_data->WantTextInput ? 1 : 0; - // Hide implicit/fallback "Debug" window if it hasn't been used + // Hide and unfocus implicit/fallback "Debug" window if it hasn't been used g.WithinFrameScopeWithImplicitWindow = false; - if (g.CurrentWindow && !g.CurrentWindow->WriteAccessed) + if (g.CurrentWindow && g.CurrentWindow->IsFallbackWindow && g.CurrentWindow->WriteAccessed == false) + { g.CurrentWindow->Active = false; + if (g.NavWindow && g.NavWindow->RootWindow == g.CurrentWindow) + FocusWindow(NULL); + } End(); // Update navigation: Ctrl+Tab, wrap-around requests From 0db591935f08c73f1e0726869a92ca803e8660a9 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 12 Mar 2026 14:23:29 +0100 Subject: [PATCH 04/14] Changed default ImTextureID_Invalid value to -1 instead of 0 +added comments. (#9293, #8745, #8465, #7090) --- docs/CHANGELOG.txt | 6 ++++++ imgui.cpp | 6 ++++-- imgui.h | 14 +++++++++----- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index d812e8b99..a88d8d0a7 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -41,6 +41,12 @@ HOW TO UPDATE? Breaking Changes: + - Changed default ImTextureID_Invalid value to -1 instead of 0 if not #define-d. + (#9293, #8745, #8465, #7090) + - It seems like a better default since it will work with backends storing + indices or memory offsets inside ImTextureID, where 0 might be a valid value. + - If this is causing problem with e.g your custom ImTextureID definition, you can + add '#define ImTextureID_Invalid 0' to your imconfig.h + PLEASE report this to GitHub. - Separator(): fixed a legacy quirk where Separator() was submitting a zero-height item for layout purpose, even though it draws a 1-pixel separator. The fix could affect code e.g. computing height from multiple widgets in order to diff --git a/imgui.cpp b/imgui.cpp index d716a5a32..291f0fcef 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -394,6 +394,9 @@ IMPLEMENTING SUPPORT for ImGuiBackendFlags_RendererHasTextures: When you are not sure about an old symbol or function name, try using the Search/Find function of your IDE to look for comments or references in all imgui files. You can read releases logs https://github.com/ocornut/imgui/releases for more details. + - 2026/03/12 (1.92.7) - Changed default ImTextureID_Invalid to -1 instead of 0 if not #define-d. (#9293, #8745, #8465, #7090) + It seems like a better default since it will work with backends storing indices or memory offsets inside ImTextureID, where 0 might be a valid value. + If this is causing problem with e.g. your custom ImTextureID definition, you can add '#define ImTextureID_Invalid 0' to your imconfig.h + PLEASE report this to GitHub. - 2026/02/26 (1.92.7) - Separator: fixed a legacy quirk where Separator() was submitting a zero-height item for layout purpose, even though it draws a 1-pixel separator. The fix could affect code e.g. computing height from multiple widgets in order to allocate vertical space for a footer or multi-line status bar. (#2657, #9263) The "Console" example had such a bug: @@ -402,8 +405,7 @@ IMPLEMENTING SUPPORT for ImGuiBackendFlags_RendererHasTextures: Should be: float footer_height = style.ItemSpacing.y + style.SeparatorSize + ImGui::GetFrameHeightWithSpacing(); BeginChild("ScrollingRegion", { 0, -footer_height }); - When such idiom was used and assuming zero-height Separator, it is likely that - in 1.92.7 the resulting window will have unexpected 1 pixel scrolling range. + When such idiom was used and assuming zero-height Separator, it is likely that in 1.92.7 the resulting window will have unexpected 1 pixel scrolling range. - 2026/02/23 (1.92.7) - Commented out legacy signature for Combo(), ListBox(), signatures which were obsoleted in 1.90 (Nov 2023), when the getter callback type was changed. - Old getter type: bool (*getter)(void* user_data, int idx, const char** out_text) // Set label + return bool. False replaced label with placeholder. - New getter type: const char* (*getter)(void* user_data, int idx) // Return label or NULL/empty label if missing diff --git a/imgui.h b/imgui.h index cc82c5f3d..a41b91b52 100644 --- a/imgui.h +++ b/imgui.h @@ -30,7 +30,7 @@ // Library Version // (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM >= 12345') #define IMGUI_VERSION "1.92.7 WIP" -#define IMGUI_VERSION_NUM 19264 +#define IMGUI_VERSION_NUM 19265 #define IMGUI_HAS_TABLE // Added BeginTable() - from IMGUI_VERSION_NUM >= 18000 #define IMGUI_HAS_TEXTURES // Added ImGuiBackendFlags_RendererHasTextures - from IMGUI_VERSION_NUM >= 19198 @@ -340,9 +340,11 @@ IM_MSVC_RUNTIME_CHECKS_RESTORE typedef ImU64 ImTextureID; // Default: store up to 64-bits (any pointer or integer). A majority of backends are ok with that. #endif -// Define this if you need 0 to be a valid ImTextureID for your backend. +// Define this if you need to change the invalid value for your backend. +// - in v1.92.7 (2025/03/12): we changed default value from 0 to -1 as it is a better default, which supports storing offsets/indices. +// - If this is causing problem with your custom ImTextureID definition, you can add '#define ImTextureID_Invalid' to your imconfig + please report this to GitHub. #ifndef ImTextureID_Invalid -#define ImTextureID_Invalid ((ImTextureID)0) +#define ImTextureID_Invalid ((ImTextureID)-1) #endif // ImTextureRef = higher-level identifier for a texture. Store a ImTextureID _or_ a ImTextureData*. @@ -3906,8 +3908,10 @@ inline ImTextureID ImTextureRef::GetTexID() const // Using an indirection to avoid patching ImDrawCmd after a SetTexID() call (but this could be an alternative solution too) inline ImTextureID ImDrawCmd::GetTexID() const { - // If you are getting this assert: A renderer backend with support for ImGuiBackendFlags_RendererHasTextures (1.92) - // must iterate and handle ImTextureData requests stored in ImDrawData::Textures[]. + // If you are getting this assert with ImTextureID_Invalid == 0 and your ImTextureID is used to store an index: + // - You can add '#define ImTextureID_Invalid ((ImTextureID)-1)' in your imconfig file. + // If you are getting this assert with a renderer backend with support for ImGuiBackendFlags_RendererHasTextures (1.92+): + // - You must correctly iterate and handle ImTextureData requests stored in ImDrawData::Textures[]. See docs/BACKENDS.md. ImTextureID tex_id = TexRef._TexData ? TexRef._TexData->TexID : TexRef._TexID; // == TexRef.GetTexID() above. if (TexRef._TexData != NULL) IM_ASSERT(tex_id != ImTextureID_Invalid && "ImDrawCmd is referring to ImTextureData that wasn't uploaded to graphics system. Backend must call ImTextureData::SetTexID() after handling ImTextureStatus_WantCreate request!"); From a1038261542442730d1ca6bf00a810a0acae92a4 Mon Sep 17 00:00:00 2001 From: exelix <13405476+exelix11@users.noreply.github.com> Date: Tue, 3 Mar 2026 16:41:24 +0100 Subject: [PATCH 05/14] Nav: allow ImGuiKey_Menu or Shift + F10 to open context menus. (#8803, #9270) --- imgui.cpp | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 291f0fcef..845529829 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -12505,7 +12505,11 @@ void ImGui::OpenPopupOnItemClick(const char* str_id, ImGuiPopupFlags popup_flags ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; ImGuiMouseButton mouse_button = GetMouseButtonFromPopupFlags(popup_flags); - if (IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + bool isMouseInvocation = IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup); + bool isKeyboardInvocation = false; + if ((g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) && IsItemFocused()) + isKeyboardInvocation = IsKeyReleased(ImGuiKey_Menu) || (IsKeyReleased(ImGuiKey_F10) && g.IO.KeyShift); + if (isKeyboardInvocation || isMouseInvocation) { ImGuiID id = str_id ? window->GetID(str_id) : g.LastItemData.ID; // If user hasn't passed an ID, we can use the LastItemID. Using LastItemID as a Popup ID won't conflict! IM_ASSERT(id != 0); // You cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item) @@ -12538,7 +12542,11 @@ bool ImGui::BeginPopupContextItem(const char* str_id, ImGuiPopupFlags popup_flag ImGuiID id = str_id ? window->GetID(str_id) : g.LastItemData.ID; // If user hasn't passed an ID, we can use the LastItemID. Using LastItemID as a Popup ID won't conflict! IM_ASSERT(id != 0); // You cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item) ImGuiMouseButton mouse_button = GetMouseButtonFromPopupFlags(popup_flags); - if (IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + bool isMouseInvocation = IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup); + bool isKeyboardInvocation = false; + if ((g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) && IsItemFocused()) + isKeyboardInvocation = IsKeyReleased(ImGuiKey_Menu) || (IsKeyReleased(ImGuiKey_F10) && g.IO.KeyShift); + if (isKeyboardInvocation || isMouseInvocation) OpenPopupEx(id, popup_flags); return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings); } @@ -12551,8 +12559,12 @@ bool ImGui::BeginPopupContextWindow(const char* str_id, ImGuiPopupFlags popup_fl str_id = "window_context"; ImGuiID id = window->GetID(str_id); ImGuiMouseButton mouse_button = GetMouseButtonFromPopupFlags(popup_flags); - if (IsMouseReleased(mouse_button) && IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) - if (!(popup_flags & ImGuiPopupFlags_NoOpenOverItems) || !IsAnyItemHovered()) + bool isMouseInvocation = IsMouseReleased(mouse_button) && IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup); + bool isKeyboardInvocation = false; + if ((g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) && IsWindowFocused()) + isKeyboardInvocation = IsKeyReleased(ImGuiKey_Menu) || (IsKeyReleased(ImGuiKey_F10) && g.IO.KeyShift); + if (isMouseInvocation || isKeyboardInvocation) + if (!(popup_flags & ImGuiPopupFlags_NoOpenOverItems) || !IsAnyItemHovered() || isKeyboardInvocation) OpenPopupEx(id, popup_flags); return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings); } From 6cc99a6e2b32b200052203f2aba5c760e5dee029 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 12 Mar 2026 18:23:50 +0100 Subject: [PATCH 06/14] Nav: allow ImGuiKey_Menu or Shift + F10 to open context menus. Amends. (#8803, #9270) This doesn't attempt to move the shortcut polling in NavUpdate() yet. --- docs/CHANGELOG.txt | 4 +++ imgui.cpp | 74 +++++++++++++++++++++++++++++++--------------- imgui_demo.cpp | 1 + imgui_internal.h | 2 ++ 4 files changed, 57 insertions(+), 24 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index a88d8d0a7..161a4e6e7 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -90,6 +90,10 @@ Other Changes: - TextLink() underline thickness. - ColorButton() border thickness. - Separator() thickness, via scaling newly added style.SeparatorSize. (#2657, #9263) +- Nav: + - Popups: Shift+F10 or Menu key can now open popups menus when using + BeginPopupContextItem(), BeginPopupContextWindow() or OpenPopupOnItemClick(). + (#8803, #9270) [@exelix11, @ocornut] - Clipper: - Clear `DisplayStart`/`DisplayEnd` fields when `Step()` returns false. - Added `UserIndex` helper storage. This is solely a convenience for cases where diff --git a/imgui.cpp b/imgui.cpp index 845529829..422cd22bd 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -167,6 +167,7 @@ CODE - Home, End Scroll to top, scroll to bottom. - Alt Toggle between scrolling layer and menu layer. - Ctrl+Tab then Ctrl+Arrows Move window. Hold Shift to resize instead of moving. + - Menu or Shift+F10 Open context menu. - Output when ImGuiConfigFlags_NavEnableKeyboard set, - io.WantCaptureKeyboard flag is set when keyboard is claimed. - io.NavActive: true when a window is focused and it doesn't have the ImGuiWindowFlags_NoNavInputs flag set. @@ -12498,21 +12499,57 @@ ImGuiMouseButton ImGui::GetMouseButtonFromPopupFlags(ImGuiPopupFlags flags) return ImGuiMouseButton_Right; // Default == 1 } +bool ImGui::IsPopupOpenRequestForItem(ImGuiPopupFlags popup_flags, ImGuiID id) +{ + ImGuiContext& g = *GImGui; + ImGuiMouseButton mouse_button = GetMouseButtonFromPopupFlags(popup_flags); + if (IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + return true; + + if (g.NavId == id && g.NavId != 0) // == IsItemFocused() + if (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) + if (Shortcut(ImGuiKey_F10 | ImGuiMod_Shift) || IsKeyReleased(ImGuiKey_Menu, ImGuiKeyOwner_NoOwner)) + { + g.NavInputSource = ImGuiInputSource_Keyboard; + SetNavCursorVisibleAfterMove(); + return true; + } + + return false; +} + +bool ImGui::IsPopupOpenRequestForWindow(ImGuiPopupFlags popup_flags, ImGuiID id) +{ + ImGuiContext& g = *GImGui; + ImGuiMouseButton mouse_button = GetMouseButtonFromPopupFlags(popup_flags); + if (IsMouseReleased(mouse_button) && IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + if (!(popup_flags & ImGuiPopupFlags_NoOpenOverItems) || !IsAnyItemHovered()) + return true; + + // FIXME-NAV: How to meaningful support ImGuiPopupFlags_NoOpenOverItems with keyboard? + if (g.CurrentWindow->ID == id && g.NavWindow != NULL) + if (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) + if (IsWindowChildOf(g.NavWindow, g.CurrentWindow, false)) + if (Shortcut(ImGuiKey_F10 | ImGuiMod_Shift) || IsKeyReleased(ImGuiKey_Menu, ImGuiKeyOwner_NoOwner)) + { + g.NavInputSource = ImGuiInputSource_Keyboard; + SetNavCursorVisibleAfterMove(); + return true; + } + + return false; +} + // Helper to open a popup if mouse button is released over the item // - This is essentially the same as BeginPopupContextItem() but without the trailing BeginPopup() void ImGui::OpenPopupOnItemClick(const char* str_id, ImGuiPopupFlags popup_flags) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; - ImGuiMouseButton mouse_button = GetMouseButtonFromPopupFlags(popup_flags); - bool isMouseInvocation = IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup); - bool isKeyboardInvocation = false; - if ((g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) && IsItemFocused()) - isKeyboardInvocation = IsKeyReleased(ImGuiKey_Menu) || (IsKeyReleased(ImGuiKey_F10) && g.IO.KeyShift); - if (isKeyboardInvocation || isMouseInvocation) + if (IsPopupOpenRequestForItem(popup_flags, g.LastItemData.ID)) { - ImGuiID id = str_id ? window->GetID(str_id) : g.LastItemData.ID; // If user hasn't passed an ID, we can use the LastItemID. Using LastItemID as a Popup ID won't conflict! - IM_ASSERT(id != 0); // You cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item) + ImGuiID id = str_id ? window->GetID(str_id) : g.LastItemData.ID; // If user hasn't passed an ID, we can use the LastItemID. Using LastItemID as a Popup ID won't conflict! + IM_ASSERT(id != 0); // You cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item) OpenPopupEx(id, popup_flags); } } @@ -12539,14 +12576,9 @@ bool ImGui::BeginPopupContextItem(const char* str_id, ImGuiPopupFlags popup_flag ImGuiWindow* window = g.CurrentWindow; if (window->SkipItems) return false; - ImGuiID id = str_id ? window->GetID(str_id) : g.LastItemData.ID; // If user hasn't passed an ID, we can use the LastItemID. Using LastItemID as a Popup ID won't conflict! - IM_ASSERT(id != 0); // You cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item) - ImGuiMouseButton mouse_button = GetMouseButtonFromPopupFlags(popup_flags); - bool isMouseInvocation = IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup); - bool isKeyboardInvocation = false; - if ((g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) && IsItemFocused()) - isKeyboardInvocation = IsKeyReleased(ImGuiKey_Menu) || (IsKeyReleased(ImGuiKey_F10) && g.IO.KeyShift); - if (isKeyboardInvocation || isMouseInvocation) + ImGuiID id = str_id ? window->GetID(str_id) : g.LastItemData.ID; // If user hasn't passed an ID, we can use the LastItem ID. Using LastItem ID as a Popup ID won't conflict! + IM_ASSERT(id != 0); // You cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item) + if (IsPopupOpenRequestForItem(popup_flags, g.LastItemData.ID)) OpenPopupEx(id, popup_flags); return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings); } @@ -12558,14 +12590,8 @@ bool ImGui::BeginPopupContextWindow(const char* str_id, ImGuiPopupFlags popup_fl if (!str_id) str_id = "window_context"; ImGuiID id = window->GetID(str_id); - ImGuiMouseButton mouse_button = GetMouseButtonFromPopupFlags(popup_flags); - bool isMouseInvocation = IsMouseReleased(mouse_button) && IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup); - bool isKeyboardInvocation = false; - if ((g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) && IsWindowFocused()) - isKeyboardInvocation = IsKeyReleased(ImGuiKey_Menu) || (IsKeyReleased(ImGuiKey_F10) && g.IO.KeyShift); - if (isMouseInvocation || isKeyboardInvocation) - if (!(popup_flags & ImGuiPopupFlags_NoOpenOverItems) || !IsAnyItemHovered() || isKeyboardInvocation) - OpenPopupEx(id, popup_flags); + if (IsPopupOpenRequestForWindow(popup_flags, window->ID)) + OpenPopupEx(id, popup_flags); return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings); } diff --git a/imgui_demo.cpp b/imgui_demo.cpp index d7dc0b028..e8a43924c 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -8738,6 +8738,7 @@ void ImGui::ShowUserGuide() BulletText("Return to input text into a widget."); BulletText("Escape to deactivate a widget, close popup,\nexit a child window or the menu layer, clear focus."); BulletText("Alt to jump to the menu layer of a window."); + BulletText("Menu or Shift+F10 to open a context menu."); Unindent(); } diff --git a/imgui_internal.h b/imgui_internal.h index c54040747..370f3cc5d 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3331,6 +3331,8 @@ namespace ImGui IMGUI_API ImVec2 FindBestWindowPosForPopup(ImGuiWindow* window); IMGUI_API ImVec2 FindBestWindowPosForPopupEx(const ImVec2& ref_pos, const ImVec2& size, ImGuiDir* last_dir, const ImRect& r_outer, const ImRect& r_avoid, ImGuiPopupPositionPolicy policy); IMGUI_API ImGuiMouseButton GetMouseButtonFromPopupFlags(ImGuiPopupFlags flags); + IMGUI_API bool IsPopupOpenRequestForItem(ImGuiPopupFlags flags, ImGuiID id); + IMGUI_API bool IsPopupOpenRequestForWindow(ImGuiPopupFlags flags, ImGuiID id); // Tooltips IMGUI_API bool BeginTooltipEx(ImGuiTooltipFlags tooltip_flags, ImGuiWindowFlags extra_window_flags); From 14a500a4760aaa74bc5913bb9de6d13035e5bd0b Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 12 Mar 2026 18:49:57 +0100 Subject: [PATCH 07/14] Nav: allow ImGuiKey_Menu or Shift + F10 to open context menus. Rework with polling in NavUpdate(). (#8803, #9270) This might be a little less flexible but removes burden from the higher-frequency calls. --- imgui.cpp | 51 +++++++++++++++++++++++++----------------------- imgui_internal.h | 4 +++- 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 422cd22bd..5e4c1606b 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1358,6 +1358,7 @@ static void NavUpdateWindowing(); static void NavUpdateWindowingApplyFocus(ImGuiWindow* window); static void NavUpdateWindowingOverlay(); static void NavUpdateCancelRequest(); +static void NavUpdateContextMenuRequest(); static void NavUpdateCreateMoveRequest(); static void NavUpdateCreateTabbingRequest(); static float NavUpdatePageUpPageDown(); @@ -4215,6 +4216,7 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) NavWindow = NULL; NavFocusScopeId = NavActivateId = NavActivateDownId = NavActivatePressedId = 0; NavLayer = ImGuiNavLayer_Main; + NavOpenContextMenuItemId = NavOpenContextMenuWindowId = 0; NavNextActivateId = 0; NavActivateFlags = NavNextActivateFlags = ImGuiActivateFlags_None; NavHighlightActivatedId = 0; @@ -12505,38 +12507,21 @@ bool ImGui::IsPopupOpenRequestForItem(ImGuiPopupFlags popup_flags, ImGuiID id) ImGuiMouseButton mouse_button = GetMouseButtonFromPopupFlags(popup_flags); if (IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) return true; - - if (g.NavId == id && g.NavId != 0) // == IsItemFocused() - if (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) - if (Shortcut(ImGuiKey_F10 | ImGuiMod_Shift) || IsKeyReleased(ImGuiKey_Menu, ImGuiKeyOwner_NoOwner)) - { - g.NavInputSource = ImGuiInputSource_Keyboard; - SetNavCursorVisibleAfterMove(); - return true; - } - + if (g.NavOpenContextMenuItemId == id && IsItemFocused()) + return true; return false; } -bool ImGui::IsPopupOpenRequestForWindow(ImGuiPopupFlags popup_flags, ImGuiID id) +bool ImGui::IsPopupOpenRequestForWindow(ImGuiPopupFlags popup_flags) { ImGuiContext& g = *GImGui; ImGuiMouseButton mouse_button = GetMouseButtonFromPopupFlags(popup_flags); if (IsMouseReleased(mouse_button) && IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) if (!(popup_flags & ImGuiPopupFlags_NoOpenOverItems) || !IsAnyItemHovered()) return true; - - // FIXME-NAV: How to meaningful support ImGuiPopupFlags_NoOpenOverItems with keyboard? - if (g.CurrentWindow->ID == id && g.NavWindow != NULL) - if (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) - if (IsWindowChildOf(g.NavWindow, g.CurrentWindow, false)) - if (Shortcut(ImGuiKey_F10 | ImGuiMod_Shift) || IsKeyReleased(ImGuiKey_Menu, ImGuiKeyOwner_NoOwner)) - { - g.NavInputSource = ImGuiInputSource_Keyboard; - SetNavCursorVisibleAfterMove(); - return true; - } - + if (g.NavOpenContextMenuWindowId && g.CurrentWindow->ID) + if (IsWindowChildOf(g.NavWindow, g.CurrentWindow, false)) // This enable ordering to be used to disambiguate item vs window (#8803) + return true; return false; } @@ -12590,7 +12575,7 @@ bool ImGui::BeginPopupContextWindow(const char* str_id, ImGuiPopupFlags popup_fl if (!str_id) str_id = "window_context"; ImGuiID id = window->GetID(str_id); - if (IsPopupOpenRequestForWindow(popup_flags, window->ID)) + if (IsPopupOpenRequestForWindow(popup_flags)) OpenPopupEx(id, popup_flags); return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings); } @@ -13768,6 +13753,7 @@ static void ImGui::NavUpdate() // Process NavCancel input (to close a popup, get back to parent, clear focus) NavUpdateCancelRequest(); + NavUpdateContextMenuRequest(); // Process manual activation request g.NavActivateId = g.NavActivateDownId = g.NavActivatePressedId = 0; @@ -14252,6 +14238,23 @@ static void ImGui::NavUpdateCancelRequest() } } +static void ImGui::NavUpdateContextMenuRequest() +{ + ImGuiContext& g = *GImGui; + g.NavOpenContextMenuItemId = g.NavOpenContextMenuWindowId = 0; + const bool nav_keyboard_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0; + if (!nav_keyboard_active || g.NavWindow == NULL) + return; + + const bool request = IsKeyReleased(ImGuiKey_Menu, ImGuiKeyOwner_NoOwner) || (IsKeyPressed(ImGuiKey_F10, ImGuiInputFlags_None, ImGuiKeyOwner_NoOwner) && g.IO.KeyMods == ImGuiMod_Shift); + if (!request) + return; + g.NavOpenContextMenuItemId = g.NavId; + g.NavOpenContextMenuWindowId = g.NavWindow->ID; + g.NavInputSource = ImGuiInputSource_Keyboard; + SetNavCursorVisibleAfterMove(); +} + // Handle PageUp/PageDown/Home/End keys // Called from NavUpdateCreateMoveRequest() which will use our output to create a move request // FIXME-NAV: This doesn't work properly with NavFlattened siblings as we use NavWindow rectangle for reference diff --git a/imgui_internal.h b/imgui_internal.h index 370f3cc5d..cd1ae9664 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2332,6 +2332,8 @@ struct ImGuiContext ImVector NavFocusRoute; // Reversed copy focus scope stack for NavId (should contains NavFocusScopeId). This essentially follow the window->ParentWindowForFocusRoute chain. ImGuiID NavHighlightActivatedId; float NavHighlightActivatedTimer; + ImGuiID NavOpenContextMenuItemId; + ImGuiID NavOpenContextMenuWindowId; ImGuiID NavNextActivateId; // Set by ActivateItemByID(), queued until next frame. ImGuiActivateFlags NavNextActivateFlags; ImGuiInputSource NavInputSource; // Keyboard or Gamepad mode? THIS CAN ONLY BE ImGuiInputSource_Keyboard or ImGuiInputSource_Gamepad @@ -3332,7 +3334,7 @@ namespace ImGui IMGUI_API ImVec2 FindBestWindowPosForPopupEx(const ImVec2& ref_pos, const ImVec2& size, ImGuiDir* last_dir, const ImRect& r_outer, const ImRect& r_avoid, ImGuiPopupPositionPolicy policy); IMGUI_API ImGuiMouseButton GetMouseButtonFromPopupFlags(ImGuiPopupFlags flags); IMGUI_API bool IsPopupOpenRequestForItem(ImGuiPopupFlags flags, ImGuiID id); - IMGUI_API bool IsPopupOpenRequestForWindow(ImGuiPopupFlags flags, ImGuiID id); + IMGUI_API bool IsPopupOpenRequestForWindow(ImGuiPopupFlags flags); // Tooltips IMGUI_API bool BeginTooltipEx(ImGuiTooltipFlags tooltip_flags, ImGuiWindowFlags extra_window_flags); From 90743d3112a703309b9e256ee1d3a3ec90c14b2d Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 12 Mar 2026 19:12:47 +0100 Subject: [PATCH 08/14] Nav: allow ImGuiKey_Menu or Shift + F10 to work on Begin()...BeginPopupContextItem() sequence aiming at title bar. (#8803, #9270) --- imgui.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/imgui.cpp b/imgui.cpp index 5e4c1606b..fb7e09a56 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -12507,7 +12507,7 @@ bool ImGui::IsPopupOpenRequestForItem(ImGuiPopupFlags popup_flags, ImGuiID id) ImGuiMouseButton mouse_button = GetMouseButtonFromPopupFlags(popup_flags); if (IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) return true; - if (g.NavOpenContextMenuItemId == id && IsItemFocused()) + if (g.NavOpenContextMenuItemId == id && (IsItemFocused() || id == g.CurrentWindow->MoveId)) return true; return false; } @@ -14251,6 +14251,11 @@ static void ImGui::NavUpdateContextMenuRequest() return; g.NavOpenContextMenuItemId = g.NavId; g.NavOpenContextMenuWindowId = g.NavWindow->ID; + + // Allow triggering for Begin()..BeginPopupContextItem(). A possible alternative would be to use g.NavLayer == ImGuiNavLayer_Menu. + if (g.NavId == g.NavWindow->GetID("#CLOSE") || g.NavId == g.NavWindow->GetID("#COLLAPSE")) + g.NavOpenContextMenuItemId = g.NavWindow->MoveId; + g.NavInputSource = ImGuiInputSource_Keyboard; SetNavCursorVisibleAfterMove(); } From 6dbda97fee1ed0ae0bce5edd7e9b2e1ef61089eb Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 12 Mar 2026 19:27:50 +0100 Subject: [PATCH 09/14] Backends: OpenGL2, OpenGL3, SDLRenderer3: replaced erroneous IM_ASSERT(tex->TexID == 0) calls. (#9295, #9293) Amend/fix 0db5919 which revealed this. --- backends/imgui_impl_opengl2.cpp | 2 +- backends/imgui_impl_opengl3.cpp | 2 +- backends/imgui_impl_sdlrenderer3.cpp | 2 +- docs/CHANGELOG.txt | 4 ++++ imgui.cpp | 1 + 5 files changed, 8 insertions(+), 3 deletions(-) diff --git a/backends/imgui_impl_opengl2.cpp b/backends/imgui_impl_opengl2.cpp index af5618fb6..7b860098f 100644 --- a/backends/imgui_impl_opengl2.cpp +++ b/backends/imgui_impl_opengl2.cpp @@ -274,7 +274,7 @@ void ImGui_ImplOpenGL2_UpdateTexture(ImTextureData* tex) { // Create and upload new texture to graphics system //IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height); - IM_ASSERT(tex->TexID == 0 && tex->BackendUserData == nullptr); + IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr); IM_ASSERT(tex->Format == ImTextureFormat_RGBA32); const void* pixels = tex->GetPixels(); GLuint gl_texture_id = 0; diff --git a/backends/imgui_impl_opengl3.cpp b/backends/imgui_impl_opengl3.cpp index 6348b01b0..893e8b852 100644 --- a/backends/imgui_impl_opengl3.cpp +++ b/backends/imgui_impl_opengl3.cpp @@ -744,7 +744,7 @@ void ImGui_ImplOpenGL3_UpdateTexture(ImTextureData* tex) { // Create and upload new texture to graphics system //IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height); - IM_ASSERT(tex->TexID == 0 && tex->BackendUserData == nullptr); + IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr); IM_ASSERT(tex->Format == ImTextureFormat_RGBA32); const void* pixels = tex->GetPixels(); GLuint gl_texture_id = 0; diff --git a/backends/imgui_impl_sdlrenderer3.cpp b/backends/imgui_impl_sdlrenderer3.cpp index 38a4383c2..72bc174ee 100644 --- a/backends/imgui_impl_sdlrenderer3.cpp +++ b/backends/imgui_impl_sdlrenderer3.cpp @@ -254,7 +254,7 @@ void ImGui_ImplSDLRenderer3_UpdateTexture(ImTextureData* tex) { // Create and upload new texture to graphics system //IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height); - IM_ASSERT(tex->TexID == 0 && tex->BackendUserData == nullptr); + IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr); IM_ASSERT(tex->Format == ImTextureFormat_RGBA32); // Create texture diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 161a4e6e7..50734a026 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -47,6 +47,10 @@ Breaking Changes: indices or memory offsets inside ImTextureID, where 0 might be a valid value. - If this is causing problem with e.g your custom ImTextureID definition, you can add '#define ImTextureID_Invalid 0' to your imconfig.h + PLEASE report this to GitHub. + - If you have hardcoded e.g. 'if (tex_id == 0)' checks they should be updated. + e.g. OpenGL2, OpenGL3 and SDLRenderer3 backends incorrectly had 'IM_ASSERT(tex->TexID == 0)' + lines which were replaced with 'IM_ASSERT(tex->TexID == ImTextureID_Invalid)'. + If you have copied or forked backends consider fixing locally. (#9295) - Separator(): fixed a legacy quirk where Separator() was submitting a zero-height item for layout purpose, even though it draws a 1-pixel separator. The fix could affect code e.g. computing height from multiple widgets in order to diff --git a/imgui.cpp b/imgui.cpp index fb7e09a56..c043989ae 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -398,6 +398,7 @@ IMPLEMENTING SUPPORT for ImGuiBackendFlags_RendererHasTextures: - 2026/03/12 (1.92.7) - Changed default ImTextureID_Invalid to -1 instead of 0 if not #define-d. (#9293, #8745, #8465, #7090) It seems like a better default since it will work with backends storing indices or memory offsets inside ImTextureID, where 0 might be a valid value. If this is causing problem with e.g. your custom ImTextureID definition, you can add '#define ImTextureID_Invalid 0' to your imconfig.h + PLEASE report this to GitHub. + If you have hard-coded e.g. 'if (tex_id == 0)' checks they should be updated. e.g. OpenGL2, OpenGL3 and SDLRenderer3 backends incorrectly had 'IM_ASSERT(tex->TexID == 0)' lines which were replaced with 'IM_ASSERT(tex->TexID == ImTextureID_Invalid)'. (#9295) - 2026/02/26 (1.92.7) - Separator: fixed a legacy quirk where Separator() was submitting a zero-height item for layout purpose, even though it draws a 1-pixel separator. The fix could affect code e.g. computing height from multiple widgets in order to allocate vertical space for a footer or multi-line status bar. (#2657, #9263) The "Console" example had such a bug: From b76ab6232d426c646149fd2fa9c8f3aca0a4b5f7 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 13 Mar 2026 14:48:10 +0100 Subject: [PATCH 10/14] Nav: changed Gamepad mapping for "Activate with Text Input" action from FaceUp press to FaceDown long press. (#8803, #787) --- docs/CHANGELOG.txt | 5 +++++ imgui.cpp | 18 +++++++++++++----- imgui.h | 4 ++-- imgui_internal.h | 3 ++- imgui_widgets.cpp | 2 +- 5 files changed, 23 insertions(+), 9 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 50734a026..df0f2bf5e 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -95,6 +95,11 @@ Other Changes: - ColorButton() border thickness. - Separator() thickness, via scaling newly added style.SeparatorSize. (#2657, #9263) - Nav: + - Changed Gamepad mapping for "Activate with Text Input" action: (#8803, #787) + - Previously: press North button (PS4/PS5 triangle, Switch X, Xbox Y). + - Now: long press (hold) Activate button (PS4/PS5 cross, Switch B, Xbox A) for ~0.60 secs. + This is rarely used, somehow easier to discover, and frees a button for other uses. + See updated Gamepad Control Sheets: https://www.dearimgui.com/controls_sheets - Popups: Shift+F10 or Menu key can now open popups menus when using BeginPopupContextItem(), BeginPopupContextWindow() or OpenPopupOnItemClick(). (#8803, #9270) [@exelix11, @ocornut] diff --git a/imgui.cpp b/imgui.cpp index c043989ae..61a6456bc 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1313,6 +1313,7 @@ static const float FONT_DEFAULT_SIZE_BASE = 20.0f; static const float NAV_WINDOWING_HIGHLIGHT_DELAY = 0.20f; // Time before the highlight and screen dimming starts fading in static const float NAV_WINDOWING_LIST_APPEAR_DELAY = 0.15f; // Time before the window list starts to appear static const float NAV_ACTIVATE_HIGHLIGHT_TIMER = 0.10f; // Time to highlight an item activated by a shortcut. +static const float NAV_ACTIVATE_INPUT_WITH_GAMEPAD_DELAY = 0.60f; // Time to hold activation button (e.g. FaceDown) to turn the activation into a text input. static const float WINDOWS_RESIZE_FROM_EDGES_FEEDBACK_TIMER = 0.04f; // Reduce visual noise by only highlighting the border after a certain time. static const float WINDOWS_MOUSE_WHEEL_SCROLL_LOCK_TIMER = 0.70f; // Lock scrolled window (so it doesn't pick child windows that are scrolling through) for a certain time, unless mouse moved. @@ -4217,6 +4218,7 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) NavWindow = NULL; NavFocusScopeId = NavActivateId = NavActivateDownId = NavActivatePressedId = 0; NavLayer = ImGuiNavLayer_Main; + NavIdItemFlags = ImGuiItemFlags_None; NavOpenContextMenuItemId = NavOpenContextMenuWindowId = 0; NavNextActivateId = 0; NavActivateFlags = NavNextActivateFlags = ImGuiActivateFlags_None; @@ -13101,6 +13103,7 @@ void ImGui::SetFocusID(ImGuiID id, ImGuiWindow* window) window->NavLastIds[nav_layer] = id; if (g.LastItemData.ID == id) window->NavRectRel[nav_layer] = WindowRectAbsToRel(window, g.LastItemData.NavRect); + g.NavIdItemFlags = (g.LastItemData.ID == id) ? g.LastItemData.ItemFlags : ImGuiItemFlags_None; if (id == g.ActiveIdIsAlive) g.NavIdIsAlive = true; @@ -13370,6 +13373,7 @@ static void ImGui::NavProcessItem() SetNavFocusScope(g.CurrentFocusScopeId); // Will set g.NavFocusScopeId AND store g.NavFocusScopePath g.NavFocusScopeId = g.CurrentFocusScopeId; g.NavIdIsAlive = true; + g.NavIdItemFlags = item_flags; if (g.LastItemData.ItemFlags & ImGuiItemFlags_HasSelectionUserData) { IM_ASSERT(g.NextItemData.SelectionUserData != ImGuiSelectionUserData_Invalid); @@ -13763,21 +13767,25 @@ static void ImGui::NavUpdate() { const bool activate_down = (nav_keyboard_active && IsKeyDown(ImGuiKey_Space, ImGuiKeyOwner_NoOwner)) || (nav_gamepad_active && IsKeyDown(ImGuiKey_NavGamepadActivate, ImGuiKeyOwner_NoOwner)); const bool activate_pressed = activate_down && ((nav_keyboard_active && IsKeyPressed(ImGuiKey_Space, 0, ImGuiKeyOwner_NoOwner)) || (nav_gamepad_active && IsKeyPressed(ImGuiKey_NavGamepadActivate, 0, ImGuiKeyOwner_NoOwner))); - const bool input_down = (nav_keyboard_active && (IsKeyDown(ImGuiKey_Enter, ImGuiKeyOwner_NoOwner) || IsKeyDown(ImGuiKey_KeypadEnter, ImGuiKeyOwner_NoOwner))) || (nav_gamepad_active && IsKeyDown(ImGuiKey_NavGamepadInput, ImGuiKeyOwner_NoOwner)); - const bool input_pressed = input_down && ((nav_keyboard_active && (IsKeyPressed(ImGuiKey_Enter, 0, ImGuiKeyOwner_NoOwner) || IsKeyPressed(ImGuiKey_KeypadEnter, 0, ImGuiKeyOwner_NoOwner))) || (nav_gamepad_active && IsKeyPressed(ImGuiKey_NavGamepadInput, 0, ImGuiKeyOwner_NoOwner))); + const bool input_pressed_keyboard = nav_keyboard_active && (IsKeyPressed(ImGuiKey_Enter, 0, ImGuiKeyOwner_NoOwner) || IsKeyPressed(ImGuiKey_KeypadEnter, 0, ImGuiKeyOwner_NoOwner)); + bool input_pressed_gamepad = false; + if (activate_down && nav_gamepad_active && IsKeyDown(ImGuiKey_NavGamepadActivate, ImGuiKeyOwner_NoOwner) && (g.NavIdItemFlags & ImGuiItemFlags_Inputable)) // requires ImGuiItemFlags_Inputable to avoid retriggering regular buttons. + if (GetKeyData(ImGuiKey_NavGamepadActivate)->DownDurationPrev < NAV_ACTIVATE_INPUT_WITH_GAMEPAD_DELAY && GetKeyData(ImGuiKey_NavGamepadActivate)->DownDuration >= NAV_ACTIVATE_INPUT_WITH_GAMEPAD_DELAY) + input_pressed_gamepad = true; + if (g.ActiveId == 0 && activate_pressed) { g.NavActivateId = g.NavId; g.NavActivateFlags = ImGuiActivateFlags_PreferTweak; } - if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && input_pressed) + if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && (input_pressed_keyboard || input_pressed_gamepad)) { g.NavActivateId = g.NavId; g.NavActivateFlags = ImGuiActivateFlags_PreferInput; } - if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && (activate_down || input_down)) + if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && (activate_down || input_pressed_keyboard || input_pressed_gamepad)) // FIXME-NAV: Unsure why input_pressed_xxx (migrated from input_down which was already dubious) g.NavActivateDownId = g.NavId; - if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && (activate_pressed || input_pressed)) + if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && (activate_pressed || input_pressed_keyboard || input_pressed_gamepad)) { g.NavActivatePressedId = g.NavId; NavHighlightActivated(g.NavId); diff --git a/imgui.h b/imgui.h index a41b91b52..349c31040 100644 --- a/imgui.h +++ b/imgui.h @@ -1620,8 +1620,8 @@ enum ImGuiKey : int ImGuiKey_GamepadBack, // View | - | Share | ImGuiKey_GamepadFaceLeft, // X | Y | Square | Tap: Toggle Menu. Hold: Windowing mode (Focus/Move/Resize windows) ImGuiKey_GamepadFaceRight, // B | A | Circle | Cancel / Close / Exit - ImGuiKey_GamepadFaceUp, // Y | X | Triangle | Text Input / On-screen Keyboard - ImGuiKey_GamepadFaceDown, // A | B | Cross | Activate / Open / Toggle / Tweak + ImGuiKey_GamepadFaceUp, // Y | X | Triangle | + ImGuiKey_GamepadFaceDown, // A | B | Cross | Activate / Open / Toggle / Tweak. Hold for 0.60f to Activate in Text Input mode (e.g. wired to an on-screen keyboard). ImGuiKey_GamepadDpadLeft, // D-pad Left | " | " | Move / Tweak / Resize Window (in Windowing mode) ImGuiKey_GamepadDpadRight, // D-pad Right | " | " | Move / Tweak / Resize Window (in Windowing mode) ImGuiKey_GamepadDpadUp, // D-pad Up | " | " | Move / Tweak / Resize Window (in Windowing mode) diff --git a/imgui_internal.h b/imgui_internal.h index cd1ae9664..e9eded337 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1509,7 +1509,7 @@ typedef ImBitArray ImBitAr #define ImGuiKey_NavGamepadActivate (g.IO.ConfigNavSwapGamepadButtons ? ImGuiKey_GamepadFaceRight : ImGuiKey_GamepadFaceDown) #define ImGuiKey_NavGamepadCancel (g.IO.ConfigNavSwapGamepadButtons ? ImGuiKey_GamepadFaceDown : ImGuiKey_GamepadFaceRight) #define ImGuiKey_NavGamepadMenu ImGuiKey_GamepadFaceLeft -#define ImGuiKey_NavGamepadInput ImGuiKey_GamepadFaceUp +//#define ImGuiKey_NavGamepadInput ImGuiKey_GamepadFaceUp enum ImGuiInputEventType { @@ -2325,6 +2325,7 @@ struct ImGuiContext ImGuiWindow* NavWindow; // Focused window for navigation. Could be called 'FocusedWindow' ImGuiID NavFocusScopeId; // Focused focus scope (e.g. selection code often wants to "clear other items" when landing on an item of the same scope) ImGuiNavLayer NavLayer; // Focused layer (main scrolling layer, or menu/title bar layer) + ImGuiItemFlags NavIdItemFlags; ImGuiID NavActivateId; // ~~ (g.ActiveId == 0) && (IsKeyPressed(ImGuiKey_Space) || IsKeyDown(ImGuiKey_Enter) || IsKeyPressed(ImGuiKey_NavGamepadActivate)) ? NavId : 0, also set when calling ActivateItemByID() ImGuiID NavActivateDownId; // ~~ IsKeyDown(ImGuiKey_Space) || IsKeyDown(ImGuiKey_Enter) || IsKeyDown(ImGuiKey_NavGamepadActivate) ? NavId : 0 ImGuiID NavActivatePressedId; // ~~ IsKeyPressed(ImGuiKey_Space) || IsKeyPressed(ImGuiKey_Enter) || IsKeyPressed(ImGuiKey_NavGamepadActivate) ? NavId : 0 (no repeat) diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 78fae523b..6552dc975 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -5068,7 +5068,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ const bool is_enter = Shortcut(ImGuiKey_Enter, f_repeat, id) || Shortcut(ImGuiKey_KeypadEnter, f_repeat, id); const bool is_ctrl_enter = Shortcut(ImGuiMod_Ctrl | ImGuiKey_Enter, f_repeat, id) || Shortcut(ImGuiMod_Ctrl | ImGuiKey_KeypadEnter, f_repeat, id); const bool is_shift_enter = Shortcut(ImGuiMod_Shift | ImGuiKey_Enter, f_repeat, id) || Shortcut(ImGuiMod_Shift | ImGuiKey_KeypadEnter, f_repeat, id); - const bool is_gamepad_validate = nav_gamepad_active && (IsKeyPressed(ImGuiKey_NavGamepadActivate, false) || IsKeyPressed(ImGuiKey_NavGamepadInput, false)); + const bool is_gamepad_validate = nav_gamepad_active && IsKeyPressed(ImGuiKey_NavGamepadActivate, false); const bool is_cancel = Shortcut(ImGuiKey_Escape, f_repeat, id) || (nav_gamepad_active && Shortcut(ImGuiKey_NavGamepadCancel, f_repeat, id)); // FIXME: Should use more Shortcut() and reduce IsKeyPressed()+SetKeyOwner(), but requires modifiers combination to be taken account of. From 49ee151ed6ce9b8043542ae5b1e2472e5bfa4bad Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 13 Mar 2026 15:01:10 +0100 Subject: [PATCH 11/14] Nav: pressing gamepad north button activates context menus. + update ShowUserGuide(). --- docs/CHANGELOG.txt | 1 + imgui.cpp | 7 +++++-- imgui.h | 6 +++--- imgui_demo.cpp | 11 ++++++++++- imgui_internal.h | 4 ++-- 5 files changed, 21 insertions(+), 8 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index df0f2bf5e..acc3965ab 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -103,6 +103,7 @@ Other Changes: - Popups: Shift+F10 or Menu key can now open popups menus when using BeginPopupContextItem(), BeginPopupContextWindow() or OpenPopupOnItemClick(). (#8803, #9270) [@exelix11, @ocornut] + - Popups: pressing North button (PS4/PS5 triangle, SwitchX, Xbox Y) also open popups menus. - Clipper: - Clear `DisplayStart`/`DisplayEnd` fields when `Step()` returns false. - Added `UserIndex` helper storage. This is solely a convenience for cases where diff --git a/imgui.cpp b/imgui.cpp index 61a6456bc..b7c5d1b22 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -14252,10 +14252,13 @@ static void ImGui::NavUpdateContextMenuRequest() ImGuiContext& g = *GImGui; g.NavOpenContextMenuItemId = g.NavOpenContextMenuWindowId = 0; const bool nav_keyboard_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0; - if (!nav_keyboard_active || g.NavWindow == NULL) + const bool nav_gamepad_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0; + if ((!nav_keyboard_active && !nav_gamepad_active) || g.NavWindow == NULL) return; - const bool request = IsKeyReleased(ImGuiKey_Menu, ImGuiKeyOwner_NoOwner) || (IsKeyPressed(ImGuiKey_F10, ImGuiInputFlags_None, ImGuiKeyOwner_NoOwner) && g.IO.KeyMods == ImGuiMod_Shift); + bool request = false; + request |= nav_keyboard_active && (IsKeyReleased(ImGuiKey_Menu, ImGuiKeyOwner_NoOwner) || (IsKeyPressed(ImGuiKey_F10, ImGuiInputFlags_None, ImGuiKeyOwner_NoOwner) && g.IO.KeyMods == ImGuiMod_Shift)); + request |= nav_gamepad_active && IsKeyPressed(ImGuiKey_NavGamepadContextMenu, ImGuiInputFlags_None, ImGuiKeyOwner_NoOwner); if (!request) return; g.NavOpenContextMenuItemId = g.NavId; diff --git a/imgui.h b/imgui.h index 349c31040..b69a9b563 100644 --- a/imgui.h +++ b/imgui.h @@ -1618,10 +1618,10 @@ enum ImGuiKey : int // // XBOX | SWITCH | PLAYSTA. | -> ACTION ImGuiKey_GamepadStart, // Menu | + | Options | ImGuiKey_GamepadBack, // View | - | Share | - ImGuiKey_GamepadFaceLeft, // X | Y | Square | Tap: Toggle Menu. Hold: Windowing mode (Focus/Move/Resize windows) + ImGuiKey_GamepadFaceLeft, // X | Y | Square | Toggle Menu. Hold for Windowing mode (Focus/Move/Resize windows) ImGuiKey_GamepadFaceRight, // B | A | Circle | Cancel / Close / Exit - ImGuiKey_GamepadFaceUp, // Y | X | Triangle | - ImGuiKey_GamepadFaceDown, // A | B | Cross | Activate / Open / Toggle / Tweak. Hold for 0.60f to Activate in Text Input mode (e.g. wired to an on-screen keyboard). + ImGuiKey_GamepadFaceUp, // Y | X | Triangle | Open Context Menu + ImGuiKey_GamepadFaceDown, // A | B | Cross | Activate / Open / Toggle. Hold for 0.60f to Activate in Text Input mode (e.g. wired to an on-screen keyboard). ImGuiKey_GamepadDpadLeft, // D-pad Left | " | " | Move / Tweak / Resize Window (in Windowing mode) ImGuiKey_GamepadDpadRight, // D-pad Right | " | " | Move / Tweak / Resize Window (in Windowing mode) ImGuiKey_GamepadDpadUp, // D-pad Up | " | " | Move / Tweak / Resize Window (in Windowing mode) diff --git a/imgui_demo.cpp b/imgui_demo.cpp index e8a43924c..738096c57 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -8731,7 +8731,7 @@ void ImGui::ShowUserGuide() BulletText("Ctrl+Z to undo, Ctrl+Y/Ctrl+Shift+Z to redo."); BulletText("Escape to revert."); Unindent(); - BulletText("With keyboard navigation enabled:"); + BulletText("With Keyboard controls enabled:"); Indent(); BulletText("Arrow keys or Home/End/PageUp/PageDown to navigate."); BulletText("Space to activate a widget."); @@ -8740,6 +8740,15 @@ void ImGui::ShowUserGuide() BulletText("Alt to jump to the menu layer of a window."); BulletText("Menu or Shift+F10 to open a context menu."); Unindent(); + BulletText("With Gamepad controls enabled:"); + Indent(); + BulletText("D-Pad: Navigate / Tweak / Resize (in Windowing mode)."); + BulletText("%s Face button: Activate / Open / Toggle. Hold: activate with text input.", io.ConfigNavSwapGamepadButtons ? "East" : "South"); + BulletText("%s Face button: Cancel / Close / Exit.", io.ConfigNavSwapGamepadButtons ? "South" : "East"); + BulletText("West Face button: Toggle Menu. Hold for Windowing mode (Focus/Move/Resize windows)."); + BulletText("North Face button: Open Context Menu."); + BulletText("L1/R1: Tweak Slower/Faster, Focus Previous/Next (in Windowing Mode)."); + Unindent(); } //----------------------------------------------------------------------------- diff --git a/imgui_internal.h b/imgui_internal.h index e9eded337..cb5e4e1c0 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1508,8 +1508,8 @@ typedef ImBitArray ImBitAr #define ImGuiKey_NavGamepadTweakFast ImGuiKey_GamepadR1 #define ImGuiKey_NavGamepadActivate (g.IO.ConfigNavSwapGamepadButtons ? ImGuiKey_GamepadFaceRight : ImGuiKey_GamepadFaceDown) #define ImGuiKey_NavGamepadCancel (g.IO.ConfigNavSwapGamepadButtons ? ImGuiKey_GamepadFaceDown : ImGuiKey_GamepadFaceRight) -#define ImGuiKey_NavGamepadMenu ImGuiKey_GamepadFaceLeft -//#define ImGuiKey_NavGamepadInput ImGuiKey_GamepadFaceUp +#define ImGuiKey_NavGamepadMenu ImGuiKey_GamepadFaceLeft // Toggle menu layer. Hold to enable Windowing. +#define ImGuiKey_NavGamepadContextMenu ImGuiKey_GamepadFaceUp // Open context menu (same as Shift+F10) enum ImGuiInputEventType { From d02c645e3847f3b3d44dfe99ed4dfc526e0e4291 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 13 Mar 2026 15:07:26 +0100 Subject: [PATCH 12/14] Nav: short Gamepad Activation press on InputText() always activate with Text Input mode. --- docs/CHANGELOG.txt | 1 + imgui_widgets.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index acc3965ab..0cff41294 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -100,6 +100,7 @@ Other Changes: - Now: long press (hold) Activate button (PS4/PS5 cross, Switch B, Xbox A) for ~0.60 secs. This is rarely used, somehow easier to discover, and frees a button for other uses. See updated Gamepad Control Sheets: https://www.dearimgui.com/controls_sheets + - Short Gamepad Activation press on InputText() always activate with Text Input mode. - Popups: Shift+F10 or Menu key can now open popups menus when using BeginPopupContextItem(), BeginPopupContextWindow() or OpenPopupOnItemClick(). (#8803, #9270) [@exelix11, @ocornut] diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 6552dc975..7395fad64 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -4760,7 +4760,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (is_wordwrap) wrap_width = ImMax(1.0f, GetContentRegionAvail().x + (draw_window->ScrollbarY ? 0.0f : -g.Style.ScrollbarSize)); - const bool input_requested_by_nav = (g.ActiveId != id) && ((g.NavActivateId == id) && ((g.NavActivateFlags & ImGuiActivateFlags_PreferInput) || (g.NavInputSource == ImGuiInputSource_Keyboard))); + const bool input_requested_by_nav = (g.ActiveId != id) && (g.NavActivateId == id); const bool input_requested_by_reactivate = (g.InputTextReactivateId == id); // for io.ConfigInputTextEnterKeepActive const bool user_clicked = hovered && io.MouseClicked[0]; const bool user_scroll_finish = is_multiline && state != NULL && g.ActiveId == 0 && g.ActiveIdPreviousFrame == GetWindowScrollbarID(draw_window, ImGuiAxis_Y); From fd752d8357d1f928265b275a293a6bc1adcfe6f7 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 13 Mar 2026 16:32:39 +0100 Subject: [PATCH 13/14] InputText: Fixed a glitch when using ImGuiInputTextFlags_ElideLeft where the local x offset would be incorrect during the deactivation frame. (#9298) --- docs/CHANGELOG.txt | 2 ++ imgui_widgets.cpp | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 0cff41294..255d809b4 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -85,6 +85,8 @@ Other Changes: - Reworked io.ConfigInputTextEnterKeepActive mode so that pressing Enter will deactivate/reactivate the item in order for e.g. IsItemDeactivatedAfterEdit() signals to be emitted the same way regardless of that setting. (#9001, #9115) + - Fixed a glitch when using ImGuiInputTextFlags_ElideLeft where the local x offset + would be incorrect during the deactivation frame. (#9298) - Style: - Border sizes are now scaled (and rounded) by ScaleAllSizes(). - When using large values with ScallAllSizes(), the following items thickness diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 7395fad64..1bc875f63 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -5568,7 +5568,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ } // Find render position for right alignment (single-line only) - if (g.ActiveId != id && flags & ImGuiInputTextFlags_ElideLeft) + if (g.ActiveId != id && (flags & ImGuiInputTextFlags_ElideLeft) && !render_cursor && !render_selection) draw_pos.x = ImMin(draw_pos.x, frame_bb.Max.x - CalcTextSize(buf_display, NULL).x - style.FramePadding.x); //draw_scroll.x = state->Scroll.x; // Preserve scroll when inactive? From 4a2e3cce69b2ef087949ae297d1a36a5f98a1058 Mon Sep 17 00:00:00 2001 From: Yan Pujante Date: Fri, 13 Mar 2026 08:58:12 -0700 Subject: [PATCH 14/14] Examples: SDL2+WebGPU: fixes hi-dpi handling. (#9300) --- docs/CHANGELOG.txt | 1 + examples/example_sdl2_wgpu/main.cpp | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 255d809b4..44c6175a1 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -136,6 +136,7 @@ Other Changes: to allocate one. (#8677) [@micb25] - WebGPU: fixed undefined behaviors in example code for requesting adapter and device. (#9246, #9256) [@r-lyeh] + - SDL2+WebGPU: fixed hi-dpi handling. (#9300) [@ypujante] - GLFW/SDL2/SDL3+WebGPU: removed suport for Emscripten <4.0.10. (#9281) [@ypujante] diff --git a/examples/example_sdl2_wgpu/main.cpp b/examples/example_sdl2_wgpu/main.cpp index b4c1be965..8d7c5a107 100644 --- a/examples/example_sdl2_wgpu/main.cpp +++ b/examples/example_sdl2_wgpu/main.cpp @@ -53,7 +53,7 @@ int main(int, char**) // Create window with graphics context float main_scale = ImGui_ImplSDL2_GetContentScaleForDisplay(0); - SDL_WindowFlags window_flags = SDL_WINDOW_RESIZABLE; + SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); SDL_Window* window = SDL_CreateWindow("Dear ImGui SDL2+WebGPU example", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, wgpu_surface_width, wgpu_surface_height, window_flags); if (window == nullptr) { @@ -143,7 +143,7 @@ int main(int, char**) // React to changes in screen size int width, height; - SDL_GetWindowSize(window, &width, &height); + SDL_GetWindowSizeInPixels(window, &width, &height); if (width != wgpu_surface_width || height != wgpu_surface_height) ResizeSurface(width, height);