From 1cc0eb4d3221a9bf404335836eaf06dec848a9f2 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 16 Jan 2024 12:32:00 +0100 Subject: [PATCH 01/13] Internals: Rename NavFocusScopePath to NavFocusRoute + fixed a static analyzer warning. --- imgui.cpp | 21 +++++++++++---------- imgui_internal.h | 3 ++- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index e75b7d957..958db4902 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -7824,7 +7824,7 @@ void ImGui::SetNavFocusScope(ImGuiID focus_scope_id) { ImGuiContext& g = *GImGui; g.NavFocusScopeId = focus_scope_id; - g.NavFocusScopePath.resize(0); // Invalidate + g.NavFocusRoute.resize(0); // Invalidate if (focus_scope_id == 0) return; IM_ASSERT(g.NavWindow != NULL); @@ -7834,17 +7834,17 @@ void ImGui::SetNavFocusScope(ImGuiID focus_scope_id) { // Top of focus stack contains local focus scopes inside current window for (int n = g.FocusScopeStack.Size - 1; n >= 0 && g.FocusScopeStack.Data[n].WindowID == g.CurrentWindow->ID; n--) - g.NavFocusScopePath.push_back(g.FocusScopeStack.Data[n]); + g.NavFocusRoute.push_back(g.FocusScopeStack.Data[n]); } else if (focus_scope_id == g.NavWindow->NavRootFocusScopeId) - g.NavFocusScopePath.push_back({ focus_scope_id, g.NavWindow->ID }); + g.NavFocusRoute.push_back({ focus_scope_id, g.NavWindow->ID }); else return; // Then follow on manually set ParentWindowForFocusRoute field (#6798) for (ImGuiWindow* window = g.NavWindow->ParentWindowForFocusRoute; window != NULL; window = window->ParentWindowForFocusRoute) - g.NavFocusScopePath.push_back({ window->NavRootFocusScopeId, window->ID }); - IM_ASSERT(g.NavFocusScopePath.Size < 100); // Maximum depth is technically 251 as per CalcRoutingScore(): 254 - 3 + g.NavFocusRoute.push_back({ window->NavRootFocusScopeId, window->ID }); + IM_ASSERT(g.NavFocusRoute.Size < 100); // Maximum depth is technically 251 as per CalcRoutingScore(): 254 - 3 } // Focus = move navigation cursor, set scrolling, set focus window. @@ -8360,10 +8360,11 @@ static int CalcRoutingScore(ImGuiID focus_scope_id, ImGuiID owner_id, ImGuiInput // - When Window/ChildB is focused -> Window scores 4, Window/ChildB scores 3 (best) // Assuming only WindowA is submitting a routing request, // - When Window/ChildB is focused -> Window scores 4 (best), Window/ChildB doesn't have a score. + // This essentially follow the window->ParentWindowForFocusRoute chain. if (focus_scope_id == 0) return 255; - for (int index_in_focus_path = 0; index_in_focus_path < g.NavFocusScopePath.Size; index_in_focus_path++) - if (g.NavFocusScopePath.Data[index_in_focus_path].ID == focus_scope_id) + for (int index_in_focus_path = 0; index_in_focus_path < g.NavFocusRoute.Size; index_in_focus_path++) + if (g.NavFocusRoute.Data[index_in_focus_path].ID == focus_scope_id) return 3 + index_in_focus_path; return 255; @@ -14531,10 +14532,10 @@ void ImGui::ShowMetricsWindow(bool* p_open) Text("NavActivateFlags: %04X", g.NavActivateFlags); Text("NavDisableHighlight: %d, NavDisableMouseHover: %d", g.NavDisableHighlight, g.NavDisableMouseHover); Text("NavFocusScopeId = 0x%08X", g.NavFocusScopeId); - Text("NavFocusScopePath[] = "); - for (int path_n = g.NavFocusScopePath.Size - 1; path_n >= 0; path_n--) + Text("NavFocusRoute[] = "); + for (int path_n = g.NavFocusRoute.Size - 1; path_n >= 0; path_n--) { - const ImGuiFocusScopeData& focus_scope = g.NavFocusScopePath[path_n]; + const ImGuiFocusScopeData& focus_scope = g.NavFocusRoute[path_n]; SameLine(0.0f, 0.0f); Text("0x%08X/", focus_scope.ID); SetItemTooltip("In window \"%s\"", FindWindowByID(focus_scope.WindowID)->Name); diff --git a/imgui_internal.h b/imgui_internal.h index 2d4d4c634..94272e971 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2003,7 +2003,7 @@ struct ImGuiContext ImGuiWindow* NavWindow; // Focused window for navigation. Could be called 'FocusedWindow' ImGuiID NavId; // Focused item for navigation ImGuiID NavFocusScopeId; // Focused focus scope (e.g. selection code often wants to "clear other items" when landing on an item of the same scope) - ImVector NavFocusScopePath; // Reversed copy focus scope stack for NavId (should contains NavFocusScopeId) + ImVector NavFocusRoute; // Reversed copy focus scope stack for NavId (should contains NavFocusScopeId). This essentially follow the window->ParentWindowForFocusRoute chain. ImGuiID NavActivateId; // ~~ (g.ActiveId == 0) && (IsKeyPressed(ImGuiKey_Space) || IsKeyDown(ImGuiKey_Enter) || IsKeyPressed(ImGuiKey_NavGamepadActivate)) ? NavId : 0, also set when calling ActivateItem() 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) @@ -2387,6 +2387,7 @@ struct ImGuiContext FramerateSecPerFrameIdx = FramerateSecPerFrameCount = 0; FramerateSecPerFrameAccum = 0.0f; WantCaptureMouseNextFrame = WantCaptureKeyboardNextFrame = WantTextInputNextFrame = -1; + memset(TempKeychordName, 0, sizeof(TempKeychordName)); } }; From 80d5cb1ab1fd0cf68ffced2fd67c998e350947d2 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 16 Jan 2024 16:25:02 +0100 Subject: [PATCH 02/13] Comments around ImGuiInputFlags. --- imgui.cpp | 1 + imgui_internal.h | 46 ++++++++++++++++++++++++++++------------------ 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 958db4902..37182fc72 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -8400,6 +8400,7 @@ bool ImGui::SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiI if (flags & ImGuiInputFlags_RouteUnlessBgFocused) if (g.NavWindow == NULL) return false; + // Note how ImGuiInputFlags_RouteAlways won't set routing and thus won't set owner. May want to rework this? if (flags & ImGuiInputFlags_RouteAlways) { diff --git a/imgui_internal.h b/imgui_internal.h index 94272e971..cf7612e58 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1403,17 +1403,19 @@ struct ImGuiKeyOwnerData }; // Flags for extended versions of IsKeyPressed(), IsMouseClicked(), Shortcut(), SetKeyOwner(), SetItemKeyOwner() -// Don't mistake with ImGuiInputTextFlags! (for ImGui::InputText() function) +// Don't mistake with ImGuiInputTextFlags! (which is for ImGui::InputText() function) enum ImGuiInputFlags_ { // Flags for IsKeyPressed(), IsKeyChordPressed(), IsMouseClicked(), Shortcut() ImGuiInputFlags_None = 0, - ImGuiInputFlags_Repeat = 1 << 0, // Return true on successive repeats. Default for legacy IsKeyPressed(). NOT Default for legacy IsMouseClicked(). MUST BE == 1. + + // Repeat mode + ImGuiInputFlags_Repeat = 1 << 0, // Enable repeat. Return true on successive repeats. Default for legacy IsKeyPressed(). NOT Default for legacy IsMouseClicked(). MUST BE == 1. ImGuiInputFlags_RepeatRateDefault = 1 << 1, // Repeat rate: Regular (default) ImGuiInputFlags_RepeatRateNavMove = 1 << 2, // Repeat rate: Fast ImGuiInputFlags_RepeatRateNavTweak = 1 << 3, // Repeat rate: Faster - // Specify when repeating key pressed can be interrupted. + // Repeat mode: Specify when repeating key pressed can be interrupted. // In theory ImGuiInputFlags_RepeatUntilOtherKeyPress may be a desirable default, but it would break too many behavior so everything is opt-in. ImGuiInputFlags_RepeatUntilRelease = 1 << 4, // Stop repeating when released (default for all functions except Shortcut). This only exists to allow overriding Shortcut() default behavior. ImGuiInputFlags_RepeatUntilKeyModsChange = 1 << 5, // Stop repeating when released OR if keyboard mods are changed (default for Shortcut) @@ -1424,38 +1426,46 @@ enum ImGuiInputFlags_ ImGuiInputFlags_CondHovered = 1 << 8, // Only set if item is hovered (default to both) ImGuiInputFlags_CondActive = 1 << 9, // Only set if item is active (default to both) ImGuiInputFlags_CondDefault_ = ImGuiInputFlags_CondHovered | ImGuiInputFlags_CondActive, - ImGuiInputFlags_CondMask_ = ImGuiInputFlags_CondHovered | ImGuiInputFlags_CondActive, // Flags for SetKeyOwner(), SetItemKeyOwner() - ImGuiInputFlags_LockThisFrame = 1 << 10, // Access to key data will require EXPLICIT owner ID (ImGuiKeyOwner_Any/0 will NOT accepted for polling). Cleared at end of frame. This is useful to make input-owner-aware code steal keys from non-input-owner-aware code. - ImGuiInputFlags_LockUntilRelease = 1 << 11, // Access to key data will require EXPLICIT owner ID (ImGuiKeyOwner_Any/0 will NOT accepted for polling). Cleared when the key is released or at end of each frame if key is released. This is useful to make input-owner-aware code steal keys from non-input-owner-aware code. + // Locking is useful to make input-owner-aware code steal keys from non-input-owner-aware code. If all code is input-owner-aware locking would never be necessary. + ImGuiInputFlags_LockThisFrame = 1 << 10, // Further accesses to key data will require EXPLICIT owner ID (ImGuiKeyOwner_Any/0 will NOT accepted for polling). Cleared at end of frame. + ImGuiInputFlags_LockUntilRelease = 1 << 11, // Further accesses to key data will require EXPLICIT owner ID (ImGuiKeyOwner_Any/0 will NOT accepted for polling). Cleared when the key is released or at end of each frame if key is released. // Routing policies for Shortcut() + low-level SetShortcutRouting() // - The general idea is that several callers register interest in a shortcut, and only one owner gets it. - // - When a policy (other than _RouteAlways) is set, Shortcut() will register itself with SetShortcutRouting(), + // Parent -> call Shortcut(Ctrl+S) // When Parent is focused, Parent gets the shortcut. + // Child1 -> call Shortcut(Ctrl+S) // When Child1 is focused, Child1 gets the shortcut (Child1 overrides Parent shortcuts) + // Child2 -> no call // When Child2 is focused, Parent gets the shortcut. + // The whole system is order independent, so if Child1 does it calls before Parent results will be identical. + // This is an important property as it facilitate working with foreign code or larger codebase. + // - Visualize registered routes in 'Metrics->Inputs' and submitted routes in 'Debug Log->InputRouting'. + // - When a policy (except for _RouteAlways *) is set, Shortcut() will register itself with SetShortcutRouting(), // allowing the system to decide where to route the input among other route-aware calls. - // - Shortcut() uses ImGuiInputFlags_RouteFocused by default: meaning that a simple Shortcut() poll - // will register a route and only succeed when parent window is in the focus stack and if no-one - // with a higher priority is claiming the shortcut. - // - Using ImGuiInputFlags_RouteAlways is roughly equivalent to doing e.g. IsKeyPressed(key) + testing mods. + // (* Using ImGuiInputFlags_RouteAlways is roughly equivalent to calling IsKeyChordPressed(key)). + // - Shortcut() uses ImGuiInputFlags_RouteFocused by default. Meaning that a Shortcut() call will register + // a route and only succeed when parent window is in the focus-stack and if no-one with a higher priority + // is claiming the same shortcut. + // - You can chain two unrelated windows in the focus stack using SetWindowParentWindowForFocusRoute(). // - Priorities: GlobalHigh > Focused (when owner is active item) > Global > Focused (when focused window) > GlobalLow. // - Can select only 1 policy among all available. - ImGuiInputFlags_RouteFocused = 1 << 12, // (Default) Register focused route: Accept inputs if window is in focus stack. Deep-most focused window takes inputs. ActiveId takes inputs over deep-most focused window. - ImGuiInputFlags_RouteGlobalLow = 1 << 13, // Register route globally (lowest priority: unless a focused window or active item registered the route) -> recommended Global priority. - ImGuiInputFlags_RouteGlobal = 1 << 14, // Register route globally (medium priority: unless an active item registered the route, e.g. CTRL+A registered by InputText). - ImGuiInputFlags_RouteGlobalHigh = 1 << 15, // Register route globally (highest priority: unlikely you need to use that: will interfere with every active items) - ImGuiInputFlags_RouteMask_ = ImGuiInputFlags_RouteFocused | ImGuiInputFlags_RouteGlobal | ImGuiInputFlags_RouteGlobalLow | ImGuiInputFlags_RouteGlobalHigh, // _Always not part of this! + ImGuiInputFlags_RouteFocused = 1 << 12, // (Default) Honor focus route: Accept inputs if window is in focus stack. Deep-most focused window takes inputs. ActiveId takes inputs over deep-most focused window. + ImGuiInputFlags_RouteGlobalLow = 1 << 13, // Register route globally (lowest priority: unless a focused window or active item registered the route) -> recommended Global priority IF you need a Global priority. + ImGuiInputFlags_RouteGlobal = 1 << 14, // Register route globally (medium priority: unless an active item registered the route, e.g. CTRL+A registered by InputText will take priority over this). + ImGuiInputFlags_RouteGlobalHigh = 1 << 15, // Register route globally (higher priority: unlikely you need to use that: will interfere with every active items, e.g. CTRL+A registered by InputText will be overiden by this) ImGuiInputFlags_RouteAlways = 1 << 16, // Do not register route, poll keys directly. + // Routing polices: extra options ImGuiInputFlags_RouteUnlessBgFocused= 1 << 17, // Global routes will not be applied if underlying background/void is focused (== no Dear ImGui windows are focused). Useful for overlay applications. - ImGuiInputFlags_RouteExtraMask_ = ImGuiInputFlags_RouteAlways | ImGuiInputFlags_RouteUnlessBgFocused, // [Internal] Mask of which function support which flags ImGuiInputFlags_RepeatRateMask_ = ImGuiInputFlags_RepeatRateDefault | ImGuiInputFlags_RepeatRateNavMove | ImGuiInputFlags_RepeatRateNavTweak, ImGuiInputFlags_RepeatUntilMask_ = ImGuiInputFlags_RepeatUntilRelease | ImGuiInputFlags_RepeatUntilKeyModsChange | ImGuiInputFlags_RepeatUntilKeyModsChangeFromNone | ImGuiInputFlags_RepeatUntilOtherKeyPress, ImGuiInputFlags_RepeatMask_ = ImGuiInputFlags_Repeat | ImGuiInputFlags_RepeatRateMask_ | ImGuiInputFlags_RepeatUntilMask_, + ImGuiInputFlags_CondMask_ = ImGuiInputFlags_CondHovered | ImGuiInputFlags_CondActive, + ImGuiInputFlags_RouteMask_ = ImGuiInputFlags_RouteFocused | ImGuiInputFlags_RouteGlobal | ImGuiInputFlags_RouteGlobalLow | ImGuiInputFlags_RouteGlobalHigh, // _Always not part of this! ImGuiInputFlags_SupportedByIsKeyPressed = ImGuiInputFlags_RepeatMask_, ImGuiInputFlags_SupportedByIsMouseClicked = ImGuiInputFlags_Repeat, - ImGuiInputFlags_SupportedByShortcut = ImGuiInputFlags_RepeatMask_ | ImGuiInputFlags_RouteMask_ | ImGuiInputFlags_RouteExtraMask_, + ImGuiInputFlags_SupportedByShortcut = ImGuiInputFlags_RepeatMask_ | ImGuiInputFlags_RouteMask_ | ImGuiInputFlags_RouteAlways | ImGuiInputFlags_RouteUnlessBgFocused, ImGuiInputFlags_SupportedBySetKeyOwner = ImGuiInputFlags_LockThisFrame | ImGuiInputFlags_LockUntilRelease, ImGuiInputFlags_SupportedBySetItemKeyOwner = ImGuiInputFlags_SupportedBySetKeyOwner | ImGuiInputFlags_CondMask_, }; From 5fdcdf7080aade03b30f742dd9369172460fcfd7 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 16 Jan 2024 17:51:14 +0100 Subject: [PATCH 03/13] Shortcut: ImGuiInputFlags_RouteFocused policy can filter Shortcuts conflicting with character input when an item is active. (#456) --- imgui.cpp | 48 ++++++++++++++++++++++++++++++++++++++++++++---- imgui.h | 2 +- imgui_internal.h | 1 + 3 files changed, 46 insertions(+), 5 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 37182fc72..5c3eaaf83 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -3596,6 +3596,14 @@ void ImGui::Initialize() g.Viewports.push_back(viewport); g.TempBuffer.resize(1024 * 3 + 1, 0); + // Build KeysMayBeCharInput[] lookup table (1 bool per named key) + for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1)) + if ((key >= ImGuiKey_0 && key <= ImGuiKey_9) || (key >= ImGuiKey_A && key <= ImGuiKey_Z) || (key >= ImGuiKey_Keypad0 && key <= ImGuiKey_Keypad9) + || key == ImGuiKey_Tab || key == ImGuiKey_Space || key == ImGuiKey_Apostrophe || key == ImGuiKey_Comma || key == ImGuiKey_Minus || key == ImGuiKey_Period + || key == ImGuiKey_Slash || key == ImGuiKey_Semicolon || key == ImGuiKey_Equal || key == ImGuiKey_LeftBracket || key == ImGuiKey_RightBracket || key == ImGuiKey_GraveAccent + || key == ImGuiKey_KeypadDecimal || key == ImGuiKey_KeypadDivide || key == ImGuiKey_KeypadMultiply || key == ImGuiKey_KeypadSubtract || key == ImGuiKey_KeypadAdd || key == ImGuiKey_KeypadEqual) + g.KeysMayBeCharInput.SetBit(key); + #ifdef IMGUI_HAS_DOCK #endif @@ -8308,13 +8316,11 @@ ImGuiKeyRoutingData* ImGui::GetShortcutRoutingData(ImGuiKeyChord key_chord) ImGuiContext& g = *GImGui; ImGuiKeyRoutingTable* rt = &g.KeysRoutingTable; ImGuiKeyRoutingData* routing_data; - if (key_chord & ImGuiMod_Shortcut) - key_chord = ConvertShortcutMod(key_chord); ImGuiKey key = (ImGuiKey)(key_chord & ~ImGuiMod_Mask_); ImGuiKey mods = (ImGuiKey)(key_chord & ImGuiMod_Mask_); if (key == ImGuiKey_None) key = ConvertSingleModFlagToKey(&g, mods); - IM_ASSERT(IsNamedKey(key)); + IM_ASSERT(IsNamedKey(key) && (key_chord & ImGuiMod_Shortcut) == 0); // Please call ConvertShortcutMod() in calling function. // Get (in the majority of case, the linked list will have one element so this should be 2 reads. // Subsequent elements will be contiguous in memory as list is sorted/rebuilt in NewFrame). @@ -8378,6 +8384,24 @@ static int CalcRoutingScore(ImGuiID focus_scope_id, ImGuiID owner_id, ImGuiInput return 0; } +// We need this to filter some Shortcut() routes when an item e.g. an InputText() is active +// e.g. ImGuiKey_G won't be considered a shortcut when item is active, but ImGuiMod|ImGuiKey_G can be. +static bool IsKeyChordPotentiallyCharInput(ImGuiKeyChord key_chord) +{ + // Mimic 'ignore_char_inputs' logic in InputText() + ImGuiContext& g = *GImGui; + + // When the right mods are pressed it cannot be a char input so we won't filter the shortcut out. + ImGuiKey mods = (ImGuiKey)(key_chord & ImGuiMod_Mask_); + const bool ignore_char_inputs = ((mods & ImGuiMod_Ctrl) && !(mods & ImGuiMod_Alt)) || (g.IO.ConfigMacOSXBehaviors && (mods & ImGuiMod_Super)); + if (ignore_char_inputs) + return false; + + // Return true for A-Z, 0-9 and other keys associated to char inputs. Other keys such as F1-F12 won't be filtered. + ImGuiKey key = (ImGuiKey)(key_chord & ~ImGuiMod_Mask_); + return g.KeysMayBeCharInput.TestBit(key); +} + // Request a desired route for an input chord (key + mods). // Return true if the route is available this frame. // - Routes and key ownership are attributed at the beginning of next frame based on best score and mod state. @@ -8392,9 +8416,11 @@ bool ImGui::SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiI flags |= ImGuiInputFlags_RouteGlobalHigh; // IMPORTANT: This is the default for SetShortcutRouting() but NOT Shortcut() else IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiInputFlags_RouteMask_)); // Check that only 1 routing flag is used + if (key_chord & ImGuiMod_Shortcut) + key_chord = ConvertShortcutMod(key_chord); // [DEBUG] Debug break requested by user - if (g.DebugBreakInShortcutRouting != 0 && g.DebugBreakInShortcutRouting == ConvertShortcutMod(key_chord)) + if (g.DebugBreakInShortcutRouting == key_chord) IM_DEBUG_BREAK(); if (flags & ImGuiInputFlags_RouteUnlessBgFocused) @@ -8408,6 +8434,18 @@ bool ImGui::SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiI return true; } + // Specific culling for shortcuts with no modifiers when there's an active id. + // e.g. Shortcut(ImGuiKey_G) also generates 'g' character, should not trigger when InputText() is active. + // but Shortcut(Ctrl+G) should generally trigger when InputText() is active. + // TL;DR: lettered shortcut with no mods or with only Alt mod will not trigger while an item reading text input is active. + // (We cannot filter based on io.InputQueueCharacters[] contents because of trickling and key<>chars submission order are undefined) + if ((flags & ImGuiInputFlags_RouteFocused) && (g.ActiveId != 0 && g.ActiveId != owner_id)) + if (g.IO.WantTextInput && IsKeyChordPotentiallyCharInput(key_chord)) + { + IMGUI_DEBUG_LOG_INPUTROUTING("SetShortcutRouting(%s, owner_id=0x%08X, flags=%04X) -> filtered as potential char input\n", GetKeyChordName(key_chord), owner_id, flags); + return false; + } + const int score = CalcRoutingScore(g.CurrentFocusScopeId, owner_id, flags); IMGUI_DEBUG_LOG_INPUTROUTING("SetShortcutRouting(%s, owner_id=0x%08X, flags=%04X) -> score %d\n", GetKeyChordName(key_chord), owner_id, flags, score); if (score == 255) @@ -8435,6 +8473,8 @@ bool ImGui::SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiI bool ImGui::TestShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id) { const ImGuiID routing_id = GetRoutingIdFromOwnerId(owner_id); + if (key_chord & ImGuiMod_Shortcut) + key_chord = ConvertShortcutMod(key_chord); ImGuiKeyRoutingData* routing_data = GetShortcutRoutingData(key_chord); // FIXME: Could avoid creating entry. return routing_data->RoutingCurr == routing_id; } diff --git a/imgui.h b/imgui.h index d9d5d6898..c8054a5c4 100644 --- a/imgui.h +++ b/imgui.h @@ -24,7 +24,7 @@ // Library Version // (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM >= 12345') #define IMGUI_VERSION "1.90.2 WIP" -#define IMGUI_VERSION_NUM 19012 +#define IMGUI_VERSION_NUM 19013 #define IMGUI_HAS_TABLE /* diff --git a/imgui_internal.h b/imgui_internal.h index cf7612e58..708e0efa5 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1974,6 +1974,7 @@ struct ImGuiContext double LastKeyModsChangeTime; // Record the last time key mods changed (affect repeat delay when using shortcut logic) double LastKeyModsChangeFromNoneTime; // Record the last time key mods changed away from being 0 (affect repeat delay when using shortcut logic) double LastKeyboardKeyPressTime; // Record the last time a keyboard key (ignore mouse/gamepad ones) was pressed. + ImBitArrayForNamedKeys KeysMayBeCharInput; // Lookup to tell if a key can emit char input, see IsKeyChordPotentiallyCharInput(). sizeof() = 20 bytes ImGuiKeyOwnerData KeysOwnerData[ImGuiKey_NamedKey_COUNT]; ImGuiKeyRoutingTable KeysRoutingTable; ImU32 ActiveIdUsingNavDirMask; // Active widget will want to read those nav move requests (e.g. can activate a button and move away from it) From 03417cc77d15100b18c486b55db409ee5e9c363e Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 17 Jan 2024 13:26:02 +0100 Subject: [PATCH 04/13] Backends: WebGPU: Filling all WGPUDepthStencilState fields explicitly as a recent Dawn update stopped setting default values. (#7232) --- backends/imgui_impl_wgpu.cpp | 7 +++++++ docs/CHANGELOG.txt | 2 ++ 2 files changed, 9 insertions(+) diff --git a/backends/imgui_impl_wgpu.cpp b/backends/imgui_impl_wgpu.cpp index 638e9fd24..c987c6d13 100644 --- a/backends/imgui_impl_wgpu.cpp +++ b/backends/imgui_impl_wgpu.cpp @@ -16,6 +16,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2024-01-17: Explicitly fill all of WGPUDepthStencilState since standard removed defaults. // 2023-07-13: Use WGPUShaderModuleWGSLDescriptor's code instead of source. use WGPUMipmapFilterMode_Linear instead of WGPUFilterMode_Linear. (#6602) // 2023-04-11: Align buffer sizes. Use WGSL shaders instead of precompiled SPIR-V. // 2023-04-11: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX). @@ -654,7 +655,13 @@ bool ImGui_ImplWGPU_CreateDeviceObjects() depth_stencil_state.depthWriteEnabled = false; depth_stencil_state.depthCompare = WGPUCompareFunction_Always; depth_stencil_state.stencilFront.compare = WGPUCompareFunction_Always; + depth_stencil_state.stencilFront.failOp = WGPUStencilOperation_Keep; + depth_stencil_state.stencilFront.depthFailOp = WGPUStencilOperation_Keep; + depth_stencil_state.stencilFront.passOp = WGPUStencilOperation_Keep; depth_stencil_state.stencilBack.compare = WGPUCompareFunction_Always; + depth_stencil_state.stencilBack.failOp = WGPUStencilOperation_Keep; + depth_stencil_state.stencilBack.depthFailOp = WGPUStencilOperation_Keep; + depth_stencil_state.stencilBack.passOp = WGPUStencilOperation_Keep; // Configure disabled depth-stencil state graphics_pipeline_desc.depthStencil = (bd->depthStencilFormat == WGPUTextureFormat_Undefined) ? nullptr : &depth_stencil_state; diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 39e7b6654..dd5eeb36e 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -52,6 +52,8 @@ Other changes: - Debug Tools: Debug Log: Added "Input Routing" logging. - Backends: Vulkan: Fixed vkMapMemory() calls unnecessarily using full buffer size. (#3957) - Backends: Vulkan: Fixed handling of ImGui_ImplVulkan_InitInfo::MinAllocationSize field. (#7189, #4238) +- Backends: WebGPU: Filling all WGPUDepthStencilState fields explicitly as a recent Dawn + update stopped setting default values. (#7232) [@GrigoryGraborenko] ----------------------------------------------------------------------- From d10641b04a3757884193b64753b9728c686d7aa9 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 17 Jan 2024 14:02:07 +0100 Subject: [PATCH 05/13] Nav: keyboard/gamepad activation mark widgets as held to give better visual feedback. --- docs/CHANGELOG.txt | 1 + imgui_widgets.cpp | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index dd5eeb36e..be244c933 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -46,6 +46,7 @@ Breaking changes: Other changes: +- Nav: keyboard/gamepad activation mark widgets as held to give better visual feedback. - Fixed SetKeyboardFocusHere() not working when current nav focus is in different scope, regression from 1.90.1 related to code scoping Tab presses to local scope. (#7226) [@bratpilz] - Debug Tools: Metrics: Fixed debug break in SetShortcutRouting() not handling ImGuiMod_Shortcut redirect. diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 6b16f55b9..2b648a604 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -666,7 +666,9 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool else if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad) { // When activated using Nav, we hold on the ActiveID until activation button is released - if (g.NavActivateDownId != id) + if (g.NavActivateDownId == id) + held = true; + else ClearActiveID(); } if (pressed) From 095665977f61df056608c6bfca1603ed2d500848 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 17 Jan 2024 14:52:05 +0100 Subject: [PATCH 06/13] Nav: marking NavId as hovered in ButtonBehavior() doesn't check for ActiveId. Aimed at allowing activating another item with a shortcut without losing NavId. Initial logic from c2cb2a69, with minor amends db5f1b79c, cea78cc57. --- 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 be244c933..df56d4f26 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -47,6 +47,8 @@ Breaking changes: Other changes: - Nav: keyboard/gamepad activation mark widgets as held to give better visual feedback. +- Nav: tweak to logic marking navigated item as hovered when using keyboard, allowing + the hover highlight to stay even while another item is activated. - Fixed SetKeyboardFocusHere() not working when current nav focus is in different scope, regression from 1.90.1 related to code scoping Tab presses to local scope. (#7226) [@bratpilz] - Debug Tools: Metrics: Fixed debug break in SetShortcutRouting() not handling ImGuiMod_Shortcut redirect. diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 2b648a604..bc71aa628 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -599,7 +599,7 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool // Gamepad/Keyboard navigation // We report navigated item as hovered but we don't set g.HoveredId to not interfere with mouse. - if (g.NavId == id && !g.NavDisableHighlight && g.NavDisableMouseHover && (g.ActiveId == 0 || g.ActiveId == id || g.ActiveId == window->MoveId)) + if (g.NavId == id && !g.NavDisableHighlight && g.NavDisableMouseHover) if (!(flags & ImGuiButtonFlags_NoHoveredOnFocus)) hovered = true; if (g.NavActivateDownId == id) From f0d1f61fa51ad144ce70c0884bbcb0abefcd717e Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 17 Jan 2024 15:23:30 +0100 Subject: [PATCH 07/13] Internals: commented out long-time obsoleted FocusableItemRegister()/FocusableItemUnregister() documentaton-only leftovers. + --- imgui_internal.h | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/imgui_internal.h b/imgui_internal.h index 708e0efa5..e909594f8 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3474,13 +3474,12 @@ namespace ImGui inline void SetItemUsingMouseWheel() { SetItemKeyOwner(ImGuiKey_MouseWheelY); } // Changed in 1.89 inline bool TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags = 0) { return TreeNodeUpdateNextOpen(id, flags); } // Renamed in 1.89 - // Refactored focus/nav/tabbing system in 1.82 and 1.84. If you have old/custom copy-and-pasted widgets that used FocusableItemRegister(): + // Refactored focus/nav/tabbing system in 1.82 and 1.84. If you have old/custom copy-and-pasted widgets which used FocusableItemRegister(): // (Old) IMGUI_VERSION_NUM < 18209: using 'ItemAdd(....)' and 'bool tab_focused = FocusableItemRegister(...)' - // (Old) IMGUI_VERSION_NUM >= 18209: using 'ItemAdd(..., ImGuiItemAddFlags_Focusable)' and 'bool tab_focused = (GetItemStatusFlags() & ImGuiItemStatusFlags_Focused) != 0' - // (New) IMGUI_VERSION_NUM >= 18413: using 'ItemAdd(..., ImGuiItemFlags_Inputable)' and 'bool tab_focused = (GetItemStatusFlags() & ImGuiItemStatusFlags_FocusedTabbing) != 0 || (g.NavActivateId == id && (g.NavActivateFlags & ImGuiActivateFlags_PreferInput))' (WIP) - // Widget code are simplified as there's no need to call FocusableItemUnregister() while managing the transition from regular widget to TempInputText() - inline bool FocusableItemRegister(ImGuiWindow* window, ImGuiID id) { IM_ASSERT(0); IM_UNUSED(window); IM_UNUSED(id); return false; } // -> pass ImGuiItemAddFlags_Inputable flag to ItemAdd() - inline void FocusableItemUnregister(ImGuiWindow* window) { IM_ASSERT(0); IM_UNUSED(window); } // -> unnecessary: TempInputText() uses ImGuiInputTextFlags_MergedItem + // (Old) IMGUI_VERSION_NUM >= 18209: using 'ItemAdd(..., ImGuiItemAddFlags_Focusable)' and 'bool tab_focused = (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Focused) != 0' + // (New) IMGUI_VERSION_NUM >= 18413: using 'ItemAdd(..., ImGuiItemFlags_Inputable)' and 'bool tab_focused = (g.NavActivateId == id && (g.NavActivateFlags & ImGuiActivateFlags_PreferInput))' + //inline bool FocusableItemRegister(ImGuiWindow* window, ImGuiID id) // -> pass ImGuiItemAddFlags_Inputable flag to ItemAdd() + //inline void FocusableItemUnregister(ImGuiWindow* window) // -> unnecessary: TempInputText() uses ImGuiInputTextFlags_MergedItem #endif #ifndef IMGUI_DISABLE_OBSOLETE_KEYIO inline bool IsKeyPressedMap(ImGuiKey key, bool repeat = true) { IM_ASSERT(IsNamedKey(key)); return IsKeyPressed(key, repeat); } // Removed in 1.87: Mapping from named key is always identity! From d431d85839b507a9c9041b0e28de7c2443976292 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 17 Jan 2024 15:33:12 +0100 Subject: [PATCH 08/13] Internals: removed obsolete ImPool::GetSize() (last used by implot 0.10, changed in implot 0.11) --- imgui_internal.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/imgui_internal.h b/imgui_internal.h index e909594f8..c96e2a564 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -690,9 +690,6 @@ struct ImPool int GetBufSize() const { return Buf.Size; } int GetMapSize() const { return Map.Data.Size; } // It is the map we need iterate to find valid items, since we don't have "alive" storage anywhere T* TryGetMapData(ImPoolIdx n) { int idx = Map.Data[n].val_i; if (idx == -1) return NULL; return GetByIndex(idx); } -#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - int GetSize() { return GetMapSize(); } // For ImPlot: should use GetMapSize() from (IMGUI_VERSION_NUM >= 18304) -#endif }; // Helper: ImChunkStream<> From 33fabdf392d0a34b52a4db3eaf467847604d5167 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 17 Jan 2024 15:45:20 +0100 Subject: [PATCH 09/13] Scrollbar() doesn't forcefully mark itself as hovered when held. Weirdly as old as when dinosaurs roamed earth, aka first commit (per-1.0). + Minor alignment in both sense of the term :) Reduce padding in ImGuiNextItemData. --- docs/CHANGELOG.txt | 2 +- imgui_internal.h | 10 +++++----- imgui_widgets.cpp | 1 - 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index df56d4f26..418fdc908 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -49,7 +49,7 @@ Other changes: - Nav: keyboard/gamepad activation mark widgets as held to give better visual feedback. - Nav: tweak to logic marking navigated item as hovered when using keyboard, allowing the hover highlight to stay even while another item is activated. -- Fixed SetKeyboardFocusHere() not working when current nav focus is in different scope, +- Nav: Fixed SetKeyboardFocusHere() not working when current nav focus is in different scope, regression from 1.90.1 related to code scoping Tab presses to local scope. (#7226) [@bratpilz] - Debug Tools: Metrics: Fixed debug break in SetShortcutRouting() not handling ImGuiMod_Shortcut redirect. - Debug Tools: Debug Log: Added "Input Routing" logging. diff --git a/imgui_internal.h b/imgui_internal.h index c96e2a564..1b4855374 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1192,9 +1192,9 @@ typedef ImS64 ImGuiSelectionUserData; enum ImGuiNextItemDataFlags_ { - ImGuiNextItemDataFlags_None = 0, - ImGuiNextItemDataFlags_HasWidth = 1 << 0, - ImGuiNextItemDataFlags_HasOpen = 1 << 1, + ImGuiNextItemDataFlags_None = 0, + ImGuiNextItemDataFlags_HasWidth = 1 << 0, + ImGuiNextItemDataFlags_HasOpen = 1 << 1, }; struct ImGuiNextItemData @@ -1202,10 +1202,10 @@ struct ImGuiNextItemData ImGuiNextItemDataFlags Flags; ImGuiItemFlags ItemFlags; // Currently only tested/used for ImGuiItemFlags_AllowOverlap. // Non-flags members are NOT cleared by ItemAdd() meaning they are still valid during NavProcessItem() - float Width; // Set by SetNextItemWidth() ImGuiSelectionUserData SelectionUserData; // Set by SetNextItemSelectionUserData() (note that NULL/0 is a valid value, we use -1 == ImGuiSelectionUserData_Invalid to mark invalid values) - ImGuiCond OpenCond; + float Width; // Set by SetNextItemWidth() bool OpenVal; // Set by SetNextItemOpen() + ImGuiCond OpenCond : 8; ImGuiNextItemData() { memset(this, 0, sizeof(*this)); SelectionUserData = -1; } inline void ClearFlags() { Flags = ImGuiNextItemDataFlags_None; ItemFlags = ImGuiItemFlags_None; } // Also cleared manually by ItemAdd()! diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index bc71aa628..3c6937a92 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -968,7 +968,6 @@ bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS6 // Click position in scrollbar normalized space (0.0f->1.0f) const float clicked_v_norm = ImSaturate((mouse_pos_v - scrollbar_pos_v) / scrollbar_size_v); - SetHoveredID(id); bool seek_absolute = false; if (g.ActiveIdIsJustActivated) From 1a48a6344665829dbc818617067ffbd5b8cb1b0c Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 17 Jan 2024 15:55:36 +0100 Subject: [PATCH 10/13] Enclosed a few more remaining sections in ifndef IMGUI_DISABLE_DEBUG_TOOLS for completeness. --- imgui.cpp | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 5c3eaaf83..b06fbd327 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -4147,17 +4147,19 @@ bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id, ImGuiItemFlags item_flag return false; } +#ifndef IMGUI_DISABLE_DEBUG_TOOLS if (id != 0) { // [DEBUG] Item Picker tool! - // We perform the check here because SetHoveredID() is not frequently called (1~ time a frame), making - // the cost of this tool near-zero. We can get slightly better call-stack and support picking non-hovered - // items if we performed the test in ItemAdd(), but that would incur a small runtime cost. + // We perform the check here because reaching is path is rare (1~ time a frame), + // making the cost of this tool near-zero! We could get better call-stack and support picking non-hovered + // items if we performed the test in ItemAdd(), but that would incur a bigger runtime cost. if (g.DebugItemPickerActive && g.HoveredIdPreviousFrame == id) GetForegroundDrawList()->AddRect(bb.Min, bb.Max, IM_COL32(255, 255, 0, 255)); if (g.DebugItemPickerBreakId == id) IM_DEBUG_BREAK(); } +#endif if (g.NavDisableMouseHover) return false; @@ -4792,6 +4794,7 @@ void ImGui::NewFrame() g.GroupStack.resize(0); // [DEBUG] Update debug features +#ifndef IMGUI_DISABLE_DEBUG_TOOLS UpdateDebugToolItemPicker(); UpdateDebugToolStackQueries(); UpdateDebugToolFlashStyleColor(); @@ -4806,6 +4809,7 @@ void ImGui::NewFrame() g.DebugLogFlags &= ~g.DebugLogAutoDisableFlags; g.DebugLogAutoDisableFlags = ImGuiDebugLogFlags_None; } +#endif // Create implicit/fallback window - which we will only render it if the user has added something to it. // We don't use "Debug" to avoid colliding with user trying to create a "Debug" window with custom flags. @@ -4817,10 +4821,12 @@ void ImGui::NewFrame() // [DEBUG] When io.ConfigDebugBeginReturnValue is set, we make Begin()/BeginChild() return false at different level of the window-stack, // allowing to validate correct Begin/End behavior in user code. +#ifndef IMGUI_DISABLE_DEBUG_TOOLS if (g.IO.ConfigDebugBeginReturnValueLoop) g.DebugBeginReturnValueCullDepth = (g.DebugBeginReturnValueCullDepth == -1) ? 0 : ((g.DebugBeginReturnValueCullDepth + ((g.FrameCount % 4) == 0 ? 1 : 0)) % 10); else g.DebugBeginReturnValueCullDepth = -1; +#endif CallContextHooks(&g, ImGuiContextHookType_NewFramePost); } @@ -7059,12 +7065,15 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // [DEBUG] io.ConfigDebugBeginReturnValue override return value to test Begin/End and BeginChild/EndChild behaviors. // (The implicit fallback window is NOT automatically ended allowing it to always be able to receive commands without crashing) - if (!window->IsFallbackWindow && ((g.IO.ConfigDebugBeginReturnValueOnce && window_just_created) || (g.IO.ConfigDebugBeginReturnValueLoop && g.DebugBeginReturnValueCullDepth == g.CurrentWindowStack.Size))) - { - if (window->AutoFitFramesX > 0) { window->AutoFitFramesX++; } - if (window->AutoFitFramesY > 0) { window->AutoFitFramesY++; } - return false; - } +#ifndef IMGUI_DISABLE_DEBUG_TOOLS + if (!window->IsFallbackWindow) + if ((g.IO.ConfigDebugBeginReturnValueOnce && window_just_created) || (g.IO.ConfigDebugBeginReturnValueLoop && g.DebugBeginReturnValueCullDepth == g.CurrentWindowStack.Size)) + { + if (window->AutoFitFramesX > 0) { window->AutoFitFramesX++; } + if (window->AutoFitFramesY > 0) { window->AutoFitFramesY++; } + return false; + } +#endif return !window->SkipItems; } @@ -15561,9 +15570,6 @@ void ImGui::DebugLogV(const char*, va_list) {} void ImGui::ShowDebugLogWindow(bool*) {} void ImGui::ShowIDStackToolWindow(bool*) {} void ImGui::DebugHookIdInfo(ImGuiID, ImGuiDataType, const void*, const void*) {} -void ImGui::UpdateDebugToolItemPicker() {} -void ImGui::UpdateDebugToolStackQueries() {} -void ImGui::UpdateDebugToolFlashStyleColor() {} #endif // #ifndef IMGUI_DISABLE_DEBUG_TOOLS From ff5f3aa38b5a67d7df94be477986a42dfa29f6df Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 17 Jan 2024 17:27:04 +0100 Subject: [PATCH 11/13] Refactor: moving ItemAdd() into a section abote ItemSize(). No logic change (part 1) Moved KeepAliveID() as well for increased locality. Adding dummy ItemAdd() placeholder to facilitate diffing (otherwise single commit single diff is a mess). --- imgui.cpp | 53 +++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 37 insertions(+), 16 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index b06fbd327..bd35586f8 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -75,6 +75,7 @@ CODE // [SECTION] MAIN CODE (most of the code! lots of stuff, needs tidying up!) // [SECTION] INPUTS // [SECTION] ERROR CHECKING +// [SECTION] ITEM SUBMISSION // [SECTION] LAYOUT // [SECTION] SCROLLING // [SECTION] TOOLTIPS @@ -3924,17 +3925,6 @@ ImGuiID ImGui::GetHoveredID() return g.HoveredId ? g.HoveredId : g.HoveredIdPreviousFrame; } -// This is called by ItemAdd(). -// Code not using ItemAdd() may need to call this manually otherwise ActiveId will be cleared. In IMGUI_VERSION_NUM < 18717 this was called by GetID(). -void ImGui::KeepAliveID(ImGuiID id) -{ - ImGuiContext& g = *GImGui; - if (g.ActiveId == id) - g.ActiveIdIsAlive = id; - if (g.ActiveIdPreviousFrame == id) - g.ActiveIdPreviousFrameIsAlive = true; -} - void ImGui::MarkItemEdited(ImGuiID id) { // This marking is solely to be able to provide info for IsItemDeactivatedAfterEdit(). @@ -9728,12 +9718,46 @@ void ImGuiStackSizes::CompareWithContextState(ImGuiContext* ctx) IM_ASSERT(SizeOfFocusScopeStack == g.FocusScopeStack.Size && "PushFocusScope/PopFocusScope Mismatch!"); } +//----------------------------------------------------------------------------- +// [SECTION] ITEM SUBMISSION +//----------------------------------------------------------------------------- +// - KeepAliveID() +// - ItemAdd() +//----------------------------------------------------------------------------- + +// Code not using ItemAdd() may need to call this manually otherwise ActiveId will be cleared. In IMGUI_VERSION_NUM < 18717 this was called by GetID(). +void ImGui::KeepAliveID(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + if (g.ActiveId == id) + g.ActiveIdIsAlive = id; + if (g.ActiveIdPreviousFrame == id) + g.ActiveIdPreviousFrameIsAlive = true; +} + +/* + +// Declare item bounding box for clipping and interaction. +// Note that the size can be different than the one provided to ItemSize(). Typically, widgets that spread over available surface +// declare their minimum size requirement to ItemSize() and provide a larger region to ItemAdd() which is used drawing/interaction. +// THIS IS IN THE PERFORMANCE CRITICAL PATH (UNTIL THE CLIPPING TEST AND EARLY-RETURN) +bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGuiItemFlags extra_flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + ... + + return true; +} + +*/ + //----------------------------------------------------------------------------- // [SECTION] LAYOUT //----------------------------------------------------------------------------- // - ItemSize() -// - ItemAdd() // - SameLine() // - GetCursorScreenPos() // - SetCursorScreenPos() @@ -9801,10 +9825,7 @@ void ImGui::ItemSize(const ImVec2& size, float text_baseline_y) SameLine(); } -// Declare item bounding box for clipping and interaction. -// Note that the size can be different than the one provided to ItemSize(). Typically, widgets that spread over available surface -// declare their minimum size requirement to ItemSize() and provide a larger region to ItemAdd() which is used drawing/interaction. -// THIS IS IN THE PERFORMANCE CRITICAL PATH (UNTIL THE CLIPPING TEST AND EARLY-RETURN) + bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGuiItemFlags extra_flags) { ImGuiContext& g = *GImGui; From 3b828d3701e9cfff30de47e203e09075c97fe1a0 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 17 Jan 2024 17:34:28 +0100 Subject: [PATCH 12/13] Refactor: moving ItemAdd() into a section abote ItemSize(). No logic change (part 2) Diff not ideal (in Fork it looks like ItemSize is moved) but understandable at a glance. --- imgui.cpp | 159 +++++++++++++++++++++++++----------------------------- 1 file changed, 72 insertions(+), 87 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index bd35586f8..d4c5e754c 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -9735,97 +9735,10 @@ void ImGui::KeepAliveID(ImGuiID id) g.ActiveIdPreviousFrameIsAlive = true; } -/* - // Declare item bounding box for clipping and interaction. // Note that the size can be different than the one provided to ItemSize(). Typically, widgets that spread over available surface // declare their minimum size requirement to ItemSize() and provide a larger region to ItemAdd() which is used drawing/interaction. // THIS IS IN THE PERFORMANCE CRITICAL PATH (UNTIL THE CLIPPING TEST AND EARLY-RETURN) -bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGuiItemFlags extra_flags) -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - - ... - - return true; -} - -*/ - - -//----------------------------------------------------------------------------- -// [SECTION] LAYOUT -//----------------------------------------------------------------------------- -// - ItemSize() -// - SameLine() -// - GetCursorScreenPos() -// - SetCursorScreenPos() -// - GetCursorPos(), GetCursorPosX(), GetCursorPosY() -// - SetCursorPos(), SetCursorPosX(), SetCursorPosY() -// - GetCursorStartPos() -// - Indent() -// - Unindent() -// - SetNextItemWidth() -// - PushItemWidth() -// - PushMultiItemsWidths() -// - PopItemWidth() -// - CalcItemWidth() -// - CalcItemSize() -// - GetTextLineHeight() -// - GetTextLineHeightWithSpacing() -// - GetFrameHeight() -// - GetFrameHeightWithSpacing() -// - GetContentRegionMax() -// - GetContentRegionMaxAbs() [Internal] -// - GetContentRegionAvail(), -// - GetWindowContentRegionMin(), GetWindowContentRegionMax() -// - BeginGroup() -// - EndGroup() -// Also see in imgui_widgets: tab bars, and in imgui_tables: tables, columns. -//----------------------------------------------------------------------------- - -// Advance cursor given item size for layout. -// Register minimum needed size so it can extend the bounding box used for auto-fit calculation. -// See comments in ItemAdd() about how/why the size provided to ItemSize() vs ItemAdd() may often different. -// THIS IS IN THE PERFORMANCE CRITICAL PATH. -void ImGui::ItemSize(const ImVec2& size, float text_baseline_y) -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - if (window->SkipItems) - return; - - // We increase the height in this function to accommodate for baseline offset. - // In theory we should be offsetting the starting position (window->DC.CursorPos), that will be the topic of a larger refactor, - // but since ItemSize() is not yet an API that moves the cursor (to handle e.g. wrapping) enlarging the height has the same effect. - const float offset_to_match_baseline_y = (text_baseline_y >= 0) ? ImMax(0.0f, window->DC.CurrLineTextBaseOffset - text_baseline_y) : 0.0f; - - const float line_y1 = window->DC.IsSameLine ? window->DC.CursorPosPrevLine.y : window->DC.CursorPos.y; - const float line_height = ImMax(window->DC.CurrLineSize.y, /*ImMax(*/window->DC.CursorPos.y - line_y1/*, 0.0f)*/ + size.y + offset_to_match_baseline_y); - - // Always align ourselves on pixel boundaries - //if (g.IO.KeyAlt) window->DrawList->AddRect(window->DC.CursorPos, window->DC.CursorPos + ImVec2(size.x, line_height), IM_COL32(255,0,0,200)); // [DEBUG] - window->DC.CursorPosPrevLine.x = window->DC.CursorPos.x + size.x; - window->DC.CursorPosPrevLine.y = line_y1; - window->DC.CursorPos.x = IM_TRUNC(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x); // Next line - window->DC.CursorPos.y = IM_TRUNC(line_y1 + line_height + g.Style.ItemSpacing.y); // Next line - window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, window->DC.CursorPosPrevLine.x); - window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, window->DC.CursorPos.y - g.Style.ItemSpacing.y); - //if (g.IO.KeyAlt) window->DrawList->AddCircle(window->DC.CursorMaxPos, 3.0f, IM_COL32(255,0,0,255), 4); // [DEBUG] - - window->DC.PrevLineSize.y = line_height; - window->DC.CurrLineSize.y = 0.0f; - window->DC.PrevLineTextBaseOffset = ImMax(window->DC.CurrLineTextBaseOffset, text_baseline_y); - window->DC.CurrLineTextBaseOffset = 0.0f; - window->DC.IsSameLine = window->DC.IsSetPos = false; - - // Horizontal layout mode - if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) - SameLine(); -} - - bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGuiItemFlags extra_flags) { ImGuiContext& g = *GImGui; @@ -9910,6 +9823,78 @@ bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGu return true; } + +//----------------------------------------------------------------------------- +// [SECTION] LAYOUT +//----------------------------------------------------------------------------- +// - ItemSize() +// - SameLine() +// - GetCursorScreenPos() +// - SetCursorScreenPos() +// - GetCursorPos(), GetCursorPosX(), GetCursorPosY() +// - SetCursorPos(), SetCursorPosX(), SetCursorPosY() +// - GetCursorStartPos() +// - Indent() +// - Unindent() +// - SetNextItemWidth() +// - PushItemWidth() +// - PushMultiItemsWidths() +// - PopItemWidth() +// - CalcItemWidth() +// - CalcItemSize() +// - GetTextLineHeight() +// - GetTextLineHeightWithSpacing() +// - GetFrameHeight() +// - GetFrameHeightWithSpacing() +// - GetContentRegionMax() +// - GetContentRegionMaxAbs() [Internal] +// - GetContentRegionAvail(), +// - GetWindowContentRegionMin(), GetWindowContentRegionMax() +// - BeginGroup() +// - EndGroup() +// Also see in imgui_widgets: tab bars, and in imgui_tables: tables, columns. +//----------------------------------------------------------------------------- + +// Advance cursor given item size for layout. +// Register minimum needed size so it can extend the bounding box used for auto-fit calculation. +// See comments in ItemAdd() about how/why the size provided to ItemSize() vs ItemAdd() may often different. +// THIS IS IN THE PERFORMANCE CRITICAL PATH. +void ImGui::ItemSize(const ImVec2& size, float text_baseline_y) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->SkipItems) + return; + + // We increase the height in this function to accommodate for baseline offset. + // In theory we should be offsetting the starting position (window->DC.CursorPos), that will be the topic of a larger refactor, + // but since ItemSize() is not yet an API that moves the cursor (to handle e.g. wrapping) enlarging the height has the same effect. + const float offset_to_match_baseline_y = (text_baseline_y >= 0) ? ImMax(0.0f, window->DC.CurrLineTextBaseOffset - text_baseline_y) : 0.0f; + + const float line_y1 = window->DC.IsSameLine ? window->DC.CursorPosPrevLine.y : window->DC.CursorPos.y; + const float line_height = ImMax(window->DC.CurrLineSize.y, /*ImMax(*/window->DC.CursorPos.y - line_y1/*, 0.0f)*/ + size.y + offset_to_match_baseline_y); + + // Always align ourselves on pixel boundaries + //if (g.IO.KeyAlt) window->DrawList->AddRect(window->DC.CursorPos, window->DC.CursorPos + ImVec2(size.x, line_height), IM_COL32(255,0,0,200)); // [DEBUG] + window->DC.CursorPosPrevLine.x = window->DC.CursorPos.x + size.x; + window->DC.CursorPosPrevLine.y = line_y1; + window->DC.CursorPos.x = IM_TRUNC(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x); // Next line + window->DC.CursorPos.y = IM_TRUNC(line_y1 + line_height + g.Style.ItemSpacing.y); // Next line + window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, window->DC.CursorPosPrevLine.x); + window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, window->DC.CursorPos.y - g.Style.ItemSpacing.y); + //if (g.IO.KeyAlt) window->DrawList->AddCircle(window->DC.CursorMaxPos, 3.0f, IM_COL32(255,0,0,255), 4); // [DEBUG] + + window->DC.PrevLineSize.y = line_height; + window->DC.CurrLineSize.y = 0.0f; + window->DC.PrevLineTextBaseOffset = ImMax(window->DC.CurrLineTextBaseOffset, text_baseline_y); + window->DC.CurrLineTextBaseOffset = 0.0f; + window->DC.IsSameLine = window->DC.IsSetPos = false; + + // Horizontal layout mode + if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) + SameLine(); +} + // Gets back to previous line and continue with horizontal layout // offset_from_start_x == 0 : follow right after previous item // offset_from_start_x != 0 : align to specified x position (relative to window/group left) From d7c2a0e38f48e7d162405452dbb996438971b34c Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 17 Jan 2024 18:19:00 +0100 Subject: [PATCH 13/13] Shortcut(): fixed 8323a06 adding _Repeat to all Shortcut() calls. --- imgui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imgui.cpp b/imgui.cpp index d4c5e754c..6ceee2252 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -9432,7 +9432,7 @@ bool ImGui::Shortcut(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiInputFlags // Default repeat behavior for Shortcut() // So e.g. pressing Ctrl+W and releasing Ctrl while holding W will not trigger the W shortcut. - if ((flags & ImGuiInputFlags_RepeatUntilMask_) == 0) + if ((flags & ImGuiInputFlags_Repeat) != 0 && (flags & ImGuiInputFlags_RepeatUntilMask_) == 0) flags |= ImGuiInputFlags_RepeatUntilKeyModsChange; if (!IsKeyChordPressed(key_chord, owner_id, flags))