From c2125774c613470c30ac3e76ef354ade3fdec173 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=91=D1=80=D0=B0=D0=BD=D0=B8=D0=BC=D0=B8=D1=80=20=D0=9A?= =?UTF-8?q?=D0=B0=D1=80=D0=B0=D1=9F=D0=B8=D1=9B?= Date: Thu, 24 Nov 2022 07:27:33 -0800 Subject: [PATCH] Updated ImGui. --- 3rdparty/dear-imgui/imgui.cpp | 724 +++++++++++++++++++++----- 3rdparty/dear-imgui/imgui.h | 15 +- 3rdparty/dear-imgui/imgui_demo.cpp | 8 +- 3rdparty/dear-imgui/imgui_draw.cpp | 2 +- 3rdparty/dear-imgui/imgui_internal.h | 249 +++++++-- 3rdparty/dear-imgui/imgui_tables.cpp | 10 +- 3rdparty/dear-imgui/imgui_widgets.cpp | 101 ++-- 7 files changed, 891 insertions(+), 218 deletions(-) diff --git a/3rdparty/dear-imgui/imgui.cpp b/3rdparty/dear-imgui/imgui.cpp index e146b72c5..bd3f95d77 100644 --- a/3rdparty/dear-imgui/imgui.cpp +++ b/3rdparty/dear-imgui/imgui.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.89 WIP +// dear imgui, v1.90 WIP // (main code and documentation) // Help: @@ -80,7 +80,8 @@ CODE // [SECTION] DRAG AND DROP // [SECTION] LOGGING/CAPTURING // [SECTION] SETTINGS -// [SECTION] VIEWPORTS +// [SECTION] LOCALIZATION +// [SECTION] VIEWPORTS, PLATFORM WINDOWS // [SECTION] PLATFORM DEPENDENT HELPERS // [SECTION] METRICS/DEBUGGER WINDOW // [SECTION] DEBUG LOG WINDOW @@ -956,7 +957,7 @@ static const float NAV_WINDOWING_LIST_APPEAR_DELAY = 0.15f; // Time // Window resizing from edges (when io.ConfigWindowsResizeFromEdges = true and ImGuiBackendFlags_HasMouseCursors is set in io.BackendFlags by backend) static const float WINDOWS_HOVER_PADDING = 4.0f; // Extend outside window for hovering/resizing (maxxed with TouchPadding) and inside windows for borders. Affect FindHoveredWindow(). 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.80f; // Lock scrolled window (so it doesn't pick child windows that are scrolling through) for a certain time, unless mouse moved. +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. //------------------------------------------------------------------------- // [SECTION] FORWARD DECLARATIONS @@ -3548,7 +3549,7 @@ void ImGui::SetActiveID(ImGuiID id, ImGuiWindow* window) // Clear declaration of inputs claimed by the widget // (Please note that this is WIP and not all keys/inputs are thoroughly declared by all widgets yet) g.ActiveIdUsingNavDirMask = 0x00; - g.ActiveIdUsingKeyInputMask.ClearAllBits(); + g.ActiveIdUsingAllKeyboardKeys = false; #ifndef IMGUI_DISABLE_OBSOLETE_KEYIO g.ActiveIdUsingNavInputMask = 0x00; #endif @@ -3564,7 +3565,6 @@ void ImGui::SetHoveredID(ImGuiID id) ImGuiContext& g = *GImGui; g.HoveredId = id; g.HoveredIdAllowOverlap = false; - g.HoveredIdUsingMouseWheel = false; if (id != 0 && g.HoveredIdPreviousFrame != id) g.HoveredIdTimer = g.HoveredIdNotActiveTimer = 0.0f; } @@ -4119,6 +4119,45 @@ static void UpdateAliasKey(ImGuiKey key, bool v, float analog_value) key_data->AnalogValue = analog_value; } +// Rewrite routing data buffers to strip old entries + sort by key to make queries not touch scattered data. +// Entries D,A,B,B,A,C,B --> A,A,B,B,B,C,D +// Index A:1 B:2 C:5 D:0 --> A:0 B:2 C:5 D:6 +// See 'Metrics->Key Owners & Shortcut Routing' to visualize the result of that operation. +static void UpdateKeyRoutingTable(ImGuiKeyRoutingTable* rt) +{ + ImGuiContext& g = *GImGui; + rt->EntriesNext.resize(0); + for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1)) + { + const int new_routing_start_idx = rt->EntriesNext.Size; + ImGuiKeyRoutingData* routing_entry; + for (int old_routing_idx = rt->Index[key - ImGuiKey_NamedKey_BEGIN]; old_routing_idx != -1; old_routing_idx = routing_entry->NextEntryIndex) + { + routing_entry = &rt->Entries[old_routing_idx]; + routing_entry->RoutingCurr = routing_entry->RoutingNext; // Update entry + routing_entry->RoutingNext = ImGuiKeyOwner_None; + routing_entry->RoutingNextScore = 255; + if (routing_entry->RoutingCurr == ImGuiKeyOwner_None) + continue; + rt->EntriesNext.push_back(*routing_entry); // Write alive ones into new buffer + + // Apply routing to owner if there's no owner already (RoutingCurr == None at this point) + if (routing_entry->Mods == g.IO.KeyMods) + { + ImGuiKeyOwnerData* owner_data = ImGui::GetKeyOwnerData(key); + if (owner_data->OwnerCurr == ImGuiKeyOwner_None) + owner_data->OwnerCurr = routing_entry->RoutingCurr; + } + } + + // Rewrite linked-list + rt->Index[key - ImGuiKey_NamedKey_BEGIN] = (ImGuiKeyRoutingIndex)(new_routing_start_idx < rt->EntriesNext.Size ? new_routing_start_idx : -1); + for (int n = new_routing_start_idx; n < rt->EntriesNext.Size; n++) + rt->EntriesNext[n].NextEntryIndex = (ImGuiKeyRoutingIndex)((n + 1 < rt->EntriesNext.Size) ? n + 1 : -1); + } + rt->Entries.swap(rt->EntriesNext); // Swap new and old indexes +} + // [Internal] Do not use directly (should read io.KeyMods instead) static ImGuiKeyChord GetMergedModsFromBools() { @@ -4220,12 +4259,25 @@ static void ImGui::UpdateKeyboardInputs() } // Update keys - for (int i = 0; i < IM_ARRAYSIZE(io.KeysData); i++) + for (int i = 0; i < ImGuiKey_KeysData_SIZE; i++) { ImGuiKeyData* key_data = &io.KeysData[i]; key_data->DownDurationPrev = key_data->DownDuration; key_data->DownDuration = key_data->Down ? (key_data->DownDuration < 0.0f ? 0.0f : key_data->DownDuration + io.DeltaTime) : -1.0f; } + + // Update keys/input owner (named keys only): one entry per key + for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1)) + { + ImGuiKeyData* key_data = &io.KeysData[key - ImGuiKey_KeysData_OFFSET]; + ImGuiKeyOwnerData* owner_data = &g.KeysOwnerData[key - ImGuiKey_NamedKey_BEGIN]; + owner_data->OwnerCurr = owner_data->OwnerNext; + if (!key_data->Down) // Important: ownership is released on the frame after a release. Ensure a 'MouseDown -> CloseWindow -> MouseUp' chain doesn't lead to someone else seeing the MouseUp. + owner_data->OwnerNext = ImGuiKeyOwner_None; + owner_data->LockThisFrame = owner_data->LockUntilRelease = owner_data->LockUntilRelease && key_data->Down; // Clear LockUntilRelease when key is not Down anymore + } + + UpdateKeyRoutingTable(&g.KeysRoutingTable); } static void ImGui::UpdateMouseInputs() @@ -4289,10 +4341,13 @@ static void ImGui::UpdateMouseInputs() } } -static void LockWheelingWindow(ImGuiWindow* window) +static void LockWheelingWindow(ImGuiWindow* window, float wheel_amount) { ImGuiContext& g = *GImGui; - g.WheelingWindowReleaseTimer = window ? WINDOWS_MOUSE_WHEEL_SCROLL_LOCK_TIMER : 0.0f; + if (window) + g.WheelingWindowReleaseTimer = ImMin(g.WheelingWindowReleaseTimer + ImAbs(wheel_amount) * WINDOWS_MOUSE_WHEEL_SCROLL_LOCK_TIMER, WINDOWS_MOUSE_WHEEL_SCROLL_LOCK_TIMER); + else + g.WheelingWindowReleaseTimer = 0.0f; if (g.WheelingWindow == window) return; IMGUI_DEBUG_LOG_IO("LockWheelingWindow() \"%s\"\n", window ? window->Name : "NULL"); @@ -4311,16 +4366,12 @@ void ImGui::UpdateMouseWheel() if (IsMousePosValid() && ImLengthSqr(g.IO.MousePos - g.WheelingWindowRefMousePos) > g.IO.MouseDragThreshold * g.IO.MouseDragThreshold) g.WheelingWindowReleaseTimer = 0.0f; if (g.WheelingWindowReleaseTimer <= 0.0f) - LockWheelingWindow(NULL); + LockWheelingWindow(NULL, 0.0f); } - const bool hovered_id_using_mouse_wheel = (g.HoveredIdPreviousFrame != 0 && g.HoveredIdPreviousFrameUsingMouseWheel); - const bool active_id_using_mouse_wheel_x = g.ActiveIdUsingKeyInputMask.TestBit(ImGuiKey_MouseWheelX); - const bool active_id_using_mouse_wheel_y = g.ActiveIdUsingKeyInputMask.TestBit(ImGuiKey_MouseWheelY); - ImVec2 wheel; - wheel.x = (!hovered_id_using_mouse_wheel && !active_id_using_mouse_wheel_x) ? g.IO.MouseWheelH : 0.0f; - wheel.y = (!hovered_id_using_mouse_wheel && !active_id_using_mouse_wheel_y) ? g.IO.MouseWheel : 0; + wheel.x = TestKeyOwner(ImGuiKey_MouseWheelX, ImGuiKeyOwner_None) ? g.IO.MouseWheelH : 0.0f; + wheel.y = TestKeyOwner(ImGuiKey_MouseWheelY, ImGuiKeyOwner_None) ? g.IO.MouseWheel : 0.0f; if (wheel.x == 0.0f && wheel.y == 0.0f) return; @@ -4333,7 +4384,7 @@ void ImGui::UpdateMouseWheel() // FIXME-OBSOLETE: This is an old feature, it still works but pretty much nobody is using it and may be best redesigned. if (wheel.y != 0.0f && g.IO.KeyCtrl && g.IO.FontAllowUserScaling) { - LockWheelingWindow(mouse_window); + LockWheelingWindow(mouse_window, wheel.y); ImGuiWindow* window = mouse_window; const float new_font_scale = ImClamp(window->FontWindowScale + g.IO.MouseWheel * 0.10f, 0.50f, 2.50f); const float scale = new_font_scale / window->FontWindowScale; @@ -4372,7 +4423,7 @@ void ImGui::UpdateMouseWheel() window = window->ParentWindow; if (!(window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(window->Flags & ImGuiWindowFlags_NoMouseInputs)) { - LockWheelingWindow(mouse_window); + LockWheelingWindow(mouse_window, wheel.y); float max_step = window->InnerRect.GetHeight() * 0.67f; float scroll_step = ImFloor(ImMin(5 * window->CalcFontSize(), max_step)); SetScrollY(window, window->Scroll.y - wheel.y * scroll_step); @@ -4387,7 +4438,7 @@ void ImGui::UpdateMouseWheel() window = window->ParentWindow; if (!(window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(window->Flags & ImGuiWindowFlags_NoMouseInputs)) { - LockWheelingWindow(mouse_window); + LockWheelingWindow(mouse_window, wheel.x); float max_step = window->InnerRect.GetWidth() * 0.67f; float scroll_step = ImFloor(ImMin(2 * window->CalcFontSize(), max_step)); SetScrollX(window, window->Scroll.x - wheel.x * scroll_step); @@ -4548,10 +4599,8 @@ void ImGui::NewFrame() if (g.HoveredId && g.ActiveId != g.HoveredId) g.HoveredIdNotActiveTimer += g.IO.DeltaTime; g.HoveredIdPreviousFrame = g.HoveredId; - g.HoveredIdPreviousFrameUsingMouseWheel = g.HoveredIdUsingMouseWheel; g.HoveredId = 0; g.HoveredIdAllowOverlap = false; - g.HoveredIdUsingMouseWheel = false; g.HoveredIdDisabled = false; // Clear ActiveID if the item is not alive anymore. @@ -4579,7 +4628,10 @@ void ImGui::NewFrame() if (g.ActiveId == 0) { g.ActiveIdUsingNavDirMask = 0x00; - g.ActiveIdUsingKeyInputMask.ClearAllBits(); + g.ActiveIdUsingAllKeyboardKeys = false; +#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO + g.ActiveIdUsingNavInputMask = 0x00; +#endif } #ifndef IMGUI_DISABLE_OBSOLETE_KEYIO @@ -4588,9 +4640,9 @@ void ImGui::NewFrame() else if (g.ActiveIdUsingNavInputMask != 0) { // If your custom widget code used: { g.ActiveIdUsingNavInputMask |= (1 << ImGuiNavInput_Cancel); } - // Since IMGUI_VERSION_NUM >= 18804 it should be: { SetActiveIdUsingKey(ImGuiKey_Escape); SetActiveIdUsingKey(ImGuiKey_NavGamepadCancel); } + // Since IMGUI_VERSION_NUM >= 18804 it should be: { SetKeyOwner(ImGuiKey_Escape, g.ActiveId); SetKeyOwner(ImGuiKey_NavGamepadCancel, g.ActiveId); } if (g.ActiveIdUsingNavInputMask & (1 << ImGuiNavInput_Cancel)) - SetActiveIdUsingKey(ImGuiKey_Escape); + SetKeyOwner(ImGuiKey_Escape, g.ActiveId); if (g.ActiveIdUsingNavInputMask & ~(1 << ImGuiNavInput_Cancel)) IM_ASSERT(0); // Other values unsupported } @@ -4709,6 +4761,8 @@ void ImGui::NewFrame() // [DEBUG] Update debug features UpdateDebugToolItemPicker(); UpdateDebugToolStackQueries(); + if (g.DebugLocateFrames > 0 && --g.DebugLocateFrames == 0) + g.DebugLocateId = 0; // 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. @@ -4721,12 +4775,24 @@ void ImGui::NewFrame() CallContextHooks(&g, ImGuiContextHookType_NewFramePost); } +// IMPORTANT: ###xxx suffixes must be same in ALL languages +static const ImGuiLocEntry GLocalizationEntriesEnUS[] = +{ + { ImGuiLocKey_TableSizeOne, "Size column to fit###SizeOne" }, + { ImGuiLocKey_TableSizeAllFit, "Size all columns to fit###SizeAll" }, + { ImGuiLocKey_TableSizeAllDefault, "Size all columns to default###SizeAll" }, + { ImGuiLocKey_TableResetOrder, "Reset order###ResetOrder" }, + { ImGuiLocKey_WindowingMainMenuBar, "(Main menu bar)" }, + { ImGuiLocKey_WindowingPopup, "(Popup)" }, + { ImGuiLocKey_WindowingUntitled, "(Untitled)" }, +}; + void ImGui::Initialize() { ImGuiContext& g = *GImGui; IM_ASSERT(!g.Initialized && !g.SettingsLoaded); - // Add .ini handle for ImGuiWindow type + // Add .ini handle for ImGuiWindow and ImGuiTable types { ImGuiSettingsHandler ini_handler; ini_handler.TypeName = "Window"; @@ -4738,10 +4804,11 @@ void ImGui::Initialize() ini_handler.WriteAllFn = WindowSettingsHandler_WriteAll; AddSettingsHandler(&ini_handler); } - - // Add .ini handle for ImGuiTable type TableSettingsAddSettingsHandler(); + // Setup default localization table + LocalizeRegisterEntries(GLocalizationEntriesEnUS, IM_ARRAYSIZE(GLocalizationEntriesEnUS)); + // Create default viewport ImGuiViewportP* viewport = IM_NEW(ImGuiViewportP)(); g.Viewports.push_back(viewport); @@ -4787,6 +4854,9 @@ void ImGui::Shutdown() g.HoveredWindow = g.HoveredWindowUnderMovingWindow = NULL; g.ActiveIdWindow = g.ActiveIdPreviousFrameWindow = NULL; g.MovingWindow = NULL; + + g.KeysRoutingTable.Clear(); + g.ColorStack.clear(); g.StyleVarStack.clear(); g.FontStack.clear(); @@ -5388,29 +5458,13 @@ void ImGui::SetItemAllowOverlap() g.ActiveIdAllowOverlap = true; } -void ImGui::SetItemUsingMouseWheel() -{ - ImGuiContext& g = *GImGui; - ImGuiID id = g.LastItemData.ID; - if (g.HoveredId == id) - g.HoveredIdUsingMouseWheel = true; - if (g.ActiveId == id) - { - g.ActiveIdUsingKeyInputMask.SetBit(ImGuiKey_MouseWheelX); - g.ActiveIdUsingKeyInputMask.SetBit(ImGuiKey_MouseWheelY); - } -} - +// FIXME: It might be undesirable that this will likely disable KeyOwner-aware shortcuts systems. Consider a more fine-tuned version for the two users of this function. void ImGui::SetActiveIdUsingAllKeyboardKeys() { ImGuiContext& g = *GImGui; IM_ASSERT(g.ActiveId != 0); g.ActiveIdUsingNavDirMask = (1 << ImGuiDir_COUNT) - 1; - g.ActiveIdUsingKeyInputMask.SetBitRange(ImGuiKey_Keyboard_BEGIN, ImGuiKey_Keyboard_END); - //g.ActiveIdUsingKeyInputMask.SetBit(ImGuiKey_ModCtrl); - //g.ActiveIdUsingKeyInputMask.SetBit(ImGuiKey_ModShift); - //g.ActiveIdUsingKeyInputMask.SetBit(ImGuiKey_ModAlt); - //g.ActiveIdUsingKeyInputMask.SetBit(ImGuiKey_ModSuper); + g.ActiveIdUsingAllKeyboardKeys = true; NavMoveRequestCancel(); } @@ -5734,8 +5788,7 @@ static ImVec2 CalcWindowAutoFitSize(ImGuiWindow* window, const ImVec2& size_cont if (is_popup || is_menu) // Popups and menus bypass style.WindowMinSize by default, but we give then a non-zero minimum size to facilitate understanding problematic cases (e.g. empty popups) size_min = ImMin(size_min, ImVec2(4.0f, 4.0f)); - // FIXME-VIEWPORT-WORKAREA: May want to use GetWorkSize() instead of Size depending on the type of windows? - ImVec2 avail_size = ImGui::GetMainViewport()->Size; + ImVec2 avail_size = ImGui::GetMainViewport()->WorkSize; ImVec2 size_auto_fit = ImClamp(size_desired, size_min, ImMax(size_min, avail_size - style.DisplaySafeAreaPadding * 2.0f)); // When the window cannot fit all contents (either because of constraints, either because screen is too small), @@ -5990,7 +6043,7 @@ static bool ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& s return ret_auto_fit; } -static inline void ClampWindowRect(ImGuiWindow* window, const ImRect& visibility_rect) +static inline void ClampWindowPos(ImGuiWindow* window, const ImRect& visibility_rect) { ImGuiContext& g = *GImGui; ImVec2 size_for_clamping = window->Size; @@ -6322,10 +6375,22 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window_stack_data.ParentLastItemDataBackup = g.LastItemData; window_stack_data.StackSizesOnBegin.SetToCurrentState(); g.CurrentWindowStack.push_back(window_stack_data); - g.CurrentWindow = NULL; if (flags & ImGuiWindowFlags_ChildMenu) g.BeginMenuCount++; + // Update ->RootWindow and others pointers (before any possible call to FocusWindow) + if (first_begin_of_the_frame) + { + UpdateWindowParentAndRootLinks(window, flags, parent_window); + window->ParentWindowInBeginStack = parent_window_in_stack; + } + + // Add to focus scope stack + PushFocusScope(window->ID); + window->NavRootFocusScopeId = g.CurrentFocusScopeId; + g.CurrentWindow = NULL; + + // Add to popup stack if (flags & ImGuiWindowFlags_Popup) { ImGuiPopupData& popup_ref = g.OpenPopupStack[g.BeginPopupStack.Size]; @@ -6335,13 +6400,6 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->PopupId = popup_ref.PopupId; } - // Update ->RootWindow and others pointers (before any possible call to FocusWindow) - if (first_begin_of_the_frame) - { - UpdateWindowParentAndRootLinks(window, flags, parent_window); - window->ParentWindowInBeginStack = parent_window_in_stack; - } - // Process SetNextWindow***() calls // (FIXME: Consider splitting the HasXXX flags into X/Y components bool window_pos_set_by_api = false; @@ -6579,9 +6637,9 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // Clamp position/size so window stays visible within its viewport or monitor // Ignore zero-sized display explicitly to avoid losing positions if a window manager reports zero-sized window when initializing or minimizing. - if (!window_pos_set_by_api && !(flags & ImGuiWindowFlags_ChildWindow) && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0) + if (!window_pos_set_by_api && !(flags & ImGuiWindowFlags_ChildWindow)) if (viewport_rect.GetWidth() > 0.0f && viewport_rect.GetHeight() > 0.0f) - ClampWindowRect(window, visibility_rect); + ClampWindowPos(window, visibility_rect); window->Pos = ImFloor(window->Pos); // Lock window rounding for the frame (so that altering them doesn't cause inconsistencies) @@ -6852,9 +6910,17 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // This is useful to allow creating context menus on title bar only, etc. SetLastItemData(window->MoveId, g.CurrentItemFlags, IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max, false) ? ImGuiItemStatusFlags_HoveredRect : 0, title_bar_rect); + // [DEBUG] +#ifndef IMGUI_DISABLE_DEBUG_TOOLS + if (g.DebugLocateId != 0 && (window->ID == g.DebugLocateId || window->MoveId == g.DebugLocateId)) + DebugLocateItemResolveWithLastItem(); +#endif + // [Test Engine] Register title bar / tab +#ifdef IMGUI_ENABLE_TEST_ENGINE if (!(window->Flags & ImGuiWindowFlags_NoTitleBar)) IMGUI_TEST_ENGINE_ITEM_ADD(g.LastItemData.Rect, g.LastItemData.ID); +#endif } else { @@ -6862,9 +6928,6 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) SetCurrentWindow(window); } - // Pull/inherit current state - window->DC.NavFocusScopeIdCurrent = (flags & ImGuiWindowFlags_ChildWindow) ? parent_window->DC.NavFocusScopeIdCurrent : window->GetID("#FOCUSSCOPE"); // Inherit from parent only // -V595 - PushClipRect(window->InnerClipRect.Min, window->InnerClipRect.Max, true); // Clear 'accessed' flag last thing (After PushClipRect which will set the flag. We want the flag to stay false when the default "Debug" window is unused) @@ -6942,6 +7005,7 @@ void ImGui::End() if (window->DC.CurrentColumns) EndColumns(); PopClipRect(); // Inner window clip rectangle + PopFocusScope(); // Stop logging if (!(window->Flags & ImGuiWindowFlags_ChildWindow)) // FIXME: add more options for scope of logging @@ -7051,7 +7115,7 @@ void ImGui::FocusWindow(ImGuiWindow* window) g.NavMousePosDirty = true; g.NavId = window ? window->NavLastIds[0] : 0; // Restore NavId g.NavLayer = ImGuiNavLayer_Main; - g.NavFocusScopeId = 0; + g.NavFocusScopeId = window ? window->NavRootFocusScopeId : 0; g.NavIdIsAlive = false; // Close popups if any @@ -7622,18 +7686,16 @@ void ImGui::ActivateItem(ImGuiID id) void ImGui::PushFocusScope(ImGuiID id) { ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - g.FocusScopeStack.push_back(window->DC.NavFocusScopeIdCurrent); - window->DC.NavFocusScopeIdCurrent = id; + g.FocusScopeStack.push_back(id); + g.CurrentFocusScopeId = id; } void ImGui::PopFocusScope() { ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; IM_ASSERT(g.FocusScopeStack.Size > 0); // Too many PopFocusScope() ? - window->DC.NavFocusScopeIdCurrent = g.FocusScopeStack.back(); g.FocusScopeStack.pop_back(); + g.CurrentFocusScopeId = g.FocusScopeStack.Size ? g.FocusScopeStack.back() : 0; } // Note: this will likely be called ActivateItem() once we rework our Focus/Activation system! @@ -7942,7 +8004,7 @@ int ImGui::GetKeyPressedAmount(ImGuiKey key, float repeat_delay, float repeat_ra { ImGuiContext& g = *GImGui; const ImGuiKeyData* key_data = GetKeyData(key); - if (!key_data->Down) // In theory this should already be encoded as (DownDuration < 0.0f), but testing this facilitates eating mechanism (until we finish work on input ownership) + if (!key_data->Down) // In theory this should already be encoded as (DownDuration < 0.0f), but testing this facilitates eating mechanism (until we finish work on key ownership) return 0; const float t = key_data->DownDuration; return CalcTypematicRepeatAmount(t - g.IO.DeltaTime, t, repeat_delay, repeat_rate); @@ -7956,31 +8018,177 @@ ImVec2 ImGui::GetKeyVector2d(ImGuiKey key_left, ImGuiKey key_right, ImGuiKey key GetKeyData(key_down)->AnalogValue - GetKeyData(key_up)->AnalogValue); } +// owner_id may be None/Any, but routing_id needs to be always be set, so we default to GetCurrentFocusScope(). +static inline ImGuiID GetRoutingIdFromOwnerId(ImGuiID owner_id) +{ + ImGuiContext& g = *GImGui; + return (owner_id != ImGuiKeyOwner_None && owner_id != ImGuiKeyOwner_Any) ? owner_id : g.CurrentFocusScopeId; +} + +ImGuiKeyRoutingData* ImGui::GetShortcutRoutingData(ImGuiKeyChord key_chord) +{ + // Majority of shortcuts will be Key + any number of Mods + // We accept _Single_ mod with ImGuiKey_None. + // - Shortcut(ImGuiKey_S | ImGuiMod_Ctrl); // Legal + // - Shortcut(ImGuiKey_S | ImGuiMod_Ctrl | ImGuiMod_Shift); // Legal + // - Shortcut(ImGuiMod_Ctrl); // Legal + // - Shortcut(ImGuiMod_Ctrl | ImGuiMod_Shift); // Not legal + ImGuiContext& g = *GImGui; + ImGuiKeyRoutingTable* rt = &g.KeysRoutingTable; + ImGuiKeyRoutingData* routing_data; + ImGuiKey key = (ImGuiKey)(key_chord & ~ImGuiMod_Mask_); + ImGuiKey mods = (ImGuiKey)(key_chord & ImGuiMod_Mask_); + if (key == ImGuiKey_None) + key = ConvertSingleModFlagToKey(mods); + IM_ASSERT(IsNamedKey(key)); + + // 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). + for (ImGuiKeyRoutingIndex idx = rt->Index[key - ImGuiKey_NamedKey_BEGIN]; idx != -1; idx = routing_data->NextEntryIndex) + { + routing_data = &rt->Entries[idx]; + if (routing_data->Mods == mods) + return routing_data; + } + + // Add to linked-list + ImGuiKeyRoutingIndex routing_data_idx = (ImGuiKeyRoutingIndex)rt->Entries.Size; + rt->Entries.push_back(ImGuiKeyRoutingData()); + routing_data = &rt->Entries[routing_data_idx]; + routing_data->Mods = (ImU16)mods; + routing_data->NextEntryIndex = rt->Index[key - ImGuiKey_NamedKey_BEGIN]; // Setup linked list + rt->Index[key - ImGuiKey_NamedKey_BEGIN] = routing_data_idx; + return routing_data; +} + +// Current score encoding (lower is highest priority): +// - 0: ImGuiInputFlags_RouteGlobalHigh +// - 1: ImGuiInputFlags_RouteFocused (if item active) +// - 2: ImGuiInputFlags_RouteGlobal +// - 3+: ImGuiInputFlags_RouteFocused (if window in focus-stack) +// - 254: ImGuiInputFlags_RouteGlobalLow +// - 255: never route +// 'flags' should include an explicit routing policy +static int CalcRoutingScore(ImGuiWindow* location, ImGuiID owner_id, ImGuiInputFlags flags) +{ + if (flags & ImGuiInputFlags_RouteFocused) + { + ImGuiContext& g = *GImGui; + ImGuiWindow* focused = g.NavWindow; + + // ActiveID gets top priority + // (we don't check g.ActiveIdUsingAllKeys here. Routing is applied but if input ownership is tested later it may discard it) + if (owner_id != 0 && g.ActiveId == owner_id) + return 1; + + // Score based on distance to focused window (lower is better) + // Assuming both windows are submitting a routing request, + // - When Window....... is focused -> Window scores 3 (best), Window/ChildB scores 255 (no match) + // - 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. + if (focused != NULL && focused->RootWindow == location->RootWindow) + for (int next_score = 3; focused != NULL; next_score++) + { + if (focused == location) + { + IM_ASSERT(next_score < 255); + return next_score; + } + focused = (focused->RootWindow != focused) ? focused->ParentWindow : NULL; // FIXME: This could be later abstracted as a focus path + } + return 255; + } + + // ImGuiInputFlags_RouteGlobalHigh is default, so calls without flags are not conditional + if (flags & ImGuiInputFlags_RouteGlobal) + return 2; + if (flags & ImGuiInputFlags_RouteGlobalLow) + return 254; + return 0; +} + +// 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. +// (Conceptually this does a "Submit for next frame" + "Test for current frame". +// As such, it could be called TrySetXXX or SubmitXXX, or the Submit and Test operations should be separate.) +// - Using 'owner_id == ImGuiKeyOwner_Any/0': auto-assign an owner based on current focus scope (each window has its focus scope by default) +// - Using 'owner_id == ImGuiKeyOwner_None': allows disabling/locking a shortcut. +bool ImGui::SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiInputFlags flags) +{ + ImGuiContext& g = *GImGui; + if ((flags & ImGuiInputFlags_RouteMask_) == 0) + 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 (flags & ImGuiInputFlags_RouteUnlessBgFocused) + if (g.NavWindow == NULL) + return false; + if (flags & ImGuiInputFlags_RouteAlways) + return true; + + const int score = CalcRoutingScore(g.CurrentWindow, owner_id, flags); + if (score == 255) + return false; + + // Submit routing for NEXT frame (assuming score is sufficient) + // FIXME: Could expose a way to use a "serve last" policy for same score resolution (using <= instead of <). + ImGuiKeyRoutingData* routing_data = GetShortcutRoutingData(key_chord); + const ImGuiID routing_id = GetRoutingIdFromOwnerId(owner_id); + //const bool set_route = (flags & ImGuiInputFlags_ServeLast) ? (score <= routing_data->RoutingNextScore) : (score < routing_data->RoutingNextScore); + if (score < routing_data->RoutingNextScore) + { + routing_data->RoutingNext = routing_id; + routing_data->RoutingNextScore = (ImU8)score; + } + + // Return routing state for CURRENT frame + return routing_data->RoutingCurr == routing_id; +} + +// Currently unused by core (but used by tests) +// Note: this cannot be turned into GetShortcutRouting() because we do the owner_id->routing_id translation, name would be more misleading. +bool ImGui::TestShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id) +{ + const ImGuiID routing_id = GetRoutingIdFromOwnerId(owner_id); + ImGuiKeyRoutingData* routing_data = GetShortcutRoutingData(key_chord); + return routing_data->RoutingCurr == routing_id; +} + // Note that Dear ImGui doesn't know the meaning/semantic of ImGuiKey from 0..511: they are legacy native keycodes. // Consider transitioning from 'IsKeyDown(MY_ENGINE_KEY_A)' (<1.87) to IsKeyDown(ImGuiKey_A) (>= 1.87) bool ImGui::IsKeyDown(ImGuiKey key) +{ + return IsKeyDown(key, ImGuiKeyOwner_Any); +} + +bool ImGui::IsKeyDown(ImGuiKey key, ImGuiID owner_id) { const ImGuiKeyData* key_data = GetKeyData(key); if (!key_data->Down) return false; + if (!TestKeyOwner(key, owner_id)) + return false; return true; } bool ImGui::IsKeyPressed(ImGuiKey key, bool repeat) { - return IsKeyPressedEx(key, repeat ? ImGuiInputFlags_Repeat : ImGuiInputFlags_None); + return IsKeyPressed(key, ImGuiKeyOwner_Any, repeat ? ImGuiInputFlags_Repeat : ImGuiInputFlags_None); } -// Important: unlike legacy IsKeyPressed(ImGuiKey, bool repeat=true) which DEFAULT to repeat, this requires EXPLICIT repeat. -// [Internal] 2022/07: Do not call this directly! It is a temporary entry point which we will soon replace with an overload for IsKeyPressed() when we introduce key ownership. -bool ImGui::IsKeyPressedEx(ImGuiKey key, ImGuiInputFlags flags) +// Important: unless legacy IsKeyPressed(ImGuiKey, bool repeat=true) which DEFAULT to repeat, this requires EXPLICIT repeat. +bool ImGui::IsKeyPressed(ImGuiKey key, ImGuiID owner_id, ImGuiInputFlags flags) { const ImGuiKeyData* key_data = GetKeyData(key); - if (!key_data->Down) // In theory this should already be encoded as (DownDuration < 0.0f), but testing this facilitates eating mechanism (until we finish work on input ownership) + if (!key_data->Down) // In theory this should already be encoded as (DownDuration < 0.0f), but testing this facilitates eating mechanism (until we finish work on key ownership) return false; const float t = key_data->DownDuration; if (t < 0.0f) return false; + IM_ASSERT((flags & ~ImGuiInputFlags_SupportedByIsKeyPressed) == 0); // Passing flags not supported by this function! bool pressed = (t == 0.0f); if (!pressed && ((flags & ImGuiInputFlags_Repeat) != 0)) @@ -7989,17 +8197,25 @@ bool ImGui::IsKeyPressedEx(ImGuiKey key, ImGuiInputFlags flags) GetTypematicRepeatRate(flags, &repeat_delay, &repeat_rate); pressed = (t > repeat_delay) && GetKeyPressedAmount(key, repeat_delay, repeat_rate) > 0; } - if (!pressed) return false; + if (!TestKeyOwner(key, owner_id)) + return false; return true; } bool ImGui::IsKeyReleased(ImGuiKey key) +{ + return IsKeyReleased(key, ImGuiKeyOwner_Any); +} + +bool ImGui::IsKeyReleased(ImGuiKey key, ImGuiID owner_id) { const ImGuiKeyData* key_data = GetKeyData(key); if (key_data->DownDurationPrev < 0.0f || key_data->Down) return false; + if (!TestKeyOwner(key, owner_id)) + return false; return true; } @@ -8007,35 +8223,62 @@ bool ImGui::IsMouseDown(ImGuiMouseButton button) { ImGuiContext& g = *GImGui; IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); - return g.IO.MouseDown[button]; + return g.IO.MouseDown[button] && TestKeyOwner(MouseButtonToKey(button), ImGuiKeyOwner_Any); // should be same as IsKeyDown(MouseButtonToKey(button), ImGuiKeyOwner_Any), but this allows legacy code hijacking the io.Mousedown[] array. +} + +bool ImGui::IsMouseDown(ImGuiMouseButton button, ImGuiID owner_id) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + return g.IO.MouseDown[button] && TestKeyOwner(MouseButtonToKey(button), owner_id); // Should be same as IsKeyDown(MouseButtonToKey(button), owner_id), but this allows legacy code hijacking the io.Mousedown[] array. } bool ImGui::IsMouseClicked(ImGuiMouseButton button, bool repeat) +{ + return IsMouseClicked(button, ImGuiKeyOwner_Any, repeat ? ImGuiInputFlags_Repeat : ImGuiInputFlags_None); +} + +bool ImGui::IsMouseClicked(ImGuiMouseButton button, ImGuiID owner_id, ImGuiInputFlags flags) { ImGuiContext& g = *GImGui; IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); - if (!g.IO.MouseDown[button]) // In theory this should already be encoded as (DownDuration < 0.0f), but testing this facilitates eating mechanism (until we finish work on input ownership) + if (!g.IO.MouseDown[button]) // In theory this should already be encoded as (DownDuration < 0.0f), but testing this facilitates eating mechanism (until we finish work on key ownership) return false; const float t = g.IO.MouseDownDuration[button]; - if (t == 0.0f) - return true; - if (repeat && t > g.IO.KeyRepeatDelay) - return CalcTypematicRepeatAmount(t - g.IO.DeltaTime, t, g.IO.KeyRepeatDelay, g.IO.KeyRepeatRate) > 0; - return false; + if (t < 0.0f) + return false; + IM_ASSERT((flags & ~ImGuiInputFlags_SupportedByIsKeyPressed) == 0); // Passing flags not supported by this function! + + const bool repeat = (flags & ImGuiInputFlags_Repeat) != 0; + const bool pressed = (t == 0.0f) || (repeat && t > g.IO.KeyRepeatDelay && CalcTypematicRepeatAmount(t - g.IO.DeltaTime, t, g.IO.KeyRepeatDelay, g.IO.KeyRepeatRate) > 0); + if (!pressed) + return false; + + if (!TestKeyOwner(MouseButtonToKey(button), owner_id)) + return false; + + return true; } bool ImGui::IsMouseReleased(ImGuiMouseButton button) { ImGuiContext& g = *GImGui; IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); - return g.IO.MouseReleased[button]; + return g.IO.MouseReleased[button] && TestKeyOwner(MouseButtonToKey(button), ImGuiKeyOwner_Any); // Should be same as IsKeyReleased(MouseButtonToKey(button), ImGuiKeyOwner_Any) +} + +bool ImGui::IsMouseReleased(ImGuiMouseButton button, ImGuiID owner_id) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + return g.IO.MouseReleased[button] && TestKeyOwner(MouseButtonToKey(button), owner_id); // Should be same as IsKeyReleased(MouseButtonToKey(button), owner_id) } bool ImGui::IsMouseDoubleClicked(ImGuiMouseButton button) { ImGuiContext& g = *GImGui; IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); - return g.IO.MouseClickedCount[button] == 2; + return g.IO.MouseClickedCount[button] == 2 && TestKeyOwner(MouseButtonToKey(button), ImGuiKeyOwner_Any); } int ImGui::GetMouseClickedCount(ImGuiMouseButton button) @@ -8125,6 +8368,10 @@ void ImGui::ResetMouseDragDelta(ImGuiMouseButton button) g.IO.MouseClickedPos[button] = g.IO.MousePos; } +// Get desired mouse cursor shape. +// Important: this is meant to be used by a platform backend, it is reset in ImGui::NewFrame(), +// updated during the frame, and locked in EndFrame()/Render(). +// If you use software rendering by setting io.MouseDrawCursor then Dear ImGui will render those for you ImGuiMouseCursor ImGui::GetMouseCursor() { ImGuiContext& g = *GImGui; @@ -8297,6 +8544,122 @@ void ImGui::UpdateInputEvents(bool trickle_fast_inputs) g.IO.ClearInputKeys(); } +ImGuiID ImGui::GetKeyOwner(ImGuiKey key) +{ + if (!IsNamedKeyOrModKey(key)) + return ImGuiKeyOwner_None; + + ImGuiContext& g = *GImGui; + ImGuiKeyOwnerData* owner_data = GetKeyOwnerData(key); + ImGuiID owner_id = owner_data->OwnerCurr; + + if (g.ActiveIdUsingAllKeyboardKeys && owner_id != g.ActiveId && owner_id != ImGuiKeyOwner_Any) + if (key >= ImGuiKey_Keyboard_BEGIN && key < ImGuiKey_Keyboard_END) + return ImGuiKeyOwner_None; + + return owner_id; +} + +// TestKeyOwner(..., ID) : (owner == None || owner == ID) +// TestKeyOwner(..., None) : (owner == None) +// TestKeyOwner(..., Any) : no owner test +// All paths are also testing for key not being locked, for the rare cases that key have been locked with using ImGuiInputFlags_LockXXX flags. +bool ImGui::TestKeyOwner(ImGuiKey key, ImGuiID owner_id) +{ + if (!IsNamedKeyOrModKey(key)) + return true; + + ImGuiContext& g = *GImGui; + if (g.ActiveIdUsingAllKeyboardKeys && owner_id != g.ActiveId && owner_id != ImGuiKeyOwner_Any) + if (key >= ImGuiKey_Keyboard_BEGIN && key < ImGuiKey_Keyboard_END) + return false; + + ImGuiKeyOwnerData* owner_data = GetKeyOwnerData(key); + if (owner_id == ImGuiKeyOwner_Any) + return (owner_data->LockThisFrame == false); + + // Note: SetKeyOwner() sets OwnerCurr. It is not strictly required for most mouse routing overlap (because of ActiveId/HoveredId + // are acting as filter before this has a chance to filter), but sane as soon as user tries to look into things. + // Setting OwnerCurr in SetKeyOwner() is more consistent than testing OwnerNext here: would be inconsistent with getter and other functions. + if (owner_data->OwnerCurr != owner_id) + { + if (owner_data->LockThisFrame) + return false; + if (owner_data->OwnerCurr != ImGuiKeyOwner_None) + return false; + } + + return true; +} + +// _LockXXX flags are useful to lock keys away from code which is not input-owner aware. +// When using _LockXXX flags, you can use ImGuiKeyOwner_Any to lock keys from everyone. +// - SetKeyOwner(..., None) : clears owner +// - SetKeyOwner(..., Any, !Lock) : illegal (assert) +// - SetKeyOwner(..., Any or None, Lock) : set lock +void ImGui::SetKeyOwner(ImGuiKey key, ImGuiID owner_id, ImGuiInputFlags flags) +{ + IM_ASSERT(IsNamedKeyOrModKey(key) && (owner_id != ImGuiKeyOwner_Any || (flags & (ImGuiInputFlags_LockThisFrame | ImGuiInputFlags_LockUntilRelease)))); // Can only use _Any with _LockXXX flags (to eat a key away without an ID to retrieve it) + IM_ASSERT((flags & ~ImGuiInputFlags_SupportedBySetKeyOwner) == 0); // Passing flags not supported by this function! + + ImGuiKeyOwnerData* owner_data = GetKeyOwnerData(key); + owner_data->OwnerCurr = owner_data->OwnerNext = owner_id; + + // We cannot lock by default as it would likely break lots of legacy code. + // In the case of using LockUntilRelease while key is not down we still lock during the frame (no key_data->Down test) + owner_data->LockUntilRelease = (flags & ImGuiInputFlags_LockUntilRelease) != 0; + owner_data->LockThisFrame = (flags & ImGuiInputFlags_LockThisFrame) != 0 || (owner_data->LockUntilRelease); +} + +// This is more or less equivalent to: +// if (IsItemHovered() || IsItemActive()) +// SetKeyOwner(key, GetItemID()); +// Extensive uses of that (e.g. many calls for a single item) may want to manually perform the tests once and then call SetKeyOwner() multiple times. +// More advanced usage scenarios may want to call SetKeyOwner() manually based on different condition. +// Worth noting is that only one item can be hovered and only one item can be active, therefore this usage pattern doesn't need to bother with routing and priority. +void ImGui::SetItemKeyOwner(ImGuiKey key, ImGuiInputFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiID id = g.LastItemData.ID; + if (id == 0 || (g.HoveredId != id && g.ActiveId != id)) + return; + if ((flags & ImGuiInputFlags_CondMask_) == 0) + flags |= ImGuiInputFlags_CondDefault_; + if ((g.HoveredId == id && (flags & ImGuiInputFlags_CondHovered)) || (g.ActiveId == id && (flags & ImGuiInputFlags_CondActive))) + { + IM_ASSERT((flags & ~ImGuiInputFlags_SupportedBySetItemKeyOwner) == 0); // Passing flags not supported by this function! + SetKeyOwner(key, id, flags & ~ImGuiInputFlags_CondMask_); + } +} + +// - Need to decide how to handle shortcut translations for Non-Mac <> Mac +// - Ideas: https://github.com/ocornut/imgui/issues/456#issuecomment-264390864 +bool ImGui::Shortcut(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiInputFlags flags) +{ + ImGuiContext& g = *GImGui; + + // When using (owner_id == 0/Any): SetShortcutRouting() will use CurrentFocusScopeId and filter with this, so IsKeyPressed() is fine with he 0/Any. + if ((flags & ImGuiInputFlags_RouteMask_) == 0) + flags |= ImGuiInputFlags_RouteFocused; + if (!SetShortcutRouting(key_chord, owner_id, flags)) + return false; + + ImGuiKey key = (ImGuiKey)(key_chord & ~ImGuiMod_Mask_); + ImGuiKey mods = (ImGuiKey)(key_chord & ImGuiMod_Mask_); + if (g.IO.KeyMods != mods) + return false; + + // Special storage location for mods + if (key == ImGuiKey_None) + key = ConvertSingleModFlagToKey(mods); + + if (!IsKeyPressed(key, owner_id, (flags & (ImGuiInputFlags_Repeat | ImGuiInputFlags_RepeatRateMask_)))) + return false; + IM_ASSERT((flags & ~ImGuiInputFlags_SupportedByShortcut) == 0); // Passing flags not supported by this function! + + return true; +} + //----------------------------------------------------------------------------- // [SECTION] ERROR CHECKING @@ -8347,6 +8710,8 @@ void ImGui::ErrorCheckUsingSetCursorPosToExtendParentBoundaries() #ifdef IMGUI_DISABLE_OBSOLETE_FUNCTIONS if (window->DC.CursorPos.x <= window->DC.CursorMaxPos.x && window->DC.CursorPos.y <= window->DC.CursorMaxPos.y) return; + if (window->SkipItems) + return; IM_ASSERT(0 && "Code uses SetCursorPos()/SetCursorScreenPos() to extend window/parent boundaries. Please submit an item e.g. Dummy() to validate extent."); #else window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos); @@ -8512,7 +8877,7 @@ void ImGui::ErrorCheckEndWindowRecover(ImGuiErrorLogCallback log_callback, vo if (log_callback) log_callback(user_data, "Recovered from missing PopStyleVar() in '%s'", window->Name); PopStyleVar(); } - while (g.FocusScopeStack.Size > stack_sizes->SizeOfFocusScopeStack) //-V1044 + while (g.FocusScopeStack.Size > stack_sizes->SizeOfFocusScopeStack + 1) //-V1044 { if (log_callback) log_callback(user_data, "Recovered from missing PopFocusScope() in '%s'", window->Name); PopFocusScope(); @@ -8692,6 +9057,11 @@ bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGu if (!g.LogEnabled) return false; + // [DEBUG] +#ifndef IMGUI_DISABLE_DEBUG_TOOLS + if (id != 0 && id == g.DebugLocateId) + DebugLocateItemResolveWithLastItem(); +#endif //if (g.IO.KeyAlt) window->DrawList->AddRect(bb.Min, bb.Max, IM_COL32(255,255,0,120)); // [DEBUG] // We need to calculate this now to take account of the current clipping rectangle (as items like Selectable may change them) @@ -9956,12 +10326,12 @@ void ImGui::SetFocusID(ImGuiID id, ImGuiWindow* window) if (g.NavWindow != window) SetNavWindow(window); - // Assume that SetFocusID() is called in the context where its window->DC.NavLayerCurrent and window->DC.NavFocusScopeIdCurrent are valid. + // Assume that SetFocusID() is called in the context where its window->DC.NavLayerCurrent and g.CurrentFocusScopeId are valid. // Note that window may be != g.CurrentWindow (e.g. SetFocusID call in InputTextEx for multi-line text) const ImGuiNavLayer nav_layer = window->DC.NavLayerCurrent; g.NavId = id; g.NavLayer = nav_layer; - g.NavFocusScopeId = window->DC.NavFocusScopeIdCurrent; + g.NavFocusScopeId = g.CurrentFocusScopeId; window->NavLastIds[nav_layer] = id; if (g.LastItemData.ID == id) window->NavRectRel[nav_layer] = WindowRectAbsToRel(window, g.LastItemData.NavRect); @@ -10142,7 +10512,7 @@ static void ImGui::NavApplyItemToResult(ImGuiNavItemData* result) ImGuiWindow* window = g.CurrentWindow; result->Window = window; result->ID = g.LastItemData.ID; - result->FocusScopeId = window->DC.NavFocusScopeIdCurrent; + result->FocusScopeId = g.CurrentFocusScopeId; result->InFlags = g.LastItemData.InFlags; result->RectRel = WindowRectAbsToRel(window, g.LastItemData.NavRect); } @@ -10209,7 +10579,7 @@ static void ImGui::NavProcessItem() if (g.NavWindow != window) SetNavWindow(window); // Always refresh g.NavWindow, because some operations such as FocusItem() may not have a window. g.NavLayer = window->DC.NavLayerCurrent; - g.NavFocusScopeId = window->DC.NavFocusScopeIdCurrent; + g.NavFocusScopeId = g.CurrentFocusScopeId; g.NavIdIsAlive = true; window->NavRectRel[window->DC.NavLayerCurrent] = WindowRectAbsToRel(window, nav_bb); // Store item bounding box (relative to window position) } @@ -10397,7 +10767,8 @@ void ImGui::NavInitWindow(ImGuiWindow* window, bool force_reinit) if (window->Flags & ImGuiWindowFlags_NoNavInputs) { - g.NavId = g.NavFocusScopeId = 0; + g.NavId = 0; + g.NavFocusScopeId = window->NavRootFocusScopeId; return; } @@ -10407,7 +10778,7 @@ void ImGui::NavInitWindow(ImGuiWindow* window, bool force_reinit) IMGUI_DEBUG_LOG_NAV("[nav] NavInitRequest: from NavInitWindow(), init_for_nav=%d, window=\"%s\", layer=%d\n", init_for_nav, window->Name, g.NavLayer); if (init_for_nav) { - SetNavID(0, g.NavLayer, 0, ImRect()); + SetNavID(0, g.NavLayer, window->NavRootFocusScopeId, ImRect()); g.NavInitRequest = true; g.NavInitRequestFromMove = false; g.NavInitResultId = 0; @@ -10417,7 +10788,7 @@ void ImGui::NavInitWindow(ImGuiWindow* window, bool force_reinit) else { g.NavId = window->NavLastIds[0]; - g.NavFocusScopeId = 0; + g.NavFocusScopeId = window->NavRootFocusScopeId; } } @@ -10678,10 +11049,10 @@ void ImGui::NavUpdateCreateMoveRequest() if (window && !g.NavWindowingTarget && !(window->Flags & ImGuiWindowFlags_NoNavInputs)) { const ImGuiInputFlags repeat_mode = ImGuiInputFlags_Repeat | ImGuiInputFlags_RepeatRateNavMove; - if (!IsActiveIdUsingNavDir(ImGuiDir_Left) && ((nav_gamepad_active && IsKeyPressedEx(ImGuiKey_GamepadDpadLeft, repeat_mode)) || (nav_keyboard_active && IsKeyPressedEx(ImGuiKey_LeftArrow, repeat_mode)))) { g.NavMoveDir = ImGuiDir_Left; } - if (!IsActiveIdUsingNavDir(ImGuiDir_Right) && ((nav_gamepad_active && IsKeyPressedEx(ImGuiKey_GamepadDpadRight, repeat_mode)) || (nav_keyboard_active && IsKeyPressedEx(ImGuiKey_RightArrow, repeat_mode)))) { g.NavMoveDir = ImGuiDir_Right; } - if (!IsActiveIdUsingNavDir(ImGuiDir_Up) && ((nav_gamepad_active && IsKeyPressedEx(ImGuiKey_GamepadDpadUp, repeat_mode)) || (nav_keyboard_active && IsKeyPressedEx(ImGuiKey_UpArrow, repeat_mode)))) { g.NavMoveDir = ImGuiDir_Up; } - if (!IsActiveIdUsingNavDir(ImGuiDir_Down) && ((nav_gamepad_active && IsKeyPressedEx(ImGuiKey_GamepadDpadDown, repeat_mode)) || (nav_keyboard_active && IsKeyPressedEx(ImGuiKey_DownArrow, repeat_mode)))) { g.NavMoveDir = ImGuiDir_Down; } + if (!IsActiveIdUsingNavDir(ImGuiDir_Left) && ((nav_gamepad_active && IsKeyPressed(ImGuiKey_GamepadDpadLeft, ImGuiKeyOwner_None, repeat_mode)) || (nav_keyboard_active && IsKeyPressed(ImGuiKey_LeftArrow, ImGuiKeyOwner_None, repeat_mode)))) { g.NavMoveDir = ImGuiDir_Left; } + if (!IsActiveIdUsingNavDir(ImGuiDir_Right) && ((nav_gamepad_active && IsKeyPressed(ImGuiKey_GamepadDpadRight, ImGuiKeyOwner_None, repeat_mode)) || (nav_keyboard_active && IsKeyPressed(ImGuiKey_RightArrow, ImGuiKeyOwner_None, repeat_mode)))) { g.NavMoveDir = ImGuiDir_Right; } + if (!IsActiveIdUsingNavDir(ImGuiDir_Up) && ((nav_gamepad_active && IsKeyPressed(ImGuiKey_GamepadDpadUp, ImGuiKeyOwner_None, repeat_mode)) || (nav_keyboard_active && IsKeyPressed(ImGuiKey_UpArrow, ImGuiKeyOwner_None, repeat_mode)))) { g.NavMoveDir = ImGuiDir_Up; } + if (!IsActiveIdUsingNavDir(ImGuiDir_Down) && ((nav_gamepad_active && IsKeyPressed(ImGuiKey_GamepadDpadDown, ImGuiKeyOwner_None, repeat_mode)) || (nav_keyboard_active && IsKeyPressed(ImGuiKey_DownArrow, ImGuiKeyOwner_None, repeat_mode)))) { g.NavMoveDir = ImGuiDir_Down; } } g.NavMoveClipDir = g.NavMoveDir; g.NavScoringNoClipRect = ImRect(+FLT_MAX, +FLT_MAX, -FLT_MAX, -FLT_MAX); @@ -10741,7 +11112,7 @@ void ImGui::NavUpdateCreateMoveRequest() inner_rect_rel.Min.y = clamp_y ? (inner_rect_rel.Min.y + pad_y) : -FLT_MAX; inner_rect_rel.Max.y = clamp_y ? (inner_rect_rel.Max.y - pad_y) : +FLT_MAX; window->NavRectRel[g.NavLayer].ClipWithFull(inner_rect_rel); - g.NavId = g.NavFocusScopeId = 0; + g.NavId = 0; } } @@ -10770,7 +11141,7 @@ void ImGui::NavUpdateCreateTabbingRequest() if (window == NULL || g.NavWindowingTarget != NULL || (window->Flags & ImGuiWindowFlags_NoNavInputs)) return; - const bool tab_pressed = IsKeyPressed(ImGuiKey_Tab, true) && !IsActiveIdUsingKey(ImGuiKey_Tab) && !g.IO.KeyCtrl && !g.IO.KeyAlt; + const bool tab_pressed = IsKeyPressed(ImGuiKey_Tab, ImGuiKeyOwner_None, ImGuiInputFlags_Repeat) && !g.IO.KeyCtrl && !g.IO.KeyAlt; if (!tab_pressed) return; @@ -10888,14 +11259,13 @@ static void ImGui::NavUpdateCancelRequest() ImGuiContext& g = *GImGui; const bool nav_gamepad_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (g.IO.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0; const bool nav_keyboard_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0; - if (!(nav_keyboard_active && IsKeyPressed(ImGuiKey_Escape, false)) && !(nav_gamepad_active && IsKeyPressed(ImGuiKey_NavGamepadCancel, false))) + if (!(nav_keyboard_active && IsKeyPressed(ImGuiKey_Escape, ImGuiKeyOwner_None)) && !(nav_gamepad_active && IsKeyPressed(ImGuiKey_NavGamepadCancel, ImGuiKeyOwner_None))) return; IMGUI_DEBUG_LOG_NAV("[nav] NavUpdateCancelRequest()\n"); if (g.ActiveId != 0) { - if (!IsActiveIdUsingKey(ImGuiKey_Escape) && !IsActiveIdUsingKey(ImGuiKey_NavGamepadCancel)) - ClearActiveID(); + ClearActiveID(); } else if (g.NavLayer != ImGuiNavLayer_Main) { @@ -10924,7 +11294,7 @@ static void ImGui::NavUpdateCancelRequest() // Clear NavLastId for popups but keep it for regular child window so we can leave one and come back where we were if (g.NavWindow && ((g.NavWindow->Flags & ImGuiWindowFlags_Popup) || !(g.NavWindow->Flags & ImGuiWindowFlags_ChildWindow))) g.NavWindow->NavLastIds[0] = 0; - g.NavId = g.NavFocusScopeId = 0; + g.NavId = 0; } } @@ -10939,10 +11309,10 @@ static float ImGui::NavUpdatePageUpPageDown() if ((window->Flags & ImGuiWindowFlags_NoNavInputs) || g.NavWindowingTarget != NULL) return 0.0f; - const bool page_up_held = IsKeyDown(ImGuiKey_PageUp) && !IsActiveIdUsingKey(ImGuiKey_PageUp); - const bool page_down_held = IsKeyDown(ImGuiKey_PageDown) && !IsActiveIdUsingKey(ImGuiKey_PageDown); - const bool home_pressed = IsKeyPressed(ImGuiKey_Home) && !IsActiveIdUsingKey(ImGuiKey_Home); - const bool end_pressed = IsKeyPressed(ImGuiKey_End) && !IsActiveIdUsingKey(ImGuiKey_End); + const bool page_up_held = IsKeyDown(ImGuiKey_PageUp, ImGuiKeyOwner_None); + const bool page_down_held = IsKeyDown(ImGuiKey_PageDown, ImGuiKeyOwner_None); + const bool home_pressed = IsKeyPressed(ImGuiKey_Home, ImGuiKeyOwner_None, ImGuiInputFlags_Repeat); + const bool end_pressed = IsKeyPressed(ImGuiKey_End, ImGuiKeyOwner_None, ImGuiInputFlags_Repeat); if (page_up_held == page_down_held && home_pressed == end_pressed) // Proceed if either (not both) are pressed, otherwise early out return 0.0f; @@ -10952,9 +11322,9 @@ static float ImGui::NavUpdatePageUpPageDown() if (window->DC.NavLayersActiveMask == 0x00 && window->DC.NavHasScroll) { // Fallback manual-scroll when window has no navigable item - if (IsKeyPressed(ImGuiKey_PageUp, true)) + if (IsKeyPressed(ImGuiKey_PageUp, ImGuiKeyOwner_None, ImGuiInputFlags_Repeat)) SetScrollY(window, window->Scroll.y - window->InnerRect.GetHeight()); - else if (IsKeyPressed(ImGuiKey_PageDown, true)) + else if (IsKeyPressed(ImGuiKey_PageDown, ImGuiKeyOwner_None, ImGuiInputFlags_Repeat)) SetScrollY(window, window->Scroll.y + window->InnerRect.GetHeight()); else if (home_pressed) SetScrollY(window, 0.0f); @@ -11142,8 +11512,10 @@ static void ImGui::NavUpdateWindowing() // Start CTRL+Tab or Square+L/R window selection const bool nav_gamepad_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (io.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0; const bool nav_keyboard_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0; - const bool start_windowing_with_gamepad = allow_windowing && nav_gamepad_active && !g.NavWindowingTarget && IsKeyPressed(ImGuiKey_NavGamepadMenu, false); - const bool start_windowing_with_keyboard = allow_windowing && !g.NavWindowingTarget && io.KeyCtrl && IsKeyPressed(ImGuiKey_Tab, false); // Note: enabled even without NavEnableKeyboard! + const bool keyboard_next_window = allow_windowing && g.ConfigNavWindowingKeyNext && Shortcut(g.ConfigNavWindowingKeyNext, ImGuiKeyOwner_None, ImGuiInputFlags_Repeat | ImGuiInputFlags_RouteAlways); + const bool keyboard_prev_window = allow_windowing && g.ConfigNavWindowingKeyPrev && Shortcut(g.ConfigNavWindowingKeyPrev, ImGuiKeyOwner_None, ImGuiInputFlags_Repeat | ImGuiInputFlags_RouteAlways); + const bool start_windowing_with_gamepad = allow_windowing && nav_gamepad_active && !g.NavWindowingTarget && IsKeyPressed(ImGuiKey_NavGamepadMenu, 0, ImGuiInputFlags_None); + const bool start_windowing_with_keyboard = allow_windowing && !g.NavWindowingTarget && (keyboard_next_window || keyboard_prev_window); // Note: enabled even without NavEnableKeyboard! if (start_windowing_with_gamepad || start_windowing_with_keyboard) if (ImGuiWindow* window = g.NavWindow ? g.NavWindow : FindWindowNavFocusable(g.WindowsFocusOrder.Size - 1, -INT_MAX, -1)) { @@ -11185,17 +11557,19 @@ static void ImGui::NavUpdateWindowing() if (g.NavWindowingTarget && g.NavInputSource == ImGuiInputSource_Keyboard) { // Visuals only appears after a brief time after pressing TAB the first time, so that a fast CTRL+TAB doesn't add visual noise + ImGuiKeyChord shared_mods = ((g.ConfigNavWindowingKeyNext ? g.ConfigNavWindowingKeyNext : ImGuiMod_Mask_) & (g.ConfigNavWindowingKeyPrev ? g.ConfigNavWindowingKeyPrev : ImGuiMod_Mask_)) & ImGuiMod_Mask_; + IM_ASSERT(shared_mods != 0); // Next/Prev shortcut currently needs a shared modifier to "hold", otherwise Prev actions would keep cycling between two windows. g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha, ImSaturate((g.NavWindowingTimer - NAV_WINDOWING_HIGHLIGHT_DELAY) / 0.05f)); // 1.0f - if (IsKeyPressed(ImGuiKey_Tab, true)) - NavUpdateWindowingHighlightWindow(io.KeyShift ? +1 : -1); - if (!io.KeyCtrl) + if (keyboard_next_window || keyboard_prev_window) + NavUpdateWindowingHighlightWindow(keyboard_next_window ? -1 : +1); + else if ((io.KeyMods & shared_mods) != shared_mods) apply_focus_window = g.NavWindowingTarget; } // Keyboard: Press and Release ALT to toggle menu layer // - Testing that only Alt is tested prevents Alt+Shift or AltGR from toggling menu layer. // - AltGR is normally Alt+Ctrl but we can't reliably detect it (not all backends/systems/layout emit it as Alt+Ctrl). But even on keyboards without AltGR we don't want Alt+Ctrl to open menu anyway. - if (nav_keyboard_active && IsKeyPressed(ImGuiMod_Alt)) + if (nav_keyboard_active && IsKeyPressed(ImGuiMod_Alt, ImGuiKeyOwner_None)) { g.NavWindowingToggleLayer = true; g.NavInputSource = ImGuiInputSource_Keyboard; @@ -11204,7 +11578,8 @@ static void ImGui::NavUpdateWindowing() { // We cancel toggling nav layer when any text has been typed (generally while holding Alt). (See #370) // We cancel toggling nav layer when other modifiers are pressed. (See #4439) - if (io.InputQueueCharacters.Size > 0 || io.KeyCtrl || io.KeyShift || io.KeySuper) + // We cancel toggling nav layer if an owner has claimed the key. + if (io.InputQueueCharacters.Size > 0 || io.KeyCtrl || io.KeyShift || io.KeySuper || TestKeyOwner(ImGuiMod_Alt, ImGuiKeyOwner_None) == false) g.NavWindowingToggleLayer = false; // Apply layer toggle on release @@ -11301,10 +11676,10 @@ static void ImGui::NavUpdateWindowing() static const char* GetFallbackWindowNameForWindowingList(ImGuiWindow* window) { if (window->Flags & ImGuiWindowFlags_Popup) - return "(Popup)"; + return ImGui::LocalizeGetMsg(ImGuiLocKey_WindowingPopup); if ((window->Flags & ImGuiWindowFlags_MenuBar) && strcmp(window->Name, "##MainMenuBar") == 0) - return "(Main menu bar)"; - return "(Untitled)"; + return ImGui::LocalizeGetMsg(ImGuiLocKey_WindowingMainMenuBar); + return ImGui::LocalizeGetMsg(ImGuiLocKey_WindowingUntitled); } // Overlay displayed when using CTRL+TAB. Called by EndFrame(). @@ -11650,7 +12025,7 @@ void ImGui::RenderDragDropTargetRect(const ImRect& bb) const ImGuiPayload* ImGui::GetDragDropPayload() { ImGuiContext& g = *GImGui; - return g.DragDropActive ? &g.DragDropPayload : NULL; + return (g.DragDropActive && g.DragDropPayload.DataFrameCount != -1) ? &g.DragDropPayload : NULL; } // We don't really use/need this now, but added it for the sake of consistency and because we might need it later. @@ -12234,6 +12609,18 @@ static void WindowSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandl } +//----------------------------------------------------------------------------- +// [SECTION] LOCALIZATION +//----------------------------------------------------------------------------- + +void ImGui::LocalizeRegisterEntries(const ImGuiLocEntry* entries, int count) +{ + ImGuiContext& g = *GImGui; + for (int n = 0; n < count; n++) + g.LocalizationTable[entries[n].Key] = entries[n].Text; +} + + //----------------------------------------------------------------------------- // [SECTION] VIEWPORTS, PLATFORM WINDOWS //----------------------------------------------------------------------------- @@ -12932,7 +13319,44 @@ void ImGui::ShowMetricsWindow(bool* p_open) TreePop(); } - // Misc Details + if (TreeNode("Key Owners & Shortcut Routing")) + { + TextUnformatted("Key Owners:"); + if (BeginListBox("##owners", ImVec2(-FLT_MIN, GetTextLineHeightWithSpacing() * 8))) + { + for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1)) + { + ImGuiKeyOwnerData* owner_data = GetKeyOwnerData(key); + if (owner_data->OwnerCurr == ImGuiKeyOwner_None) + continue; + Text("%s: 0x%08X%s", GetKeyName(key), owner_data->OwnerCurr, + owner_data->LockUntilRelease ? " LockUntilRelease" : owner_data->LockThisFrame ? " LockThisFrame" : ""); + DebugLocateItemOnHover(owner_data->OwnerCurr); + } + EndListBox(); + } + TextUnformatted("Shortcut Routing:"); + if (BeginListBox("##routes", ImVec2(-FLT_MIN, GetTextLineHeightWithSpacing() * 8))) + { + for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1)) + { + ImGuiKeyRoutingTable* rt = &g.KeysRoutingTable; + for (ImGuiKeyRoutingIndex idx = rt->Index[key - ImGuiKey_NamedKey_BEGIN]; idx != -1; ) + { + char key_chord_name[64]; + ImGuiKeyRoutingData* routing_data = &rt->Entries[idx]; + GetKeyChordName(key | routing_data->Mods, key_chord_name, IM_ARRAYSIZE(key_chord_name)); + Text("%s: 0x%08X", key_chord_name, routing_data->RoutingCurr); + DebugLocateItemOnHover(routing_data->RoutingCurr); + idx = routing_data->NextEntryIndex; + } + } + EndListBox(); + } + Text("(ActiveIdUsing: AllKeyboardKeys: %d, NavDirMask: 0x%X)", g.ActiveIdUsingAllKeyboardKeys, g.ActiveIdUsingNavDirMask); + TreePop(); + } + if (TreeNode("Internal state")) { Text("WINDOWING"); @@ -12946,21 +13370,20 @@ void ImGui::ShowMetricsWindow(bool* p_open) Text("ITEMS"); Indent(); Text("ActiveId: 0x%08X/0x%08X (%.2f sec), AllowOverlap: %d, Source: %s", g.ActiveId, g.ActiveIdPreviousFrame, g.ActiveIdTimer, g.ActiveIdAllowOverlap, GetInputSourceName(g.ActiveIdSource)); + DebugLocateItemOnHover(g.ActiveId); Text("ActiveIdWindow: '%s'", g.ActiveIdWindow ? g.ActiveIdWindow->Name : "NULL"); - - int active_id_using_key_input_count = 0; - for (int n = ImGuiKey_NamedKey_BEGIN; n < ImGuiKey_NamedKey_END; n++) - active_id_using_key_input_count += g.ActiveIdUsingKeyInputMask[n] ? 1 : 0; - Text("ActiveIdUsing: NavDirMask: %X, KeyInputMask: %d key(s)", g.ActiveIdUsingNavDirMask, active_id_using_key_input_count); + Text("ActiveIdUsing: AllKeyboardKeys: %d, NavDirMask: %X", g.ActiveIdUsingAllKeyboardKeys, g.ActiveIdUsingNavDirMask); Text("HoveredId: 0x%08X (%.2f sec), AllowOverlap: %d", g.HoveredIdPreviousFrame, g.HoveredIdTimer, g.HoveredIdAllowOverlap); // Not displaying g.HoveredId as it is update mid-frame Text("HoverDelayId: 0x%08X, Timer: %.2f, ClearTimer: %.2f", g.HoverDelayId, g.HoverDelayTimer, g.HoverDelayClearTimer); Text("DragDrop: %d, SourceId = 0x%08X, Payload \"%s\" (%d bytes)", g.DragDropActive, g.DragDropPayload.SourceId, g.DragDropPayload.DataType, g.DragDropPayload.DataSize); + DebugLocateItemOnHover(g.DragDropPayload.SourceId); Unindent(); Text("NAV,FOCUS"); Indent(); Text("NavWindow: '%s'", g.NavWindow ? g.NavWindow->Name : "NULL"); Text("NavId: 0x%08X, NavLayer: %d", g.NavId, g.NavLayer); + DebugLocateItemOnHover(g.NavId); Text("NavInputSource: %s", GetInputSourceName(g.NavInputSource)); Text("NavActive: %d, NavVisible: %d", g.IO.NavActive, g.IO.NavVisible); Text("NavActivateId/DownId/PressedId/InputId: %08X/%08X/%08X/%08X", g.NavActivateId, g.NavActivateDownId, g.NavActivatePressedId, g.NavActivateInputId); @@ -13384,13 +13807,10 @@ void ImGui::DebugNodeWindow(ImGuiWindow* window, const char* label) { ImRect r = window->NavRectRel[layer]; if (r.Min.x >= r.Max.y && r.Min.y >= r.Max.y) - { BulletText("NavLastIds[%d]: 0x%08X", layer, window->NavLastIds[layer]); - continue; - } - BulletText("NavLastIds[%d]: 0x%08X at +(%.1f,%.1f)(%.1f,%.1f)", layer, window->NavLastIds[layer], r.Min.x, r.Min.y, r.Max.x, r.Max.y); - if (IsItemHovered()) - GetForegroundDrawList(window)->AddRect(r.Min + window->Pos, r.Max + window->Pos, IM_COL32(255, 255, 0, 255)); + else + BulletText("NavLastIds[%d]: 0x%08X at +(%.1f,%.1f)(%.1f,%.1f)", layer, window->NavLastIds[layer], r.Min.x, r.Min.y, r.Max.x, r.Max.y); + DebugLocateItemOnHover(window->NavLastIds[layer]); } BulletText("NavLayersActiveMask: %X, NavLastChildNavWindow: %s", window->DC.NavLayersActiveMask, window->NavLastChildNavWindow ? window->NavLastChildNavWindow->Name : "NULL"); if (window->RootWindow != window) { DebugNodeWindow(window->RootWindow, "RootWindow"); } @@ -13505,6 +13925,20 @@ void ImGui::ShowDebugLogWindow(bool* p_open) const char* line_begin = g.DebugLogIndex.get_line_begin(g.DebugLogBuf.c_str(), line_no); const char* line_end = g.DebugLogIndex.get_line_end(g.DebugLogBuf.c_str(), line_no); TextUnformatted(line_begin, line_end); + ImRect text_rect = g.LastItemData.Rect; + if (IsItemHovered()) + for (const char* p = line_begin; p < line_end - 10; p++) + { + ImGuiID id = 0; + if (p[0] != '0' || (p[1] != 'x' && p[1] != 'X') || sscanf(p + 2, "%X", &id) != 1) + continue; + ImVec2 p0 = CalcTextSize(line_begin, p); + ImVec2 p1 = CalcTextSize(p, p + 10); + g.LastItemData.Rect = ImRect(text_rect.Min + ImVec2(p0.x, 0.0f), text_rect.Min + ImVec2(p0.x + p1.x, p1.y)); + if (IsMouseHoveringRect(g.LastItemData.Rect.Min, g.LastItemData.Rect.Max, true)) + DebugLocateItemOnHover(id); + p += 10; + } } if (GetScrollY() >= GetScrollMaxY()) SetScrollHereY(1.0f); @@ -13517,6 +13951,38 @@ void ImGui::ShowDebugLogWindow(bool* p_open) // [SECTION] OTHER DEBUG TOOLS (ITEM PICKER, STACK TOOL) //----------------------------------------------------------------------------- +static const ImU32 DEBUG_LOCATE_ITEM_COLOR = IM_COL32(0, 255, 0, 255); // Green + +void ImGui::DebugLocateItem(ImGuiID target_id) +{ + ImGuiContext& g = *GImGui; + g.DebugLocateId = target_id; + g.DebugLocateFrames = 2; +} + +void ImGui::DebugLocateItemOnHover(ImGuiID target_id) +{ + if (target_id == 0 || !IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem | ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + return; + ImGuiContext& g = *GImGui; + DebugLocateItem(target_id); + GetForegroundDrawList(g.CurrentWindow)->AddRect(g.LastItemData.Rect.Min - ImVec2(3.0f, 3.0f), g.LastItemData.Rect.Max + ImVec2(3.0f, 3.0f), DEBUG_LOCATE_ITEM_COLOR); +} + +void ImGui::DebugLocateItemResolveWithLastItem() +{ + ImGuiContext& g = *GImGui; + ImGuiLastItemData item_data = g.LastItemData; + g.DebugLocateId = 0; + ImDrawList* draw_list = GetForegroundDrawList(g.CurrentWindow); + ImRect r = item_data.Rect; + r.Expand(3.0f); + ImVec2 p1 = g.IO.MousePos; + ImVec2 p2 = ImVec2((p1.x < r.Min.x) ? r.Min.x : (p1.x > r.Max.x) ? r.Max.x : p1.x, (p1.y < r.Min.y) ? r.Min.y : (p1.y > r.Max.y) ? r.Max.y : p1.y); + draw_list->AddRect(r.Min, r.Max, DEBUG_LOCATE_ITEM_COLOR); + draw_list->AddLine(p1, p2, DEBUG_LOCATE_ITEM_COLOR); +} + // [DEBUG] Item picker tool - start with DebugStartItemPicker() - useful to visually select an item and break into its call-stack. void ImGui::UpdateDebugToolItemPicker() { diff --git a/3rdparty/dear-imgui/imgui.h b/3rdparty/dear-imgui/imgui.h index 5ae2158ce..a626c79f4 100644 --- a/3rdparty/dear-imgui/imgui.h +++ b/3rdparty/dear-imgui/imgui.h @@ -1,4 +1,4 @@ -// dear imgui, v1.89 WIP +// dear imgui, v1.89.1 WIP // (headers) // Help: @@ -22,8 +22,8 @@ // Library Version // (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM > 12345') -#define IMGUI_VERSION "1.89 WIP" -#define IMGUI_VERSION_NUM 18833 +#define IMGUI_VERSION "1.89.1 WIP" +#define IMGUI_VERSION_NUM 18903 #define IMGUI_HAS_TABLE /* @@ -172,7 +172,7 @@ typedef int ImGuiCond; // -> enum ImGuiCond_ // Enum: A typedef int ImGuiDataType; // -> enum ImGuiDataType_ // Enum: A primary data type typedef int ImGuiDir; // -> enum ImGuiDir_ // Enum: A cardinal direction typedef int ImGuiMouseButton; // -> enum ImGuiMouseButton_ // Enum: A mouse button identifier (0=left, 1=right, 2=middle) -typedef int ImGuiMouseCursor; // -> enum ImGuiMouseCursor_ // Enum: A mouse cursor identifier +typedef int ImGuiMouseCursor; // -> enum ImGuiMouseCursor_ // Enum: A mouse cursor shape typedef int ImGuiSortDirection; // -> enum ImGuiSortDirection_ // Enum: A sorting direction (ascending or descending) typedef int ImGuiStyleVar; // -> enum ImGuiStyleVar_ // Enum: A variable identifier for styling typedef int ImGuiTableBgTarget; // -> enum ImGuiTableBgTarget_ // Enum: A color target for TableSetBgColor() @@ -362,6 +362,7 @@ namespace ImGui IMGUI_API void SetNextWindowContentSize(const ImVec2& size); // set next window content size (~ scrollable client area, which enforce the range of scrollbars). Not including window decorations (title bar, menu bar, etc.) nor WindowPadding. set an axis to 0.0f to leave it automatic. call before Begin() IMGUI_API void SetNextWindowCollapsed(bool collapsed, ImGuiCond cond = 0); // set next window collapsed state. call before Begin() IMGUI_API void SetNextWindowFocus(); // set next window to be focused / top-most. call before Begin() + IMGUI_API void SetNextWindowScroll(const ImVec2& scroll); // set next window scrolling value (use < 0.0f to not affect a given axis). IMGUI_API void SetNextWindowBgAlpha(float alpha); // set next window background color alpha. helper to easily override the Alpha component of ImGuiCol_WindowBg/ChildBg/PopupBg. you may also use ImGuiWindowFlags_NoBackground. IMGUI_API void SetWindowPos(const ImVec2& pos, ImGuiCond cond = 0); // (not recommended) set current window position - call within Begin()/End(). prefer using SetNextWindowPos(), as this may incur tearing and side-effects. IMGUI_API void SetWindowSize(const ImVec2& size, ImGuiCond cond = 0); // (not recommended) set current window size - call within Begin()/End(). set to ImVec2(0, 0) to force an auto-fit. prefer using SetNextWindowSize(), as this may incur tearing and minor side-effects. @@ -382,6 +383,8 @@ namespace ImGui IMGUI_API ImVec2 GetWindowContentRegionMax(); // content boundaries max for the full window (roughly (0,0)+Size-Scroll) where Size can be overridden with SetNextWindowContentSize(), in window coordinates // Windows Scrolling + // - Any change of Scroll will be applied at the beginning of next frame in the first call to Begin(). + // - You may instead use SetNextWindowScroll() prior to calling Begin() to avoid this delay, as an alternative to using SetScrollX()/SetScrollY(). IMGUI_API float GetScrollX(); // get scrolling amount [0 .. GetScrollMaxX()] IMGUI_API float GetScrollY(); // get scrolling amount [0 .. GetScrollMaxY()] IMGUI_API void SetScrollX(float scroll_x); // set scrolling amount [0 .. GetScrollMaxX()] @@ -908,8 +911,8 @@ namespace ImGui IMGUI_API bool IsMouseDragging(ImGuiMouseButton button, float lock_threshold = -1.0f); // is mouse dragging? (if lock_threshold < -1.0f, uses io.MouseDraggingThreshold) IMGUI_API ImVec2 GetMouseDragDelta(ImGuiMouseButton button = 0, float lock_threshold = -1.0f); // return the delta from the initial clicking position while the mouse button is pressed or was just released. This is locked and return 0.0f until the mouse moves past a distance threshold at least once (if lock_threshold < -1.0f, uses io.MouseDraggingThreshold) IMGUI_API void ResetMouseDragDelta(ImGuiMouseButton button = 0); // - IMGUI_API ImGuiMouseCursor GetMouseCursor(); // get desired cursor type, reset in ImGui::NewFrame(), this is updated during the frame. valid before Render(). If you use software rendering by setting io.MouseDrawCursor ImGui will render those for you - IMGUI_API void SetMouseCursor(ImGuiMouseCursor cursor_type); // set desired cursor type + IMGUI_API ImGuiMouseCursor GetMouseCursor(); // get desired mouse cursor shape. Important: reset in ImGui::NewFrame(), this is updated during the frame. valid before Render(). If you use software rendering by setting io.MouseDrawCursor ImGui will render those for you + IMGUI_API void SetMouseCursor(ImGuiMouseCursor cursor_type); // set desired mouse cursor shape IMGUI_API void SetNextFrameWantCaptureMouse(bool want_capture_mouse); // Override io.WantCaptureMouse flag next frame (said flag is left for your application to handle, typical when true it instucts your app to ignore inputs). This is equivalent to setting "io.WantCaptureMouse = want_capture_mouse;" after the next NewFrame() call. // Clipboard Utilities diff --git a/3rdparty/dear-imgui/imgui_demo.cpp b/3rdparty/dear-imgui/imgui_demo.cpp index a323e8e8e..560586933 100644 --- a/3rdparty/dear-imgui/imgui_demo.cpp +++ b/3rdparty/dear-imgui/imgui_demo.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.89 WIP +// dear imgui, v1.89.1 WIP // (demo code) // Help: @@ -5767,7 +5767,7 @@ static void ShowDemoWindowInputs() const ImGuiKey key_first = (ImGuiKey)0; //ImGui::Text("Legacy raw:"); for (ImGuiKey key = key_first; key < ImGuiKey_COUNT; key++) { if (io.KeysDown[key]) { ImGui::SameLine(); ImGui::Text("\"%s\" %d", ImGui::GetKeyName(key), key); } } #endif - ImGui::Text("Keys down:"); for (ImGuiKey key = key_first; key < ImGuiKey_COUNT; key = (ImGuiKey)(key + 1)) { if (funcs::IsLegacyNativeDupe(key)) continue; if (ImGui::IsKeyDown(key)) { ImGui::SameLine(); ImGui::Text("\"%s\" %d (%.02f secs)", ImGui::GetKeyName(key), key, ImGui::GetKeyData(key)->DownDuration); } } + ImGui::Text("Keys down:"); for (ImGuiKey key = key_first; key < ImGuiKey_COUNT; key = (ImGuiKey)(key + 1)) { if (funcs::IsLegacyNativeDupe(key)) continue; if (ImGui::IsKeyDown(key)) { ImGui::SameLine(); ImGui::Text("\"%s\" %d (%.02f)", ImGui::GetKeyName(key), key, ImGui::GetKeyData(key)->DownDuration); } } ImGui::Text("Keys pressed:"); for (ImGuiKey key = key_first; key < ImGuiKey_COUNT; key = (ImGuiKey)(key + 1)) { if (funcs::IsLegacyNativeDupe(key)) continue; if (ImGui::IsKeyPressed(key)) { ImGui::SameLine(); ImGui::Text("\"%s\" %d", ImGui::GetKeyName(key), key); } } ImGui::Text("Keys released:"); for (ImGuiKey key = key_first; key < ImGuiKey_COUNT; key = (ImGuiKey)(key + 1)) { if (funcs::IsLegacyNativeDupe(key)) continue; if (ImGui::IsKeyReleased(key)) { ImGui::SameLine(); ImGui::Text("\"%s\" %d", ImGui::GetKeyName(key), key); } } ImGui::Text("Keys mods: %s%s%s%s", io.KeyCtrl ? "CTRL " : "", io.KeyShift ? "SHIFT " : "", io.KeyAlt ? "ALT " : "", io.KeySuper ? "SUPER " : ""); @@ -6721,6 +6721,8 @@ struct ExampleAppConsole if (copy_to_clipboard) ImGui::LogFinish(); + // Keep up at the bottom of the scroll region if we were already at the bottom at the beginning of the frame. + // Using a scrollbar or mouse-wheel will take away from the bottom edge. if (ScrollToBottom || (AutoScroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY())) ImGui::SetScrollHereY(1.0f); ScrollToBottom = false; @@ -7029,6 +7031,8 @@ struct ExampleAppLog } ImGui::PopStyleVar(); + // Keep up at the bottom of the scroll region if we were already at the bottom at the beginning of the frame. + // Using a scrollbar or mouse-wheel will take away from the bottom edge. if (AutoScroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) ImGui::SetScrollHereY(1.0f); } diff --git a/3rdparty/dear-imgui/imgui_draw.cpp b/3rdparty/dear-imgui/imgui_draw.cpp index 7a8f229eb..859b62e7e 100644 --- a/3rdparty/dear-imgui/imgui_draw.cpp +++ b/3rdparty/dear-imgui/imgui_draw.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.89 WIP +// dear imgui, v1.89.1 WIP // (drawing and font code) /* diff --git a/3rdparty/dear-imgui/imgui_internal.h b/3rdparty/dear-imgui/imgui_internal.h index 9fd0ede25..015f6b657 100644 --- a/3rdparty/dear-imgui/imgui_internal.h +++ b/3rdparty/dear-imgui/imgui_internal.h @@ -1,4 +1,4 @@ -// dear imgui, v1.89 WIP +// dear imgui, v1.89.1 WIP // (internal structures/api) // You may use this file to debug, understand or extend ImGui features but we don't provide any guarantee of forward compatibility! @@ -26,6 +26,7 @@ Index of this file: // [SECTION] Docking support // [SECTION] Viewport support // [SECTION] Settings support +// [SECTION] Localization support // [SECTION] Metrics, Debug tools // [SECTION] Generic context hooks // [SECTION] ImGuiContext (main imgui context) @@ -121,6 +122,7 @@ struct ImGuiDataTypeInfo; // Type information associated to a ImGuiDat struct ImGuiGroupData; // Stacked storage data for BeginGroup()/EndGroup() struct ImGuiInputTextState; // Internal state of the currently focused/edited text input box struct ImGuiLastItemData; // Status storage for last submitted items +struct ImGuiLocEntry; // A localization entry. struct ImGuiMenuColumns; // Simple column measurement, currently used for MenuItem() only struct ImGuiNavItemData; // Result of a gamepad/keyboard directional navigation move query result struct ImGuiMetricsConfig; // Storage for ShowMetricsWindow() and DebugNodeXXX() functions @@ -144,11 +146,15 @@ struct ImGuiWindow; // Storage for one window struct ImGuiWindowTempData; // Temporary storage for one window (that's the data which in theory we could ditch at the end of the frame, in practice we currently keep it for each window) struct ImGuiWindowSettings; // Storage for a window .ini settings (we keep one of those even if the actual window wasn't instanced during this session) +// Enumerations // Use your programming IDE "Go to definition" facility on the names of the center columns to find the actual flags/enum lists. +enum ImGuiLocKey : int; // -> enum ImGuiLocKey // Enum: a localization entry for translation. typedef int ImGuiLayoutType; // -> enum ImGuiLayoutType_ // Enum: Horizontal or vertical + +// Flags typedef int ImGuiActivateFlags; // -> enum ImGuiActivateFlags_ // Flags: for navigation/focus function (will be for ActivateItem() later) typedef int ImGuiDebugLogFlags; // -> enum ImGuiDebugLogFlags_ // Flags: for ShowDebugLogWindow(), g.DebugLogFlags -typedef int ImGuiInputFlags; // -> enum ImGuiInputFlags_ // Flags: for IsKeyPressedEx() +typedef int ImGuiInputFlags; // -> enum ImGuiInputFlags_ // Flags: for IsKeyPressed(), IsMouseClicked(), SetKeyOwner(), SetItemKeyOwner() etc. typedef int ImGuiItemFlags; // -> enum ImGuiItemFlags_ // Flags: for PushItemFlag(), g.LastItemData.InFlags typedef int ImGuiItemStatusFlags; // -> enum ImGuiItemStatusFlags_ // Flags: for g.LastItemData.StatusFlags typedef int ImGuiOldColumnFlags; // -> enum ImGuiOldColumnFlags_ // Flags: for BeginColumns() @@ -852,6 +858,8 @@ enum ImGuiButtonFlagsPrivate_ ImGuiButtonFlags_NoHoldingActiveId = 1 << 17, // don't set ActiveId while holding the mouse (ImGuiButtonFlags_PressedOnClick only) ImGuiButtonFlags_NoNavFocus = 1 << 18, // don't override navigation focus when activated (FIXME: this is essentially used everytime an item uses ImGuiItemFlags_NoNav, but because legacy specs don't requires LastItemData to be set ButtonBehavior(), we can't poll g.LastItemData.InFlags) ImGuiButtonFlags_NoHoveredOnFocus = 1 << 19, // don't report as hovered when nav focus is on this item + ImGuiButtonFlags_NoSetKeyOwner = 1 << 20, // don't set key/input owner on the initial click (note: mouse buttons are keys! often, the key in question will be ImGuiKey_MouseLeft!) + ImGuiButtonFlags_NoTestKeyOwner = 1 << 21, // don't test key/input owner when polling the key (note: mouse buttons are keys! often, the key in question will be ImGuiKey_MouseLeft!) ImGuiButtonFlags_PressedOnMask_ = ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClickReleaseAnywhere | ImGuiButtonFlags_PressedOnRelease | ImGuiButtonFlags_PressedOnDoubleClick | ImGuiButtonFlags_PressedOnDragDropHold, ImGuiButtonFlags_PressedOnDefault_ = ImGuiButtonFlags_PressedOnClickRelease, }; @@ -881,6 +889,7 @@ enum ImGuiSelectableFlagsPrivate_ ImGuiSelectableFlags_DrawHoveredWhenHeld = 1 << 25, // Always show active when held, even is not hovered. This concept could probably be renamed/formalized somehow. ImGuiSelectableFlags_SetNavIdOnHover = 1 << 26, // Set Nav/Focus ID on mouse hover (used by MenuItem) ImGuiSelectableFlags_NoPadWithHalfSpacing = 1 << 27, // Disable padding each side with ItemSpacing * 0.5f + ImGuiSelectableFlags_NoSetKeyOwner = 1 << 28, // Don't set key/input owner on the initial click (note: mouse buttons are keys! often, the key in question will be ImGuiKey_MouseLeft!) }; // Extend ImGuiTreeNodeFlags_ @@ -1207,8 +1216,10 @@ typedef ImBitArray ImBitAr #define ImGuiKey_Keyboard_END (ImGuiKey_GamepadStart) #define ImGuiKey_Gamepad_BEGIN (ImGuiKey_GamepadStart) #define ImGuiKey_Gamepad_END (ImGuiKey_GamepadRStickDown + 1) -#define ImGuiKey_Aliases_BEGIN (ImGuiKey_MouseLeft) -#define ImGuiKey_Aliases_END (ImGuiKey_MouseWheelY + 1) +#define ImGuiKey_Mouse_BEGIN (ImGuiKey_MouseLeft) +#define ImGuiKey_Mouse_END (ImGuiKey_MouseWheelY + 1) +#define ImGuiKey_Aliases_BEGIN (ImGuiKey_Mouse_BEGIN) +#define ImGuiKey_Aliases_END (ImGuiKey_Mouse_END) // [Internal] Named shortcuts for Navigation #define ImGuiKey_NavKeyboardTweakSlow ImGuiMod_Ctrl @@ -1270,17 +1281,94 @@ struct ImGuiInputEvent ImGuiInputEvent() { memset(this, 0, sizeof(*this)); } }; -// Flags for IsKeyPressedEx(). In upcoming feature this will be used more (and IsKeyPressedEx() renamed) +// Input function taking an 'ImGuiID owner_id' argument defaults to (ImGuiKeyOwner_Any == 0) aka don't test ownership, which matches legacy behavior. +#define ImGuiKeyOwner_Any ((ImGuiID)0) // Accept key that have an owner, UNLESS a call to SetKeyOwner() explicitly used ImGuiInputFlags_LockThisFrame or ImGuiInputFlags_LockUntilRelease. +#define ImGuiKeyOwner_None ((ImGuiID)-1) // Require key to have no owner. + +typedef ImS16 ImGuiKeyRoutingIndex; + +// Routing table entry (sizeof() == 16 bytes) +struct ImGuiKeyRoutingData +{ + ImGuiKeyRoutingIndex NextEntryIndex; + ImU16 Mods; // Technically we'd only need 4 bits but for simplify we store ImGuiMod_ values which need 16 bits. + ImU8 RoutingNextScore; // Lower is better (0: perfect score) + ImGuiID RoutingCurr; + ImGuiID RoutingNext; + + ImGuiKeyRoutingData() { NextEntryIndex = -1; Mods = 0; RoutingNextScore = 255; RoutingCurr = RoutingNext = ImGuiKeyOwner_None; } +}; + +// Routing table: maintain a desired owner for each possible key-chord (key + mods), and setup owner in NewFrame() when mods are matching. +// Stored in main context (1 instance) +struct ImGuiKeyRoutingTable +{ + ImGuiKeyRoutingIndex Index[ImGuiKey_NamedKey_COUNT]; // Index of first entry in Entries[] + ImVector Entries; + ImVector EntriesNext; // Double-buffer to avoid reallocation (could use a shared buffer) + + ImGuiKeyRoutingTable() { Clear(); } + void Clear() { for (int n = 0; n < IM_ARRAYSIZE(Index); n++) Index[n] = -1; Entries.clear(); EntriesNext.clear(); } +}; + +// This extends ImGuiKeyData but only for named keys (legacy keys don't support the new features) +// Stored in main context (1 per named key). In the future it might be merged into ImGuiKeyData. +struct ImGuiKeyOwnerData +{ + ImGuiID OwnerCurr; + ImGuiID OwnerNext; + bool LockThisFrame; // Reading this key requires explicit owner id (until end of frame). Set by ImGuiInputFlags_LockThisFrame. + bool LockUntilRelease; // Reading this key requires explicit owner id (until key is released). Set by ImGuiInputFlags_LockUntilRelease. When this is true LockThisFrame is always true as well. + + ImGuiKeyOwnerData() { OwnerCurr = OwnerNext = ImGuiKeyOwner_None; LockThisFrame = LockUntilRelease = false; } +}; + +// Flags for extended versions of IsKeyPressed(), IsMouseClicked(), Shortcut(), SetKeyOwner(), SetItemKeyOwner() // Don't mistake with ImGuiInputTextFlags! (for ImGui::InputText() function) enum ImGuiInputFlags_ { - // Flags for IsKeyPressedEx() + // Flags for IsKeyPressed(), 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. ImGuiInputFlags_RepeatRateDefault = 1 << 1, // Repeat rate: Regular (default) ImGuiInputFlags_RepeatRateNavMove = 1 << 2, // Repeat rate: Fast ImGuiInputFlags_RepeatRateNavTweak = 1 << 3, // Repeat rate: Faster ImGuiInputFlags_RepeatRateMask_ = ImGuiInputFlags_RepeatRateDefault | ImGuiInputFlags_RepeatRateNavMove | ImGuiInputFlags_RepeatRateNavTweak, + + // Flags for SetItemKeyOwner() + ImGuiInputFlags_CondHovered = 1 << 4, // Only set if item is hovered (default to both) + ImGuiInputFlags_CondActive = 1 << 5, // 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 << 6, // 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 << 7, // 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. + + // 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(), + // 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. + // - 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 << 8, // (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 << 9, // Register route globally (lowest priority: unless a focused window or active item registered the route) -> recommended Global priority. + ImGuiInputFlags_RouteGlobal = 1 << 10, // Register route globally (medium priority: unless an active item registered the route, e.g. CTRL+A registered by InputText). + ImGuiInputFlags_RouteGlobalHigh = 1 << 11, // 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_RouteAlways = 1 << 12, // Do not register route, poll keys directly. + ImGuiInputFlags_RouteUnlessBgFocused= 1 << 13, // 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_SupportedByIsKeyPressed = ImGuiInputFlags_Repeat | ImGuiInputFlags_RepeatRateMask_, + ImGuiInputFlags_SupportedByShortcut = ImGuiInputFlags_Repeat | ImGuiInputFlags_RepeatRateMask_ | ImGuiInputFlags_RouteMask_ | ImGuiInputFlags_RouteExtraMask_, + ImGuiInputFlags_SupportedBySetKeyOwner = ImGuiInputFlags_LockThisFrame | ImGuiInputFlags_LockUntilRelease, + ImGuiInputFlags_SupportedBySetItemKeyOwner = ImGuiInputFlags_SupportedBySetKeyOwner | ImGuiInputFlags_CondMask_, }; //----------------------------------------------------------------------------- @@ -1526,6 +1614,30 @@ struct ImGuiSettingsHandler ImGuiSettingsHandler() { memset(this, 0, sizeof(*this)); } }; +//----------------------------------------------------------------------------- +// [SECTION] Localization support +//----------------------------------------------------------------------------- + +// This is experimental and not officially supported, it'll probably fall short of features, if/when it does we may backtrack. +enum ImGuiLocKey : int +{ + ImGuiLocKey_TableSizeOne, + ImGuiLocKey_TableSizeAllFit, + ImGuiLocKey_TableSizeAllDefault, + ImGuiLocKey_TableResetOrder, + ImGuiLocKey_WindowingMainMenuBar, + ImGuiLocKey_WindowingPopup, + ImGuiLocKey_WindowingUntitled, + ImGuiLocKey_COUNT +}; + +struct ImGuiLocEntry +{ + ImGuiLocKey Key; + const char* Text; +}; + + //----------------------------------------------------------------------------- // [SECTION] Metrics, Debug Tools //----------------------------------------------------------------------------- @@ -1655,8 +1767,6 @@ struct ImGuiContext ImGuiID HoveredId; // Hovered widget, filled during the frame ImGuiID HoveredIdPreviousFrame; bool HoveredIdAllowOverlap; - bool HoveredIdUsingMouseWheel; // Hovered widget will use mouse wheel. Blocks scrolling the underlying window. - bool HoveredIdPreviousFrameUsingMouseWheel; bool HoveredIdDisabled; // At least one widget passed the rect test, but has been discarded by disabled flag or popup inhibit. May be true even if HoveredId == 0. float HoveredIdTimer; // Measure contiguous hovering time float HoveredIdNotActiveTimer; // Measure contiguous hovering time where the item has not been active @@ -1680,15 +1790,22 @@ struct ImGuiContext ImGuiID LastActiveId; // Store the last non-zero ActiveId, useful for animation. float LastActiveIdTimer; // Store the last non-zero ActiveId timer since the beginning of activation, useful for animation. - // Input Ownership + // [EXPERIMENTAL] Key/Input Ownership + Shortcut Routing system + // - The idea is that instead of "eating" a given key, we can link to an owner. + // - Input query can then read input by specifying ImGuiKeyOwner_Any (== 0), ImGuiKeyOwner_None (== -1) or a custom ID. + // - Routing is requested ahead of time for a given chord (Key + Mods) and granted in NewFrame(). + 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) - ImBitArrayForNamedKeys ActiveIdUsingKeyInputMask; // Active widget will want to read those key inputs. When we grow the ImGuiKey enum we'll need to either to order the enum to make useful keys come first, either redesign this into e.g. a small array. + bool ActiveIdUsingAllKeyboardKeys; // Active widget will want to read all keyboard keys inputs. (FIXME: This is a shortcut for not taking ownership of 100+ keys but perhaps best to not have the inconsistency) #ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - ImU32 ActiveIdUsingNavInputMask; // If you used this. Since (IMGUI_VERSION_NUM >= 18804) : 'g.ActiveIdUsingNavInputMask |= (1 << ImGuiNavInput_Cancel);' becomes 'SetActiveIdUsingKey(ImGuiKey_Escape); SetActiveIdUsingKey(ImGuiKey_NavGamepadCancel);' + ImU32 ActiveIdUsingNavInputMask; // If you used this. Since (IMGUI_VERSION_NUM >= 18804) : 'g.ActiveIdUsingNavInputMask |= (1 << ImGuiNavInput_Cancel);' becomes 'SetKeyOwner(ImGuiKey_Escape, g.ActiveId) and/or SetKeyOwner(ImGuiKey_NavGamepadCancel, g.ActiveId);' #endif // Next window/item data + ImGuiID CurrentFocusScopeId; // == g.FocusScopeStack.back() ImGuiItemFlags CurrentItemFlags; // == g.ItemFlagsStack.back() + ImGuiID DebugLocateId; // Storage for DebugLocateItemOnHover() feature: this is read by ItemAdd() so we keep it in a hot/cached location ImGuiNextItemData NextItemData; // Storage for SetNextItem** functions ImGuiLastItemData LastItemData; // Storage for last submitted item (setup by ItemAdd) ImGuiNextWindowData NextWindowData; // Storage for SetNextWindow** functions @@ -1697,7 +1814,7 @@ struct ImGuiContext ImVector ColorStack; // Stack for PushStyleColor()/PopStyleColor() - inherited by Begin() ImVector StyleVarStack; // Stack for PushStyleVar()/PopStyleVar() - inherited by Begin() ImVector FontStack; // Stack for PushFont()/PopFont() - inherited by Begin() - ImVector FocusScopeStack; // Stack for PushFocusScope()/PopFocusScope() - not inherited by Begin(), unless child window + ImVector FocusScopeStack; // Stack for PushFocusScope()/PopFocusScope() - inherited by BeginChild(), pushed into by Begin() ImVectorItemFlagsStack; // Stack for PushItemFlag()/PopItemFlag() - inherited by Begin() ImVectorGroupStack; // Stack for BeginGroup()/EndGroup() - not inherited by Begin() ImVectorOpenPopupStack; // Which popups are open (persistent) @@ -1754,6 +1871,8 @@ struct ImGuiContext ImGuiNavItemData NavTabbingResultFirst; // First tabbing request candidate within NavWindow and flattened hierarchy // Navigation: Windowing (CTRL+TAB for list, or Menu button + keys or directional pads to move/resize) + ImGuiKeyChord ConfigNavWindowingKeyNext; // = ImGuiMod_Ctrl | ImGuiKey_Tab, for reconfiguration (see #4828) + ImGuiKeyChord ConfigNavWindowingKeyPrev; // = ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_Tab ImGuiWindow* NavWindowingTarget; // Target window when doing CTRL+Tab (or Pad Menu + FocusPrev/Next), this window is temporarily displayed top-most! ImGuiWindow* NavWindowingTargetAnim; // Record of last valid NavWindowingTarget until DimBgRatio and NavWindowingHighlightAlpha becomes 0.0f, so the fade-out can stay on it. ImGuiWindow* NavWindowingListWindow; // Internal window actually listing the CTRL+Tab contents @@ -1849,6 +1968,9 @@ struct ImGuiContext ImVector Hooks; // Hooks for extensions (e.g. test engine) ImGuiID HookIdNext; // Next available HookId + // Localization + const char* LocalizationTable[ImGuiLocKey_COUNT]; + // Capture/Logging bool LogEnabled; // Currently capturing ImGuiLogType LogType; // Capture target @@ -1866,6 +1988,7 @@ struct ImGuiContext ImGuiDebugLogFlags DebugLogFlags; ImGuiTextBuffer DebugLogBuf; ImGuiTextIndex DebugLogIndex; + ImU8 DebugLocateFrames; // For DebugLocateItemOnHover(). This is used together with DebugLocateId which is in a hot/cached spot above. bool DebugItemPickerActive; // Item picker is active (started with DebugStartItemPicker()) ImU8 DebugItemPickerMouseButton; ImGuiID DebugItemPickerBreakId; // Will call IM_DEBUG_BREAK() when encountering this ID @@ -1908,7 +2031,6 @@ struct ImGuiContext DebugHookIdInfo = 0; HoveredId = HoveredIdPreviousFrame = 0; HoveredIdAllowOverlap = false; - HoveredIdUsingMouseWheel = HoveredIdPreviousFrameUsingMouseWheel = false; HoveredIdDisabled = false; HoveredIdTimer = HoveredIdNotActiveTimer = 0.0f; ActiveId = 0; @@ -1932,11 +2054,12 @@ struct ImGuiContext LastActiveIdTimer = 0.0f; ActiveIdUsingNavDirMask = 0x00; - ActiveIdUsingKeyInputMask.ClearAllBits(); + ActiveIdUsingAllKeyboardKeys = false; #ifndef IMGUI_DISABLE_OBSOLETE_KEYIO ActiveIdUsingNavInputMask = 0x00; #endif + CurrentFocusScopeId = 0; CurrentItemFlags = ImGuiItemFlags_None; BeginMenuCount = 0; @@ -1966,6 +2089,8 @@ struct ImGuiContext NavTabbingDir = 0; NavTabbingCounter = 0; + ConfigNavWindowingKeyNext = ImGuiMod_Ctrl | ImGuiKey_Tab; + ConfigNavWindowingKeyPrev = ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_Tab; NavWindowingTarget = NavWindowingTargetAnim = NavWindowingListWindow = NULL; NavWindowingTimer = NavWindowingHighlightAlpha = 0.0f; NavWindowingToggleLayer = false; @@ -2017,6 +2142,8 @@ struct ImGuiContext SettingsDirtyTimer = 0.0f; HookIdNext = 0; + memset(LocalizationTable, 0, sizeof(LocalizationTable)); + LogEnabled = false; LogType = ImGuiLogType_None; LogNextPrefix = LogNextSuffix = NULL; @@ -2027,6 +2154,8 @@ struct ImGuiContext LogDepthToExpand = LogDepthToExpandDefault = 2; DebugLogFlags = ImGuiDebugLogFlags_OutputToTTY; + DebugLocateId = 0; + DebugLocateFrames = 0; DebugItemPickerActive = false; DebugItemPickerMouseButton = ImGuiMouseButton_Left; DebugItemPickerBreakId = 0; @@ -2068,7 +2197,6 @@ struct IMGUI_API ImGuiWindowTempData ImGuiNavLayer NavLayerCurrent; // Current layer, 0..31 (we currently only use 0..1) short NavLayersActiveMask; // Which layers have been written to (result from previous frame) short NavLayersActiveMaskNext;// Which layers have been written to (accumulator for current frame) - ImGuiID NavFocusScopeIdCurrent; // Current focus scope ID while appending bool NavHideHighlightOneFrame; bool NavHasScroll; // Set when scrolling can be used (ScrollMax > 0.0f) @@ -2186,6 +2314,7 @@ struct IMGUI_API ImGuiWindow ImGuiWindow* NavLastChildNavWindow; // When going to the menu bar, we remember the child window we came from. (This could probably be made implicit if we kept g.Windows sorted by last focused including child window.) ImGuiID NavLastIds[ImGuiNavLayer_COUNT]; // Last known NavId for this window, per layer (0/1) ImRect NavRectRel[ImGuiNavLayer_COUNT]; // Reference rectangle, in window relative space + ImGuiID NavRootFocusScopeId; // Focus Scope ID at the time of Begin() int MemoryDrawListIdxCapacity; // Backup of last idx/vtx count, so when waking up the window we can preallocate and avoid iterative alloc/copy int MemoryDrawListVtxCapacity; @@ -2631,8 +2760,11 @@ namespace ImGui IMGUI_API void RemoveSettingsHandler(const char* type_name); IMGUI_API ImGuiSettingsHandler* FindSettingsHandler(const char* type_name); + // Localization + IMGUI_API void LocalizeRegisterEntries(const ImGuiLocEntry* entries, int count); + inline const char* LocalizeGetMsg(ImGuiLocKey key) { ImGuiContext& g = *GImGui; const char* msg = g.LocalizationTable[key]; return msg ? msg : "*Missing Text*"; } + // Scrolling - IMGUI_API void SetNextWindowScroll(const ImVec2& scroll); // Use -1.0f on one axis to leave as-is IMGUI_API void SetScrollX(ImGuiWindow* window, float scroll_x); IMGUI_API void SetScrollY(ImGuiWindow* window, float scroll_y); IMGUI_API void SetScrollFromPosX(ImGuiWindow* window, float local_x, float center_x_ratio); @@ -2725,20 +2857,14 @@ namespace ImGui IMGUI_API void SetNavWindow(ImGuiWindow* window); IMGUI_API void SetNavID(ImGuiID id, ImGuiNavLayer nav_layer, ImGuiID focus_scope_id, const ImRect& rect_rel); - // Focus Scope (WIP) - // This is generally used to identify a selection set (multiple of which may be in the same window), as selection - // patterns generally need to react (e.g. clear selection) when landing on an item of the set. - IMGUI_API void PushFocusScope(ImGuiID id); - IMGUI_API void PopFocusScope(); - inline ImGuiID GetFocusedFocusScope() { ImGuiContext& g = *GImGui; return g.NavFocusScopeId; } // Focus scope which is actually active - inline ImGuiID GetFocusScope() { ImGuiContext& g = *GImGui; return g.CurrentWindow->DC.NavFocusScopeIdCurrent; } // Focus scope we are outputting into, set by PushFocusScope() - // Inputs // FIXME: Eventually we should aim to move e.g. IsActiveIdUsingKey() into IsKeyXXX functions. inline bool IsNamedKey(ImGuiKey key) { return key >= ImGuiKey_NamedKey_BEGIN && key < ImGuiKey_NamedKey_END; } inline bool IsNamedKeyOrModKey(ImGuiKey key) { return (key >= ImGuiKey_NamedKey_BEGIN && key < ImGuiKey_NamedKey_END) || key == ImGuiMod_Ctrl || key == ImGuiMod_Shift || key == ImGuiMod_Alt || key == ImGuiMod_Super; } inline bool IsLegacyKey(ImGuiKey key) { return key >= ImGuiKey_LegacyNativeKey_BEGIN && key < ImGuiKey_LegacyNativeKey_END; } + inline bool IsKeyboardKey(ImGuiKey key) { return key >= ImGuiKey_Keyboard_BEGIN && key < ImGuiKey_Keyboard_END; } inline bool IsGamepadKey(ImGuiKey key) { return key >= ImGuiKey_Gamepad_BEGIN && key < ImGuiKey_Gamepad_END; } + inline bool IsMouseKey(ImGuiKey key) { return key >= ImGuiKey_Mouse_BEGIN && key < ImGuiKey_Mouse_END; } inline bool IsAliasKey(ImGuiKey key) { return key >= ImGuiKey_Aliases_BEGIN && key < ImGuiKey_Aliases_END; } inline ImGuiKey ConvertSingleModFlagToKey(ImGuiKey key) { @@ -2751,21 +2877,71 @@ namespace ImGui IMGUI_API ImGuiKeyData* GetKeyData(ImGuiKey key); IMGUI_API void GetKeyChordName(ImGuiKeyChord key_chord, char* out_buf, int out_buf_size); - IMGUI_API void SetItemUsingMouseWheel(); - IMGUI_API void SetActiveIdUsingAllKeyboardKeys(); - inline bool IsActiveIdUsingNavDir(ImGuiDir dir) { ImGuiContext& g = *GImGui; return (g.ActiveIdUsingNavDirMask & (1 << dir)) != 0; } - inline bool IsActiveIdUsingKey(ImGuiKey key) { ImGuiContext& g = *GImGui; return g.ActiveIdUsingKeyInputMask[key]; } - inline void SetActiveIdUsingKey(ImGuiKey key) { ImGuiContext& g = *GImGui; g.ActiveIdUsingKeyInputMask.SetBit(key); } inline ImGuiKey MouseButtonToKey(ImGuiMouseButton button) { IM_ASSERT(button >= 0 && button < ImGuiMouseButton_COUNT); return (ImGuiKey)(ImGuiKey_MouseLeft + button); } IMGUI_API bool IsMouseDragPastThreshold(ImGuiMouseButton button, float lock_threshold = -1.0f); IMGUI_API ImVec2 GetKeyVector2d(ImGuiKey key_left, ImGuiKey key_right, ImGuiKey key_up, ImGuiKey key_down); IMGUI_API float GetNavTweakPressedAmount(ImGuiAxis axis); IMGUI_API int CalcTypematicRepeatAmount(float t0, float t1, float repeat_delay, float repeat_rate); IMGUI_API void GetTypematicRepeatRate(ImGuiInputFlags flags, float* repeat_delay, float* repeat_rate); - IMGUI_API bool IsKeyPressedEx(ImGuiKey key, ImGuiInputFlags flags = 0); -#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] -#endif + IMGUI_API void SetActiveIdUsingAllKeyboardKeys(); + inline bool IsActiveIdUsingNavDir(ImGuiDir dir) { ImGuiContext& g = *GImGui; return (g.ActiveIdUsingNavDirMask & (1 << dir)) != 0; } + + // [EXPERIMENTAL] Low-Level: Key/Input Ownership + // - The idea is that instead of "eating" a given input, we can link to an owner id. + // - Ownership is most often claimed as a result of reacting to a press/down event (but occasionally may be claimed ahead). + // - Input queries can then read input by specifying ImGuiKeyOwner_Any (== 0), ImGuiKeyOwner_None (== -1) or a custom ID. + // - Legacy input queries (without specifying an owner or _Any or _None) are equivalent to using ImGuiKeyOwner_Any (== 0). + // - Input ownership is automatically released on the frame after a key is released. Therefore: + // - for ownership registration happening as a result of a down/press event, the SetKeyOwner() call may be done once (common case). + // - for ownership registration happening ahead of a down/press event, the SetKeyOwner() call needs to be made every frame (happens if e.g. claiming ownership on hover). + // - SetItemKeyOwner() is a shortcut for common simple case. A custom widget will probably want to call SetKeyOwner() multiple times directly based on its interaction state. + // - This is marked experimental because not all widgets are fully honoring the Set/Test idioms. We will need to move forward step by step. + // Please open a GitHub Issue to submit your usage scenario or if there's a use case you need solved. + IMGUI_API ImGuiID GetKeyOwner(ImGuiKey key); + IMGUI_API void SetKeyOwner(ImGuiKey key, ImGuiID owner_id, ImGuiInputFlags flags = 0); + IMGUI_API void SetItemKeyOwner(ImGuiKey key, ImGuiInputFlags flags = 0); // Set key owner to last item if it is hovered or active. Equivalent to 'if (IsItemHovered() || IsItemActive()) { SetKeyOwner(key, GetItemID());'. + IMGUI_API bool TestKeyOwner(ImGuiKey key, ImGuiID owner_id); // Test that key is either not owned, either owned by 'owner_id' + inline ImGuiKeyOwnerData* GetKeyOwnerData(ImGuiKey key) { if (key & ImGuiMod_Mask_) key = ConvertSingleModFlagToKey(key); IM_ASSERT(IsNamedKey(key)); return &GImGui->KeysOwnerData[key - ImGuiKey_NamedKey_BEGIN]; } + + // [EXPERIMENTAL] High-Level: Input Access functions w/ support for Key/Input Ownership + // - Important: legacy IsKeyPressed(ImGuiKey, bool repeat=true) _DEFAULTS_ to repeat, new IsKeyPressed() requires _EXPLICIT_ ImGuiInputFlags_Repeat flag. + // - Expected to be later promoted to public API, the prototypes are designed to replace existing ones (since owner_id can default to Any == 0) + // - Specifying a value for 'ImGuiID owner' will test that EITHER the key is NOT owned (UNLESS locked), EITHER the key is owned by 'owner'. + // Legacy functions use ImGuiKeyOwner_Any meaning that they typically ignore ownership, unless a call to SetKeyOwner() explicitly used ImGuiInputFlags_LockThisFrame or ImGuiInputFlags_LockUntilRelease. + // - Binding generators may want to ignore those for now, or suffix them with Ex() until we decide if this gets moved into public API. + IMGUI_API bool IsKeyDown(ImGuiKey key, ImGuiID owner_id); + IMGUI_API bool IsKeyPressed(ImGuiKey key, ImGuiID owner_id, ImGuiInputFlags flags = 0); // Important: when transitioning from old to new IsKeyPressed(): old API has "bool repeat = true", so would default to repeat. New API requiress explicit ImGuiInputFlags_Repeat. + IMGUI_API bool IsKeyReleased(ImGuiKey key, ImGuiID owner_id); + IMGUI_API bool IsMouseDown(ImGuiMouseButton button, ImGuiID owner_id); + IMGUI_API bool IsMouseClicked(ImGuiMouseButton button, ImGuiID owner_id, ImGuiInputFlags flags = 0); + IMGUI_API bool IsMouseReleased(ImGuiMouseButton button, ImGuiID owner_id); + + // [EXPERIMENTAL] Shortcut Routing + // - ImGuiKeyChord = a ImGuiKey optionally OR-red with ImGuiMod_Alt/ImGuiMod_Ctrl/ImGuiMod_Shift/ImGuiMod_Super. + // ImGuiKey_C (accepted by functions taking ImGuiKey or ImGuiKeyChord) + // ImGuiKey_C | ImGuiMod_Ctrl (accepted by functions taking ImGuiKeyChord) + // ONLY ImGuiMod_XXX values are legal to 'OR' with an ImGuiKey. You CANNOT 'OR' two ImGuiKey values. + // - When using one of the routing flags (e.g. ImGuiInputFlags_RouteFocused): routes requested ahead of time given a chord (key + modifiers) and a routing policy. + // - Routes are resolved during NewFrame(): if keyboard modifiers are matching current ones: SetKeyOwner() is called + route is granted for the frame. + // - Route is granted to a single owner. When multiple requests are made we have policies to select the winning route. + // - Multiple read sites may use the same owner id and will all get the granted route. + // - For routing: when owner_id is 0 we use the current Focus Scope ID as a default owner in order to identify our location. + IMGUI_API bool Shortcut(ImGuiKeyChord key_chord, ImGuiID owner_id = 0, ImGuiInputFlags flags = 0); + IMGUI_API bool SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id = 0, ImGuiInputFlags flags = 0); + IMGUI_API bool TestShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id); + IMGUI_API ImGuiKeyRoutingData* GetShortcutRoutingData(ImGuiKeyChord key_chord); + + // [EXPERIMENTAL] Focus Scope + // This is generally used to identify a unique input location (for e.g. a selection set) + // There is one per window (automatically set in Begin), but: + // - Selection patterns generally need to react (e.g. clear a selection) when landing on one item of the set. + // So in order to identify a set multiple lists in same window may each need a focus scope. + // If you imagine an hypothetical BeginSelectionGroup()/EndSelectionGroup() api, it would likely call PushFocusScope()/EndFocusScope() + // - Shortcut routing also use focus scope as a default location identifier if an owner is not provided. + // We don't use the ID Stack for this as it is common to want them separate. + IMGUI_API void PushFocusScope(ImGuiID id); + IMGUI_API void PopFocusScope(); + inline ImGuiID GetCurrentFocusScope() { ImGuiContext& g = *GImGui; return g.CurrentFocusScopeId; } // Focus scope we are outputting into, set by PushFocusScope() // Drag and Drop IMGUI_API bool IsDragDropActive(); @@ -2952,6 +3128,9 @@ namespace ImGui IMGUI_API void ErrorCheckEndFrameRecover(ImGuiErrorLogCallback log_callback, void* user_data = NULL); IMGUI_API void ErrorCheckEndWindowRecover(ImGuiErrorLogCallback log_callback, void* user_data = NULL); IMGUI_API void ErrorCheckUsingSetCursorPosToExtendParentBoundaries(); + IMGUI_API void DebugLocateItem(ImGuiID target_id); // Call sparingly: only 1 at the same time! + IMGUI_API void DebugLocateItemOnHover(ImGuiID target_id); // Only call on reaction to a mouse Hover: because only 1 at the same time! + IMGUI_API void DebugLocateItemResolveWithLastItem(); inline void DebugDrawItemRect(ImU32 col = IM_COL32(255,0,0,255)) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; GetForegroundDrawList(window)->AddRect(g.LastItemData.Rect.Min, g.LastItemData.Rect.Max, col); } inline void DebugStartItemPicker() { ImGuiContext& g = *GImGui; g.DebugItemPickerActive = true; } IMGUI_API void ShowFontAtlas(ImFontAtlas* atlas); @@ -2975,6 +3154,7 @@ namespace ImGui // Obsolete functions #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + 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(): @@ -2985,6 +3165,9 @@ namespace ImGui 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 #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! +#endif } // namespace ImGui diff --git a/3rdparty/dear-imgui/imgui_tables.cpp b/3rdparty/dear-imgui/imgui_tables.cpp index d5f93f4b0..66a46573b 100644 --- a/3rdparty/dear-imgui/imgui_tables.cpp +++ b/3rdparty/dear-imgui/imgui_tables.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.89 WIP +// dear imgui, v1.89.1 WIP // (tables and columns code) /* @@ -3070,15 +3070,15 @@ void ImGui::TableDrawContextMenu(ImGuiTable* table) if (column != NULL) { const bool can_resize = !(column->Flags & ImGuiTableColumnFlags_NoResize) && column->IsEnabled; - if (MenuItem("Size column to fit###SizeOne", NULL, false, can_resize)) + if (MenuItem(LocalizeGetMsg(ImGuiLocKey_TableSizeOne), NULL, false, can_resize)) // "###SizeOne" TableSetColumnWidthAutoSingle(table, column_n); } const char* size_all_desc; if (table->ColumnsEnabledFixedCount == table->ColumnsEnabledCount && (table->Flags & ImGuiTableFlags_SizingMask_) != ImGuiTableFlags_SizingFixedSame) - size_all_desc = "Size all columns to fit###SizeAll"; // All fixed + size_all_desc = LocalizeGetMsg(ImGuiLocKey_TableSizeAllFit); // "###SizeAll" All fixed else - size_all_desc = "Size all columns to default###SizeAll"; // All stretch or mixed + size_all_desc = LocalizeGetMsg(ImGuiLocKey_TableSizeAllDefault); // "###SizeAll" All stretch or mixed if (MenuItem(size_all_desc, NULL)) TableSetColumnWidthAutoAll(table); want_separator = true; @@ -3087,7 +3087,7 @@ void ImGui::TableDrawContextMenu(ImGuiTable* table) // Ordering if (table->Flags & ImGuiTableFlags_Reorderable) { - if (MenuItem("Reset order", NULL, false, !table->IsDefaultDisplayOrder)) + if (MenuItem(LocalizeGetMsg(ImGuiLocKey_TableResetOrder), NULL, false, !table->IsDefaultDisplayOrder)) table->IsResetDisplayOrderRequest = true; want_separator = true; } diff --git a/3rdparty/dear-imgui/imgui_widgets.cpp b/3rdparty/dear-imgui/imgui_widgets.cpp index 160e9facb..3b8be01e7 100644 --- a/3rdparty/dear-imgui/imgui_widgets.cpp +++ b/3rdparty/dear-imgui/imgui_widgets.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.89 WIP +// dear imgui, v1.89.1 WIP // (widgets code) /* @@ -541,18 +541,20 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool hovered = false; // Mouse handling + const ImGuiID test_owner_id = (flags & ImGuiButtonFlags_NoTestKeyOwner) ? ImGuiKeyOwner_Any : id; if (hovered) { if (!(flags & ImGuiButtonFlags_NoKeyModifiers) || (!g.IO.KeyCtrl && !g.IO.KeyShift && !g.IO.KeyAlt)) { // Poll buttons int mouse_button_clicked = -1; - if ((flags & ImGuiButtonFlags_MouseButtonLeft) && g.IO.MouseClicked[0]) { mouse_button_clicked = 0; } - else if ((flags & ImGuiButtonFlags_MouseButtonRight) && g.IO.MouseClicked[1]) { mouse_button_clicked = 1; } - else if ((flags & ImGuiButtonFlags_MouseButtonMiddle) && g.IO.MouseClicked[2]) { mouse_button_clicked = 2; } - + if ((flags & ImGuiButtonFlags_MouseButtonLeft) && IsMouseClicked(0, test_owner_id)) { mouse_button_clicked = 0; } + else if ((flags & ImGuiButtonFlags_MouseButtonRight) && IsMouseClicked(1, test_owner_id)) { mouse_button_clicked = 1; } + else if ((flags & ImGuiButtonFlags_MouseButtonMiddle) && IsMouseClicked(2, test_owner_id)) { mouse_button_clicked = 2; } if (mouse_button_clicked != -1 && g.ActiveId != id) { + if (!(flags & ImGuiButtonFlags_NoSetKeyOwner)) + SetKeyOwner(MouseButtonToKey(mouse_button_clicked), id); if (flags & (ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClickReleaseAnywhere)) { SetActiveID(id, window); @@ -577,9 +579,9 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool if (flags & ImGuiButtonFlags_PressedOnRelease) { int mouse_button_released = -1; - if ((flags & ImGuiButtonFlags_MouseButtonLeft) && g.IO.MouseReleased[0]) { mouse_button_released = 0; } - else if ((flags & ImGuiButtonFlags_MouseButtonRight) && g.IO.MouseReleased[1]) { mouse_button_released = 1; } - else if ((flags & ImGuiButtonFlags_MouseButtonMiddle) && g.IO.MouseReleased[2]) { mouse_button_released = 2; } + if ((flags & ImGuiButtonFlags_MouseButtonLeft) && IsMouseReleased(0, test_owner_id)) { mouse_button_released = 0; } + else if ((flags & ImGuiButtonFlags_MouseButtonRight) && IsMouseReleased(1, test_owner_id)) { mouse_button_released = 1; } + else if ((flags & ImGuiButtonFlags_MouseButtonMiddle) && IsMouseReleased(2, test_owner_id)) { mouse_button_released = 2; } if (mouse_button_released != -1) { const bool has_repeated_at_least_once = (flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[mouse_button_released] >= g.IO.KeyRepeatDelay; // Repeat mode trumps on release behavior @@ -594,7 +596,7 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool // 'Repeat' mode acts when held regardless of _PressedOn flags (see table above). // Relies on repeat logic of IsMouseClicked() but we may as well do it ourselves if we end up exposing finer RepeatDelay/RepeatRate settings. if (g.ActiveId == id && (flags & ImGuiButtonFlags_Repeat)) - if (g.IO.MouseDownDuration[g.ActiveIdMouseButton] > 0.0f && IsMouseClicked(g.ActiveIdMouseButton, true)) + if (g.IO.MouseDownDuration[g.ActiveIdMouseButton] > 0.0f && IsMouseClicked(g.ActiveIdMouseButton, test_owner_id, ImGuiInputFlags_Repeat)) pressed = true; } @@ -640,8 +642,7 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool g.ActiveIdClickOffset = g.IO.MousePos - bb.Min; const int mouse_button = g.ActiveIdMouseButton; - IM_ASSERT(mouse_button >= 0 && mouse_button < ImGuiMouseButton_COUNT); - if (g.IO.MouseDown[mouse_button]) + if (IsMouseDown(mouse_button, test_owner_id)) { held = true; } @@ -654,7 +655,8 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool // Report as pressed when releasing the mouse (this is the most common path) bool is_double_click_release = (flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseReleased[mouse_button] && g.IO.MouseClickedLastCount[mouse_button] == 2; bool is_repeating_already = (flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[mouse_button] >= g.IO.KeyRepeatDelay; // Repeat mode trumps - if (!is_double_click_release && !is_repeating_already) + bool is_button_avail_or_owned = TestKeyOwner(MouseButtonToKey(mouse_button), test_owner_id); + if (!is_double_click_release && !is_repeating_already && is_button_avail_or_owned) pressed = true; } ClearActiveID(); @@ -2339,9 +2341,11 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, { // Tabbing or CTRL-clicking on Drag turns it into an InputText const bool input_requested_by_tabbing = temp_input_allowed && (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_FocusedByTabbing) != 0; - const bool clicked = (hovered && g.IO.MouseClicked[0]); - const bool double_clicked = (hovered && g.IO.MouseClickedCount[0] == 2); + const bool clicked = hovered && IsMouseClicked(0, id); + const bool double_clicked = (hovered && g.IO.MouseClickedCount[0] == 2 && TestKeyOwner(ImGuiKey_MouseLeft, id)); const bool make_active = (input_requested_by_tabbing || clicked || double_clicked || g.NavActivateId == id || g.NavActivateInputId == id); + if (make_active && (clicked || double_clicked)) + SetKeyOwner(ImGuiKey_MouseLeft, id); if (make_active && temp_input_allowed) if (input_requested_by_tabbing || (clicked && g.IO.KeyCtrl) || double_clicked || g.NavActivateInputId == id) temp_input_is_active = true; @@ -2945,8 +2949,10 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_dat { // Tabbing or CTRL-clicking on Slider turns it into an input box const bool input_requested_by_tabbing = temp_input_allowed && (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_FocusedByTabbing) != 0; - const bool clicked = (hovered && g.IO.MouseClicked[0]); + const bool clicked = hovered && IsMouseClicked(0, id); const bool make_active = (input_requested_by_tabbing || clicked || g.NavActivateId == id || g.NavActivateInputId == id); + if (make_active && clicked) + SetKeyOwner(ImGuiKey_MouseLeft, id); if (make_active && temp_input_allowed) if (input_requested_by_tabbing || (clicked && g.IO.KeyCtrl) || g.NavActivateInputId == id) temp_input_is_active = true; @@ -3105,8 +3111,11 @@ bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType d format = DataTypeGetInfo(data_type)->PrintFmt; const bool hovered = ItemHoverable(frame_bb, id); - if ((hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || g.NavActivateInputId == id) + const bool clicked = hovered && IsMouseClicked(0, id); + if (clicked || g.NavActivateId == id || g.NavActivateInputId == id) { + if (clicked) + SetKeyOwner(ImGuiKey_MouseLeft, id); SetActiveID(id, window); SetFocusID(id, window); FocusWindow(window); @@ -4102,24 +4111,24 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ SetActiveID(id, window); SetFocusID(id, window); FocusWindow(window); - - // Declare our inputs + } + if (g.ActiveId == id) + { + // Declare some inputs, the other are registered and polled via Shortcut() routing system. + if (user_clicked) + SetKeyOwner(ImGuiKey_MouseLeft, id); g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right); if (is_multiline || (flags & ImGuiInputTextFlags_CallbackHistory)) g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down); - SetActiveIdUsingKey(ImGuiKey_Escape); - SetActiveIdUsingKey(ImGuiKey_NavGamepadCancel); - SetActiveIdUsingKey(ImGuiKey_Home); - SetActiveIdUsingKey(ImGuiKey_End); + SetKeyOwner(ImGuiKey_Home, id); + SetKeyOwner(ImGuiKey_End, id); if (is_multiline) { - SetActiveIdUsingKey(ImGuiKey_PageUp); - SetActiveIdUsingKey(ImGuiKey_PageDown); + SetKeyOwner(ImGuiKey_PageUp, id); + SetKeyOwner(ImGuiKey_PageDown, id); } if (flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_AllowTabInput)) // Disable keyboard tabbing out as we will use the \t character. - { - SetActiveIdUsingKey(ImGuiKey_Tab); - } + SetKeyOwner(ImGuiKey_Tab, id); } // We have an edge case if ActiveId was set through another widget (e.g. widget being swapped), clear id immediately (don't wait until the end of the function) @@ -4289,26 +4298,26 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0); const bool is_osx = io.ConfigMacOSXBehaviors; - const bool is_osx_shift_shortcut = is_osx && (io.KeyMods == (ImGuiMod_Super | ImGuiMod_Shift)); const bool is_wordmove_key_down = is_osx ? io.KeyAlt : io.KeyCtrl; // OS X style: Text editing cursor movement using Alt instead of Ctrl const bool is_startend_key_down = is_osx && io.KeySuper && !io.KeyCtrl && !io.KeyAlt; // OS X style: Line/Text Start and End using Cmd+Arrows instead of Home/End - const bool is_ctrl_key_only = (io.KeyMods == ImGuiMod_Ctrl); - const bool is_shift_key_only = (io.KeyMods == ImGuiMod_Shift); - const bool is_shortcut_key = g.IO.ConfigMacOSXBehaviors ? (io.KeyMods == ImGuiMod_Super) : (io.KeyMods == ImGuiMod_Ctrl); - const bool is_cut = ((is_shortcut_key && IsKeyPressed(ImGuiKey_X)) || (is_shift_key_only && IsKeyPressed(ImGuiKey_Delete))) && !is_readonly && !is_password && (!is_multiline || state->HasSelection()); - const bool is_copy = ((is_shortcut_key && IsKeyPressed(ImGuiKey_C)) || (is_ctrl_key_only && IsKeyPressed(ImGuiKey_Insert))) && !is_password && (!is_multiline || state->HasSelection()); - const bool is_paste = ((is_shortcut_key && IsKeyPressed(ImGuiKey_V)) || (is_shift_key_only && IsKeyPressed(ImGuiKey_Insert))) && !is_readonly; - const bool is_undo = ((is_shortcut_key && IsKeyPressed(ImGuiKey_Z)) && !is_readonly && is_undoable); - const bool is_redo = ((is_shortcut_key && IsKeyPressed(ImGuiKey_Y)) || (is_osx_shift_shortcut && IsKeyPressed(ImGuiKey_Z))) && !is_readonly && is_undoable; - const bool is_select_all = is_shortcut_key && IsKeyPressed(ImGuiKey_A); + // Using Shortcut() with ImGuiInputFlags_RouteFocused (default policy) to allow routing operations for other code (e.g. calling window trying to use CTRL+A and CTRL+B: formet would be handled by InputText) + // Otherwise we could simply assume that we own the keys as we are active. + const ImGuiInputFlags f_repeat = ImGuiInputFlags_Repeat; + const bool is_cut = (Shortcut(ImGuiMod_Shortcut | ImGuiKey_X, id, f_repeat) || Shortcut(ImGuiMod_Shift | ImGuiKey_Delete, id, f_repeat)) && !is_readonly && !is_password && (!is_multiline || state->HasSelection()); + const bool is_copy = (Shortcut(ImGuiMod_Shortcut | ImGuiKey_C, id) || Shortcut(ImGuiMod_Ctrl | ImGuiKey_Insert, id)) && !is_password && (!is_multiline || state->HasSelection()); + const bool is_paste = (Shortcut(ImGuiMod_Shortcut | ImGuiKey_V, id, f_repeat) || Shortcut(ImGuiMod_Shift | ImGuiKey_Insert, id, f_repeat)) && !is_readonly; + const bool is_undo = (Shortcut(ImGuiMod_Shortcut | ImGuiKey_Z, id, f_repeat)) && !is_readonly && is_undoable; + const bool is_redo = (Shortcut(ImGuiMod_Shortcut | ImGuiKey_Y, id, f_repeat) || (is_osx && Shortcut(ImGuiMod_Shortcut | ImGuiMod_Shift | ImGuiKey_Z, id, f_repeat))) && !is_readonly && is_undoable; + const bool is_select_all = Shortcut(ImGuiMod_Shortcut | ImGuiKey_A, id); // We allow validate/cancel with Nav source (gamepad) to makes it easier to undo an accidental NavInput press with no keyboard wired, but otherwise it isn't very useful. const bool nav_gamepad_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (io.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0; const bool is_enter_pressed = IsKeyPressed(ImGuiKey_Enter, true) || IsKeyPressed(ImGuiKey_KeypadEnter, true); const bool is_gamepad_validate = nav_gamepad_active && (IsKeyPressed(ImGuiKey_NavGamepadActivate, false) || IsKeyPressed(ImGuiKey_NavGamepadInput, false)); - const bool is_cancel = IsKeyPressed(ImGuiKey_Escape, false) || (nav_gamepad_active && IsKeyPressed(ImGuiKey_NavGamepadCancel, false)); + const bool is_cancel = Shortcut(ImGuiKey_Escape, id, f_repeat) || (nav_gamepad_active && Shortcut(ImGuiKey_NavGamepadCancel, id, f_repeat)); + // FIXME: Should use more Shortcut() and reduce IsKeyPressed()+SetKeyOwner(), but requires modifiers combination to be taken account of. if (IsKeyPressed(ImGuiKey_LeftArrow)) { state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINESTART : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDLEFT : STB_TEXTEDIT_K_LEFT) | k_mask); } else if (IsKeyPressed(ImGuiKey_RightArrow)) { state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINEEND : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDRIGHT : STB_TEXTEDIT_K_RIGHT) | k_mask); } else if (IsKeyPressed(ImGuiKey_UpArrow) && is_multiline) { if (io.KeyCtrl) SetScrollY(draw_window, ImMax(draw_window->Scroll.y - g.FontSize, 0.0f)); else state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTSTART : STB_TEXTEDIT_K_UP) | k_mask); } @@ -4860,6 +4869,7 @@ void ImGui::DebugNodeInputTextState(ImGuiInputTextState* state) ImStb::STB_TexteditState* stb_state = &state->Stb; ImStb::StbUndoState* undo_state = &stb_state->undostate; Text("ID: 0x%08X, ActiveID: 0x%08X", state->ID, g.ActiveId); + DebugLocateItemOnHover(state->ID); Text("CurLenW: %d, CurLenA: %d, Cursor: %d, Selection: %d..%d", state->CurLenA, state->CurLenW, stb_state->cursor, stb_state->select_start, stb_state->select_end); Text("undo_point: %d, redo_point: %d, undo_char_point: %d, redo_char_point: %d", undo_state->undo_point, undo_state->redo_point, undo_state->undo_char_point, undo_state->redo_char_point); if (BeginChild("undopoints", ImVec2(0.0f, GetTextLineHeight() * 15), true)) // Visualize undo state @@ -5108,7 +5118,10 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag if (label != label_display_end && !(flags & ImGuiColorEditFlags_NoLabel)) { + // Position not necessarily next to last submitted button (e.g. if style.ColorButtonPosition == ImGuiDir_Left), + // but we need to use SameLine() to setup baseline correctly. Might want to refactor SameLine() to simplify this. SameLine(0.0f, style.ItemInnerSpacing.x); + window->DC.CursorPos.x = pos.x + ((flags & ImGuiColorEditFlags_NoInputs) ? w_button : w_full + style.ItemInnerSpacing.x); TextEx(label, label_display_end); } @@ -6328,6 +6341,7 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl // We use NoHoldingActiveID on menus so user can click and _hold_ on a menu then drag to browse child entries ImGuiButtonFlags button_flags = 0; if (flags & ImGuiSelectableFlags_NoHoldingActiveID) { button_flags |= ImGuiButtonFlags_NoHoldingActiveId; } + if (flags & ImGuiSelectableFlags_NoSetKeyOwner) { button_flags |= ImGuiButtonFlags_NoSetKeyOwner; } if (flags & ImGuiSelectableFlags_SelectOnClick) { button_flags |= ImGuiButtonFlags_PressedOnClick; } if (flags & ImGuiSelectableFlags_SelectOnRelease) { button_flags |= ImGuiButtonFlags_PressedOnRelease; } if (flags & ImGuiSelectableFlags_AllowDoubleClick) { button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick; } @@ -6344,7 +6358,7 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl // - (1) it would require focus scope to be set, need exposing PushFocusScope() or equivalent (e.g. BeginSelection() calling PushFocusScope()) // - (2) usage will fail with clipped items // The multi-select API aim to fix those issues, e.g. may be replaced with a BeginSelection() API. - if ((flags & ImGuiSelectableFlags_SelectOnNav) && g.NavJustMovedToId != 0 && g.NavJustMovedToFocusScopeId == window->DC.NavFocusScopeIdCurrent) + if ((flags & ImGuiSelectableFlags_SelectOnNav) && g.NavJustMovedToId != 0 && g.NavJustMovedToFocusScopeId == g.CurrentFocusScopeId) if (g.NavJustMovedToId == id) selected = pressed = true; @@ -6353,7 +6367,7 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl { if (!g.NavDisableMouseHover && g.NavWindow == window && g.NavLayer == window->DC.NavLayerCurrent) { - SetNavID(id, window->DC.NavLayerCurrent, window->DC.NavFocusScopeIdCurrent, WindowRectAbsToRel(window, bb)); // (bb == NavRect) + SetNavID(id, window->DC.NavLayerCurrent, g.CurrentFocusScopeId, WindowRectAbsToRel(window, bb)); // (bb == NavRect) g.NavDisableHighlight = true; } } @@ -7010,7 +7024,9 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) BeginDisabled(); const ImGuiMenuColumns* offsets = &window->DC.MenuColumns; bool pressed; - const ImGuiSelectableFlags selectable_flags = ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_SelectOnClick | ImGuiSelectableFlags_DontClosePopups; + + // We use ImGuiSelectableFlags_NoSetKeyOwner to allow down on one menu item, move, up on another. + const ImGuiSelectableFlags selectable_flags = ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_NoSetKeyOwner | ImGuiSelectableFlags_SelectOnClick | ImGuiSelectableFlags_DontClosePopups; if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) { // Menu inside an horizontal menu bar @@ -7202,7 +7218,8 @@ bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut if (!enabled) BeginDisabled(); - const ImGuiSelectableFlags selectable_flags = ImGuiSelectableFlags_SelectOnRelease | ImGuiSelectableFlags_SetNavIdOnHover; + // We use ImGuiSelectableFlags_NoSetKeyOwner to allow down on one menu item, move, up on another. + const ImGuiSelectableFlags selectable_flags = ImGuiSelectableFlags_SelectOnRelease | ImGuiSelectableFlags_NoSetKeyOwner | ImGuiSelectableFlags_SetNavIdOnHover; const ImGuiMenuColumns* offsets = &window->DC.MenuColumns; if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) {