Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/wolfpld/tracy.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'imgui/imgui.cpp')
-rw-r--r--imgui/imgui.cpp816
1 files changed, 653 insertions, 163 deletions
diff --git a/imgui/imgui.cpp b/imgui/imgui.cpp
index 3f239a5c..3e3a2e5e 100644
--- a/imgui/imgui.cpp
+++ b/imgui/imgui.cpp
@@ -65,7 +65,7 @@ CODE
// [SECTION] MISC HELPERS/UTILITIES (Color functions)
// [SECTION] ImGuiStorage
// [SECTION] ImGuiTextFilter
-// [SECTION] ImGuiTextBuffer
+// [SECTION] ImGuiTextBuffer, ImGuiTextIndex
// [SECTION] ImGuiListClipper
// [SECTION] STYLING
// [SECTION] RENDER HELPERS
@@ -392,6 +392,7 @@ CODE
- likewise io.MousePos and GetMousePos() will use OS coordinates.
If you query mouse positions to interact with non-imgui coordinates you will need to offset them, e.g. subtract GetWindowViewport()->Pos.
+ - 2022/10/26 (1.89) - commented out redirecting OpenPopupContextItem() which was briefly the name of OpenPopupOnItemClick() from 1.77 to 1.79.
- 2022/10/12 (1.89) - removed runtime patching of invalid "%f"/"%0.f" format strings for DragInt()/SliderInt(). This was obsoleted in 1.61 (May 2018). See 1.61 changelog for details.
- 2022/09/26 (1.89) - renamed and merged keyboard modifiers key enums and flags into a same set. Kept inline redirection enums (will obsolete).
- ImGuiKey_ModCtrl and ImGuiModFlags_Ctrl -> ImGuiMod_Ctrl
@@ -401,7 +402,7 @@ CODE
the ImGuiKey_ModXXX were introduced in 1.87 and mostly used by backends.
the ImGuiModFlags_XXX have been exposed in imgui.h but not really used by any public api only by third-party extensions.
exceptionally commenting out the older ImGuiKeyModFlags_XXX names ahead of obsolescence schedule to reduce confusion and because they were not meant to be used anyway.
- - 2022/09/12 (1.89) - removed the bizarre legacy default argument for 'TreePush(const void* ptr = NULL)', always pass a pointer value explicitly, NULL is ok.
+ - 2022/09/12 (1.89) - removed the bizarre legacy default argument for 'TreePush(const void* ptr = NULL)', always pass a pointer value explicitly. NULL/nullptr is ok but require cast, e.g. TreePush((void*)nullptr);
- 2022/09/05 (1.89) - commented out redirecting functions/enums names that were marked obsolete in 1.77 and 1.78 (June 2020):
- DragScalar(), DragScalarN(), DragFloat(), DragFloat2(), DragFloat3(), DragFloat4(): For old signatures ending with (..., const char* format, float power = 1.0f) -> use (..., format ImGuiSliderFlags_Logarithmic) if power != 1.0f.
- SliderScalar(), SliderScalarN(), SliderFloat(), SliderFloat2(), SliderFloat3(), SliderFloat4(): For old signatures ending with (..., const char* format, float power = 1.0f) -> use (..., format ImGuiSliderFlags_Logarithmic) if power != 1.0f.
@@ -868,7 +869,6 @@ CODE
#include "imgui_internal.h"
// System includes
-#include <ctype.h> // toupper
#include <stdio.h> // vsnprintf, sscanf, printf
#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier
#include <stddef.h> // intptr_t
@@ -1339,6 +1339,13 @@ void ImGuiIO::ClearInputKeys()
}
KeyCtrl = KeyShift = KeyAlt = KeySuper = false;
KeyMods = ImGuiMod_None;
+ MousePos = ImVec2(-FLT_MAX, -FLT_MAX);
+ for (int n = 0; n < IM_ARRAYSIZE(MouseDown); n++)
+ {
+ MouseDown[n] = false;
+ MouseDownDuration[n] = MouseDownDurationPrev[n] = -1.0f;
+ }
+ MouseWheel = MouseWheelH = 0.0f;
}
static ImGuiInputEvent* FindLatestInputEvent(ImGuiInputEventType type, int arg = -1)
@@ -1670,14 +1677,14 @@ ImVec2 ImTriangleClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c,
int ImStricmp(const char* str1, const char* str2)
{
int d;
- while ((d = toupper(*str2) - toupper(*str1)) == 0 && *str1) { str1++; str2++; }
+ while ((d = ImToUpper(*str2) - ImToUpper(*str1)) == 0 && *str1) { str1++; str2++; }
return d;
}
int ImStrnicmp(const char* str1, const char* str2, size_t count)
{
int d = 0;
- while (count > 0 && (d = toupper(*str2) - toupper(*str1)) == 0 && *str1) { str1++; str2++; count--; }
+ while (count > 0 && (d = ImToUpper(*str2) - ImToUpper(*str1)) == 0 && *str1) { str1++; str2++; count--; }
return d;
}
@@ -1744,14 +1751,14 @@ const char* ImStristr(const char* haystack, const char* haystack_end, const char
if (!needle_end)
needle_end = needle + strlen(needle);
- const char un0 = (char)toupper(*needle);
+ const char un0 = (char)ImToUpper(*needle);
while ((!haystack_end && *haystack) || (haystack_end && haystack < haystack_end))
{
- if (toupper(*haystack) == un0)
+ if (ImToUpper(*haystack) == un0)
{
const char* b = needle + 1;
for (const char* a = haystack + 1; b < needle_end; a++, b++)
- if (toupper(*a) != toupper(*b))
+ if (ImToUpper(*a) != ImToUpper(*b))
break;
if (b == needle_end)
return haystack;
@@ -2515,7 +2522,7 @@ bool ImGuiTextFilter::PassFilter(const char* text, const char* text_end) const
}
//-----------------------------------------------------------------------------
-// [SECTION] ImGuiTextBuffer
+// [SECTION] ImGuiTextBuffer, ImGuiTextIndex
//-----------------------------------------------------------------------------
// On some platform vsnprintf() takes va_list by reference and modifies it.
@@ -2583,6 +2590,20 @@ void ImGuiTextBuffer::appendfv(const char* fmt, va_list args)
va_end(args_copy);
}
+void ImGuiTextIndex::append(const char* base, int old_size, int new_size)
+{
+ IM_ASSERT(old_size >= 0 && new_size >= old_size && new_size >= EndOffset);
+ if (old_size == new_size)
+ return;
+ if (EndOffset == 0 || base[EndOffset - 1] == '\n')
+ LineOffsets.push_back(EndOffset);
+ const char* base_end = base + new_size;
+ for (const char* p = base + old_size; (p = (const char*)memchr(p, '\n', base_end - p)) != 0; )
+ if (++p < base_end) // Don't push a trailing offset on last \n
+ LineOffsets.push_back((int)(intptr_t)(p - base));
+ EndOffset = ImMax(EndOffset, new_size);
+}
+
//-----------------------------------------------------------------------------
// [SECTION] ImGuiListClipper
// This is currently not as flexible/powerful as it should be and really confusing/spaghetti, mostly because we changed
@@ -3597,7 +3618,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
@@ -3613,7 +3634,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;
}
@@ -3797,8 +3817,7 @@ bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id)
// [DEBUG] Item Picker tool!
// We perform the check here because SetHoveredID() is not frequently called (1~ time a frame), making
// the cost of this tool near-zero. We can get slightly better call-stack and support picking non-hovered
- // items if we perform the test in ItemAdd(), but that would incur a small runtime cost.
- // #define IMGUI_DEBUG_TOOL_ITEM_PICKER_EX in imconfig.h if you want this check to also be performed in ItemAdd().
+ // items if we performed the test in ItemAdd(), but that would incur a small runtime cost.
if (g.DebugItemPickerActive && g.HoveredIdPreviousFrame == id)
GetForegroundDrawList()->AddRect(bb.Min, bb.Max, IM_COL32(255, 255, 0, 255));
if (g.DebugItemPickerBreakId == id)
@@ -3811,6 +3830,7 @@ bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id)
return true;
}
+// FIXME: This is inlined/duplicated in ItemAdd()
bool ImGui::IsClippedEx(const ImRect& bb, ImGuiID id)
{
ImGuiContext& g = *GImGui;
@@ -4261,6 +4281,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()
{
@@ -4362,12 +4421,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()
@@ -4459,13 +4531,9 @@ void ImGui::UpdateMouseWheel()
LockWheelingWindow(NULL);
}
- 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;
@@ -4653,6 +4721,11 @@ void ImGui::NewFrame()
g.FramerateSecPerFrameCount = ImMin(g.FramerateSecPerFrameCount + 1, IM_ARRAYSIZE(g.FramerateSecPerFrame));
g.IO.Framerate = (g.FramerateSecPerFrameAccum > 0.0f) ? (1.0f / (g.FramerateSecPerFrameAccum / (float)g.FramerateSecPerFrameCount)) : FLT_MAX;
+ // Process input queue (trickle as many events as possible), turn events into writes to IO structure
+ g.InputEventsTrail.resize(0);
+ UpdateInputEvents(g.IO.ConfigInputTrickleEventQueue);
+
+ // Update viewports (after processing input queue, so io.MouseHoveredViewport is set)
UpdateViewportsNewFrame();
// Setup current font and draw list shared data
@@ -4698,10 +4771,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.
@@ -4729,7 +4800,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
@@ -4740,7 +4814,7 @@ void ImGui::NewFrame()
// 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); }
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
}
@@ -4775,10 +4849,6 @@ void ImGui::NewFrame()
//if (g.IO.AppFocusLost)
// ClosePopupsExceptModals();
- // Process input queue (trickle as many events as possible)
- g.InputEventsTrail.resize(0);
- UpdateInputEvents(g.IO.ConfigInputTrickleEventQueue);
-
// Update keyboard input state
UpdateKeyboardInputs();
@@ -4866,6 +4936,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.
@@ -4957,6 +5029,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();
@@ -4993,6 +5068,7 @@ void ImGui::Shutdown()
}
g.LogBuffer.clear();
g.DebugLogBuf.clear();
+ g.DebugLogIndex.clear();
g.Initialized = false;
}
@@ -5358,6 +5434,7 @@ void ImGui::EndFrame()
g.IO.Fonts->Locked = false;
// Clear Input data for next frame
+ g.IO.AppFocusLost = false;
g.IO.MouseWheel = g.IO.MouseWheelH = 0.0f;
g.IO.InputQueueCharacters.resize(0);
@@ -5614,7 +5691,7 @@ bool ImGui::IsAnyItemFocused()
bool ImGui::IsItemVisible()
{
ImGuiContext& g = *GImGui;
- return g.CurrentWindow->ClipRect.Overlaps(g.LastItemData.Rect);
+ return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible) != 0;
}
bool ImGui::IsItemEdited()
@@ -5635,29 +5712,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();
}
@@ -6002,8 +6063,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 = window->Viewport->Size;
+ ImVec2 avail_size = window->Viewport->WorkSize;
if (window->ViewportOwned)
avail_size = ImVec2(FLT_MAX, FLT_MAX);
const int monitor_idx = window->ViewportAllowPlatformMonitorExtend;
@@ -6168,7 +6228,7 @@ static bool ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& s
if (resize_rect.Min.x > resize_rect.Max.x) ImSwap(resize_rect.Min.x, resize_rect.Max.x);
if (resize_rect.Min.y > resize_rect.Max.y) ImSwap(resize_rect.Min.y, resize_rect.Max.y);
ImGuiID resize_grip_id = window->GetID(resize_grip_n); // == GetWindowResizeCornerID()
- KeepAliveID(resize_grip_id);
+ ItemAdd(resize_rect, resize_grip_id, NULL, ImGuiItemFlags_NoNav);
ButtonBehavior(resize_rect, resize_grip_id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_NoNavFocus);
//GetForegroundDrawList(window)->AddRect(resize_rect.Min, resize_rect.Max, IM_COL32(255, 255, 0, 255));
if (hovered || held)
@@ -6204,7 +6264,7 @@ static bool ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& s
bool hovered, held;
ImRect border_rect = GetResizeBorderRect(window, border_n, grip_hover_inner_size, WINDOWS_HOVER_PADDING);
ImGuiID border_id = window->GetID(border_n + 4); // == GetWindowResizeBorderID()
- KeepAliveID(border_id);
+ ItemAdd(border_rect, border_id, NULL, ImGuiItemFlags_NoNav);
ButtonBehavior(border_rect, border_id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_NoNavFocus);
//GetForegroundDrawLists(window)->AddRect(border_rect.Min, border_rect.Max, IM_COL32(255, 255, 0, 255));
if ((hovered && g.HoveredIdTimer > WINDOWS_RESIZE_FROM_EDGES_FEEDBACK_TIMER) || held)
@@ -6273,7 +6333,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;
@@ -6702,10 +6762,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];
@@ -6715,13 +6787,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;
@@ -6869,6 +6934,9 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
window->DC.MenuBarOffset.x = ImMax(ImMax(window->WindowPadding.x, style.ItemSpacing.x), g.NextWindowData.MenuBarOffsetMinVal.x);
window->DC.MenuBarOffset.y = g.NextWindowData.MenuBarOffsetMinVal.y;
+ bool use_current_size_for_scrollbar_x = window_just_created;
+ bool use_current_size_for_scrollbar_y = window_just_created;
+
// Collapse window by double-clicking on title bar
// At this point we don't have a clipping rectangle setup yet, so we can use the title bar area for hit detection and drawing
if (!(flags & ImGuiWindowFlags_NoTitleBar) && !(flags & ImGuiWindowFlags_NoCollapse) && !window->DockIsActive)
@@ -6880,6 +6948,8 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
if (window->WantCollapseToggle)
{
window->Collapsed = !window->Collapsed;
+ if (!window->Collapsed)
+ use_current_size_for_scrollbar_y = true;
MarkIniSettingsDirty(window);
}
}
@@ -6893,8 +6963,6 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
// Calculate auto-fit size, handle automatic resize
const ImVec2 size_auto_fit = CalcWindowAutoFitSize(window, window->ContentSizeIdeal);
- bool use_current_size_for_scrollbar_x = window_just_created;
- bool use_current_size_for_scrollbar_y = window_just_created;
if ((flags & ImGuiWindowFlags_AlwaysAutoResize) && !window->Collapsed)
{
// Using SetNextWindowSize() overrides ImGuiWindowFlags_AlwaysAutoResize, so it can be used on tooltips/popups, etc.
@@ -6992,11 +7060,11 @@ 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.
// FIXME: Similar to code in GetWindowAllowedExtentRect()
- 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 (!window->ViewportOwned && viewport_rect.GetWidth() > 0 && viewport_rect.GetHeight() > 0.0f)
{
- ClampWindowRect(window, visibility_rect);
+ ClampWindowPos(window, visibility_rect);
}
else if (window->ViewportOwned && g.PlatformIO.Monitors.Size > 0)
{
@@ -7004,7 +7072,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
const ImGuiPlatformMonitor* monitor = GetViewportPlatformMonitor(window->Viewport);
visibility_rect.Min = monitor->WorkPos + visibility_padding;
visibility_rect.Max = monitor->WorkPos + monitor->WorkSize - visibility_padding;
- ClampWindowRect(window, visibility_rect);
+ ClampWindowPos(window, visibility_rect);
}
}
window->Pos = ImFloor(window->Pos);
@@ -7331,9 +7399,17 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
else
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
{
@@ -7342,9 +7418,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
-
if (!(flags & ImGuiWindowFlags_DockNodeHost))
PushClipRect(window->InnerClipRect.Min, window->InnerClipRect.Max, true);
@@ -7446,6 +7519,7 @@ void ImGui::End()
EndColumns();
if (!(window->Flags & ImGuiWindowFlags_DockNodeHost)) // Pop inner window clip rectangle
PopClipRect();
+ PopFocusScope();
// Stop logging
if (!(window->Flags & ImGuiWindowFlags_ChildWindow)) // FIXME: add more options for scope of logging
@@ -7562,7 +7636,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
@@ -8199,18 +8273,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!
@@ -8521,7 +8593,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);
@@ -8535,31 +8607,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
+ ImGuiKeyRoutingIndex idx = (ImGuiKeyRoutingIndex)rt->Entries.Size;
+ rt->Entries.push_back(ImGuiKeyRoutingData());
+ routing_data = &rt->Entries[idx];
+ routing_data->Mods = (ImU16)mods;
+ routing_data->NextEntryIndex = rt->Index[key - ImGuiKey_NamedKey_BEGIN]; // Setup linked list
+ rt->Index[key - ImGuiKey_NamedKey_BEGIN] = idx;
+ return routing_data;
+}
+
+// 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;
+
+ // 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: none
+ int score = 255;
+ if (flags & ImGuiInputFlags_RouteFocused)
+ {
+ ImGuiWindow* location = g.CurrentWindow;
+ ImGuiWindow* focused = g.NavWindow;
+
+ if (g.ActiveId != 0 && g.ActiveId == owner_id)
+ {
+ // 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)
+ score = 1;
+ }
+ else if (focused != NULL && focused->RootWindow == location->RootWindow) // Early out
+ {
+ // 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.
+ for (int next_score = 3; focused != NULL; next_score++)
+ {
+ if (focused == location)
+ {
+ IM_ASSERT(next_score < 255);
+ score = (ImU8)next_score;
+ break;
+ }
+ focused = (focused->RootWindow != focused) ? focused->ParentWindow : NULL; // FIXME: This could be later abstracted as a focus path
+ }
+ }
+ }
+ else
+ {
+ if (flags & ImGuiInputFlags_RouteGlobal)
+ score = 2;
+ else if (flags & ImGuiInputFlags_RouteGlobalLow)
+ score = 254;
+ else // ImGuiInputFlags_RouteGlobalHigh is default, so call to SetShorcutRouting() without no flags are not conditional
+ score = 0;
+ }
+ 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))
@@ -8568,17 +8786,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;
}
@@ -8586,35 +8812,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)
@@ -8874,12 +9127,127 @@ void ImGui::UpdateInputEvents(bool trickle_fast_inputs)
g.InputEventsQueue.erase(g.InputEventsQueue.Data, g.InputEventsQueue.Data + event_n);
// Clear buttons state when focus is lost
- // (this is useful so e.g. releasing Alt after focus loss on Alt-Tab doesn't trigger the Alt menu toggle)
+ // - this is useful so e.g. releasing Alt after focus loss on Alt-Tab doesn't trigger the Alt menu toggle.
+ // - we clear in EndFrame() and not now in order allow application/user code polling this flag
+ // (e.g. custom backend may want to clear additional data, custom widgets may want to react with a "canceling" event).
if (g.IO.AppFocusLost)
- {
g.IO.ClearInputKeys();
- g.IO.AppFocusLost = false;
+}
+
+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)
+ if ((key >= ImGuiKey_Keyboard_BEGIN && key < ImGuiKey_Keyboard_END) || key == ImGuiMod_Ctrl || key == ImGuiMod_Shift || key == ImGuiMod_Alt || key == ImGuiMod_Super)
+ 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)
+ if ((key >= ImGuiKey_Keyboard_BEGIN && key < ImGuiKey_Keyboard_END) || key == ImGuiMod_Ctrl || key == ImGuiMod_Shift || key == ImGuiMod_Alt || key == ImGuiMod_Super)
+ 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;
}
@@ -9137,7 +9505,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();
@@ -9285,25 +9653,19 @@ bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGu
// to reach unclipped widgets. This would work if user had explicit scrolling control (e.g. mapped on a stick).
// We intentionally don't check if g.NavWindow != NULL because g.NavAnyRequest should only be set when it is non null.
// If we crash on a NULL g.NavWindow we need to fix the bug elsewhere.
- window->DC.NavLayersActiveMaskNext |= (1 << window->DC.NavLayerCurrent);
- if (g.NavId == id || g.NavAnyRequest)
- if (g.NavWindow->RootWindowForNav == window->RootWindowForNav)
- if (window == g.NavWindow || ((window->Flags | g.NavWindow->Flags) & ImGuiWindowFlags_NavFlattened))
- NavProcessItem();
+ if (!(g.LastItemData.InFlags & ImGuiItemFlags_NoNav))
+ {
+ window->DC.NavLayersActiveMaskNext |= (1 << window->DC.NavLayerCurrent);
+ if (g.NavId == id || g.NavAnyRequest)
+ if (g.NavWindow->RootWindowForNav == window->RootWindowForNav)
+ if (window == g.NavWindow || ((window->Flags | g.NavWindow->Flags) & ImGuiWindowFlags_NavFlattened))
+ NavProcessItem();
+ }
// [DEBUG] People keep stumbling on this problem and using "" as identifier in the root of a window instead of "##something".
// Empty identifier are valid and useful in a small amount of cases, but 99.9% of the time you want to use "##something".
// READ THE FAQ: https://dearimgui.org/faq
IM_ASSERT(id != window->ID && "Cannot have an empty ID at the root of a window. If you need an empty label, use ## and read the FAQ about how the ID Stack works!");
-
- // [DEBUG] Item Picker tool, when enabling the "extended" version we perform the check in ItemAdd()
-#ifdef IMGUI_DEBUG_TOOL_ITEM_PICKER_EX
- if (id == g.DebugItemPickerBreakId)
- {
- IM_DEBUG_BREAK();
- g.DebugItemPickerBreakId = 0;
- }
-#endif
}
g.NextItemData.Flags = ImGuiNextItemDataFlags_None;
@@ -9313,12 +9675,26 @@ bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGu
#endif
// Clipping test
- const bool is_clipped = IsClippedEx(bb, id);
- if (is_clipped)
- return false;
+ // (FIXME: This is a modified copy of IsClippedEx() so we can reuse the is_rect_visible value)
+ //const bool is_clipped = IsClippedEx(bb, id);
+ //if (is_clipped)
+ // return false;
+ const bool is_rect_visible = bb.Overlaps(window->ClipRect);
+ if (!is_rect_visible)
+ if (id == 0 || (id != g.ActiveId && id != g.NavId))
+ 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)
+ if (is_rect_visible)
+ g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_Visible;
if (IsMouseHoveringRect(bb.Min, bb.Max))
g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredRect;
return true;
@@ -10589,12 +10965,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);
@@ -10775,7 +11151,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);
}
@@ -10818,7 +11194,7 @@ static void ImGui::NavProcessItem()
if (is_tab_stop || (g.NavMoveFlags & ImGuiNavMoveFlags_FocusApi))
NavProcessItemForTabbingRequest(id);
}
- else if ((g.NavId != id || (g.NavMoveFlags & ImGuiNavMoveFlags_AllowCurrentNavId)) && !(item_flags & (ImGuiItemFlags_Disabled | ImGuiItemFlags_NoNav)))
+ else if ((g.NavId != id || (g.NavMoveFlags & ImGuiNavMoveFlags_AllowCurrentNavId)) && !(item_flags & ImGuiItemFlags_Disabled))
{
ImGuiNavItemData* result = (window == g.NavWindow) ? &g.NavMoveResultLocal : &g.NavMoveResultOther;
if (!is_tabbing)
@@ -10842,7 +11218,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)
}
@@ -11034,7 +11410,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;
}
@@ -11044,7 +11421,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;
@@ -11054,7 +11431,7 @@ void ImGui::NavInitWindow(ImGuiWindow* window, bool force_reinit)
else
{
g.NavId = window->NavLastIds[0];
- g.NavFocusScopeId = 0;
+ g.NavFocusScopeId = window->NavRootFocusScopeId;
}
}
@@ -11315,10 +11692,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);
@@ -11378,7 +11755,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;
}
}
@@ -11407,7 +11784,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;
@@ -11525,14 +11902,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)
{
@@ -11561,7 +11937,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;
}
}
@@ -11576,10 +11952,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;
@@ -11589,9 +11965,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);
@@ -11779,8 +12155,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))
{
@@ -11822,17 +12200,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;
@@ -11841,7 +12221,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
@@ -12273,7 +12654,6 @@ const ImGuiPayload* ImGui::AcceptDragDropPayload(const char* type, ImGuiDragDrop
}
// Render default drop visuals
- // FIXME-DRAGDROP: Settle on a proper default visuals for drop target.
payload.Preview = was_accepted_previously;
flags |= (g.DragDropSourceFlags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect); // Source can also inhibit the preview (useful for external sources that live for 1 frame)
if (!(flags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect) && payload.Preview)
@@ -12287,6 +12667,12 @@ const ImGuiPayload* ImGui::AcceptDragDropPayload(const char* type, ImGuiDragDrop
return &payload;
}
+// FIXME-DRAGDROP: Settle on a proper default visuals for drop target.
+void ImGui::RenderDragDropTargetRect(const ImRect& bb)
+{
+ GetWindowDrawList()->AddRect(bb.Min - ImVec2(3.5f, 3.5f), bb.Max + ImVec2(3.5f, 3.5f), GetColorU32(ImGuiCol_DragDropTarget), 0.0f, 0, 2.0f);
+}
+
const ImGuiPayload* ImGui::GetDragDropPayload()
{
ImGuiContext& g = *GImGui;
@@ -15379,8 +15765,17 @@ static void ImGui::DockNodeUpdate(ImGuiDockNode* node)
if (node->IsSplitNode())
IM_ASSERT(node->TabBar == NULL);
if (node->IsRootNode())
- if (g.NavWindow && g.NavWindow->RootWindow->DockNode && g.NavWindow->RootWindow->ParentWindow == host_window)
- node->LastFocusedNodeId = g.NavWindow->RootWindow->DockNode->ID;
+ if (ImGuiWindow* p_window = g.NavWindow ? g.NavWindow->RootWindow : NULL)
+ while (p_window != NULL && p_window->DockNode != NULL)
+ {
+ ImGuiDockNode* p_node = DockNodeGetRootNode(p_window->DockNode);
+ if (p_node == node)
+ {
+ node->LastFocusedNodeId = p_window->DockNode->ID; // Note: not using root node ID!
+ break;
+ }
+ p_window = p_node->HostWindow ? p_node->HostWindow->RootWindow : NULL;
+ }
// Register a hit-test hole in the window unless we are currently dragging a window that is compatible with our dockspace
ImGuiDockNode* central_node = node->CentralNode;
@@ -16600,7 +16995,8 @@ ImGuiDockNode* ImGui::DockNodeTreeFindVisibleNodeByPos(ImGuiDockNode* node, ImVe
if (ImGuiDockNode* hovered_node = DockNodeTreeFindVisibleNodeByPos(node->ChildNodes[1], pos))
return hovered_node;
- return NULL;
+ // This means we are hovering over the splitter/spacing of a parent node
+ return node;
}
//-----------------------------------------------------------------------------
@@ -17469,17 +17865,19 @@ void ImGui::BeginDockableDragDropTarget(ImGuiWindow* window)
const bool do_preview = payload->IsPreview() || payload->IsDelivery();
if (do_preview && (node != NULL || dock_into_floating_window))
{
+ // If we have a non-leaf node it means we are hovering the border of a parent node, in which case only outer markers will appear.
ImGuiDockPreviewData split_inner;
ImGuiDockPreviewData split_outer;
ImGuiDockPreviewData* split_data = &split_inner;
- if (node && (node->ParentNode || node->IsCentralNode()))
+ if (node && (node->ParentNode || node->IsCentralNode() || !node->IsLeafNode()))
if (ImGuiDockNode* root_node = DockNodeGetRootNode(node))
{
DockNodePreviewDockSetup(window, root_node, payload_window, NULL, &split_outer, is_explicit_target, true);
if (split_outer.IsSplitDirExplicit)
split_data = &split_outer;
}
- DockNodePreviewDockSetup(window, node, payload_window, NULL, &split_inner, is_explicit_target, false);
+ if (!node || node->IsLeafNode())
+ DockNodePreviewDockSetup(window, node, payload_window, NULL, &split_inner, is_explicit_target, false);
if (split_data == &split_outer)
split_inner.IsDropAllowed = false;
@@ -18445,7 +18843,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");
@@ -18461,21 +18896,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);
@@ -18614,7 +19048,7 @@ void ImGui::DebugNodeDockNode(ImGuiDockNode* node, const char* label)
if (node->Windows.Size > 0)
open = TreeNodeEx((void*)(intptr_t)node->ID, tree_node_flags, "%s 0x%04X%s: %d windows (vis: '%s')", label, node->ID, node->IsVisible ? "" : " (hidden)", node->Windows.Size, node->VisibleWindow ? node->VisibleWindow->Name : "NULL");
else
- open = TreeNodeEx((void*)(intptr_t)node->ID, tree_node_flags, "%s 0x%04X%s: %s split (vis: '%s')", label, node->ID, node->IsVisible ? "" : " (hidden)", (node->SplitAxis == ImGuiAxis_X) ? "horizontal" : (node->SplitAxis == ImGuiAxis_Y) ? "vertical" : "n/a", node->VisibleWindow ? node->VisibleWindow->Name : "NULL");
+ open = TreeNodeEx((void*)(intptr_t)node->ID, tree_node_flags, "%s 0x%04X%s: %s (vis: '%s')", label, node->ID, node->IsVisible ? "" : " (hidden)", (node->SplitAxis == ImGuiAxis_X) ? "horizontal split" : (node->SplitAxis == ImGuiAxis_Y) ? "vertical split" : "empty", node->VisibleWindow ? node->VisibleWindow->Name : "NULL");
if (!is_alive) { PopStyleColor(); }
if (is_active && IsItemHovered())
if (ImGuiWindow* window = node->HostWindow ? node->HostWindow : node->VisibleWindow)
@@ -19012,13 +19446,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");
@@ -19080,7 +19511,7 @@ void ImGui::DebugNodeWindowsListByBeginStackParent(ImGuiWindow** windows, int wi
}
//-----------------------------------------------------------------------------
-// [SECTION] DEBUG LOG
+// [SECTION] DEBUG LOG WINDOW
//-----------------------------------------------------------------------------
void ImGui::DebugLog(const char* fmt, ...)
@@ -19099,6 +19530,7 @@ void ImGui::DebugLogV(const char* fmt, va_list args)
g.DebugLogBuf.appendfv(fmt, args);
if (g.DebugLogFlags & ImGuiDebugLogFlags_OutputToTTY)
IMGUI_DEBUG_PRINTF("%s", g.DebugLogBuf.begin() + old_size);
+ g.DebugLogIndex.append(g.DebugLogBuf.c_str(), old_size, g.DebugLogBuf.size());
}
void ImGui::ShowDebugLogWindow(bool* p_open)
@@ -19125,12 +19557,38 @@ void ImGui::ShowDebugLogWindow(bool* p_open)
SameLine(); CheckboxFlags("Viewport", &g.DebugLogFlags, ImGuiDebugLogFlags_EventViewport);
if (SmallButton("Clear"))
+ {
g.DebugLogBuf.clear();
+ g.DebugLogIndex.clear();
+ }
SameLine();
if (SmallButton("Copy"))
SetClipboardText(g.DebugLogBuf.c_str());
BeginChild("##log", ImVec2(0.0f, 0.0f), true, ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar);
- TextUnformatted(g.DebugLogBuf.begin(), g.DebugLogBuf.end()); // FIXME-OPT: Could use a line index, but TextUnformatted() has a semi-decent fast path for large text.
+
+ ImGuiListClipper clipper;
+ clipper.Begin(g.DebugLogIndex.size());
+ while (clipper.Step())
+ for (int line_no = clipper.DisplayStart; line_no < clipper.DisplayEnd; line_no++)
+ {
+ 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);
EndChild();
@@ -19142,6 +19600,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()
{