diff options
author | Bartosz Taudul <wolf@nereid.pl> | 2021-08-21 01:52:30 +0300 |
---|---|---|
committer | Bartosz Taudul <wolf@nereid.pl> | 2021-08-21 01:52:30 +0300 |
commit | 30445b656fe94c9f8f26480fc79cf021c9ad67ac (patch) | |
tree | 1c0c935fc3e657d2d2a8622813e28920e24dabdf /imgui/imgui.cpp | |
parent | c82bf3915bae272cb36b5846545ed250627302f3 (diff) |
Update ImGui to 1.84.1 + docking.
Diffstat (limited to 'imgui/imgui.cpp')
-rw-r--r-- | imgui/imgui.cpp | 1001 |
1 files changed, 659 insertions, 342 deletions
diff --git a/imgui/imgui.cpp b/imgui/imgui.cpp index aad29c78..bc1616e4 100644 --- a/imgui/imgui.cpp +++ b/imgui/imgui.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.83 +// dear imgui, v1.84 // (main code and documentation) // Help: @@ -11,7 +11,7 @@ // - FAQ http://dearimgui.org/faq // - Homepage & latest https://github.com/ocornut/imgui // - Releases & changelog https://github.com/ocornut/imgui/releases -// - Gallery https://github.com/ocornut/imgui/issues/3793 (please post your screenshots/video there!) +// - Gallery https://github.com/ocornut/imgui/issues/4451 (please post your screenshots/video there!) // - Wiki https://github.com/ocornut/imgui/wiki (lots of good stuff there) // - Glossary https://github.com/ocornut/imgui/wiki/Glossary // - Issues & support https://github.com/ocornut/imgui/issues @@ -386,6 +386,9 @@ CODE - 2021/XX/XX (1.XX) - Moved IME support functions from io.ImeSetInputScreenPosFn, io.ImeWindowHandle to the PlatformIO api. + - 2021/07/26 (1.84) - commented out redirecting functions/enums names that were marked obsolete in 1.67 and 1.69 (March 2019): + - ImGui::GetOverlayDrawList() -> use ImGui::GetForegroundDrawList() + - ImFont::GlyphRangesBuilder -> use ImFontGlyphRangesBuilder - 2021/05/19 (1.83) - backends: obsoleted direct access to ImDrawCmd::TextureId in favor of calling ImDrawCmd::GetTexID(). - if you are using official backends from the source tree: you have nothing to do. - if you have copied old backend code or using your own: change access to draw_cmd->TextureId to draw_cmd->GetTexID(). @@ -406,7 +409,7 @@ CODE - ImGui::SetScrollHere() -> use ImGui::SetScrollHereY() - 2021/03/11 (1.82) - clarified that ImDrawList::PathArcTo(), ImDrawList::PathArcToFast() won't render with radius < 0.0f. Previously it sorts of accidentally worked but would generally lead to counter-clockwise paths and have an effect on anti-aliasing. - 2021/03/10 (1.82) - upgraded ImDrawList::AddPolyline() and PathStroke() "bool closed" parameter to "ImDrawFlags flags". The matching ImDrawFlags_Closed value is guaranteed to always stay == 1 in the future. - - 2021/02/22 (1.82) - win32+mingw: Re-enabled IME functions by default even under MinGW. In July 2016, issue #738 had me incorrectly disable those default functions for MinGW. MinGW users should: either link with -limm32, either set their imconfig file with '#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS'. + - 2021/02/22 (1.82) - (*undone in 1.84*) win32+mingw: Re-enabled IME functions by default even under MinGW. In July 2016, issue #738 had me incorrectly disable those default functions for MinGW. MinGW users should: either link with -limm32, either set their imconfig file with '#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS'. - 2021/02/17 (1.82) - renamed rarely used style.CircleSegmentMaxError (old default = 1.60f) to style.CircleTessellationMaxError (new default = 0.30f) as the meaning of the value changed. - 2021/02/03 (1.81) - renamed ListBoxHeader(const char* label, ImVec2 size) to BeginListBox(). Kept inline redirection function (will obsolete). - removed ListBoxHeader(const char* label, int items_count, int height_in_items = -1) in favor of specifying size. Kept inline redirection function (will obsolete). @@ -795,6 +798,11 @@ CODE #include <stdint.h> // intptr_t #endif +// [Windows] On non-Visual Studio compilers, we default to IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS unless explicitly enabled +#if defined(_WIN32) && !defined(_MSC_VER) && !defined(IMGUI_ENABLE_WIN32_DEFAULT_IME_FUNCTIONS) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS) +#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS +#endif + // [Windows] OS specific includes (optional) #if defined(_WIN32) && defined(IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS) && defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS) && defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) #define IMGUI_DISABLE_WIN32_FUNCTIONS @@ -946,7 +954,8 @@ const ImGuiID IMGUI_VIEWPORT_DEFAULT_ID = 0x11111111; // Using an arbi static ImGuiViewportP* AddUpdateViewport(ImGuiWindow* window, ImGuiID id, const ImVec2& platform_pos, const ImVec2& size, ImGuiViewportFlags flags); static void UpdateViewportsNewFrame(); static void UpdateViewportsEndFrame(); -static void UpdateSelectWindowViewport(ImGuiWindow* window); +static void WindowSelectViewport(ImGuiWindow* window); +static void WindowSyncOwnedViewport(ImGuiWindow* window, ImGuiWindow* parent_window_in_stack); static bool UpdateTryMergeWindowIntoHostViewport(ImGuiWindow* window, ImGuiViewportP* host_viewport); static bool UpdateTryMergeWindowIntoHostViewports(ImGuiWindow* window); static bool GetWindowAlwaysWantOwnViewport(ImGuiWindow* window); @@ -1004,7 +1013,8 @@ static void* GImAllocatorUserData = NULL; ImGuiStyle::ImGuiStyle() { - Alpha = 1.0f; // Global alpha applies to everything in ImGui + Alpha = 1.0f; // Global alpha applies to everything in Dear ImGui. + DisabledAlpha = 0.60f; // Additional alpha multiplier applied by BeginDisabled(). Multiply over current value of Alpha. WindowPadding = ImVec2(8,8); // Padding within a window WindowRounding = 0.0f; // Radius of window corners rounding. Set to 0.0f to have rectangular windows. Large values tend to lead to variety of artifacts and are not recommended. WindowBorderSize = 1.0f; // Thickness of border around windows. Generally set to 0.0f or 1.0f. Other values not well tested. @@ -1089,7 +1099,7 @@ ImGuiIO::ImGuiIO() DisplaySize = ImVec2(-1.0f, -1.0f); DeltaTime = 1.0f / 60.0f; IniSavingRate = 5.0f; - IniFilename = "imgui.ini"; + IniFilename = "imgui.ini"; // Important: "imgui.ini" is relative to current working dir, most apps will want to lock this to an absolute path (e.g. same path as executables). LogFilename = "imgui_log.txt"; MouseDoubleClickTime = 0.30f; MouseDoubleClickMaxDist = 6.0f; @@ -1205,6 +1215,24 @@ void ImGuiIO::ClearInputCharacters() InputQueueCharacters.resize(0); } +void ImGuiIO::ClearInputKeys() +{ + memset(KeysDown, 0, sizeof(KeysDown)); + for (int n = 0; n < IM_ARRAYSIZE(KeysDownDuration); n++) + KeysDownDuration[n] = KeysDownDurationPrev[n] = -1.0f; + KeyCtrl = KeyShift = KeyAlt = KeySuper = false; + KeyMods = KeyModsPrev = ImGuiKeyModFlags_None; + for (int n = 0; n < IM_ARRAYSIZE(NavInputsDownDuration); n++) + NavInputsDownDuration[n] = NavInputsDownDurationPrev[n] = -1.0f; +} + +void ImGuiIO::AddFocusEvent(bool focused) +{ + // We intentionally overwrite this and process in NewFrame(), in order to give a chance + // to multi-viewports backends to queue AddFocusEvent(false),AddFocusEvent(true) in same frame. + AppFocusLost = !focused; +} + //----------------------------------------------------------------------------- // [SECTION] MISC HELPERS/UTILITIES (Geometry functions) //----------------------------------------------------------------------------- @@ -1734,7 +1762,7 @@ int ImTextCountCharsFromUtf8(const char* in_text, const char* in_text_end) } // Based on stb_to_utf8() from github.com/nothings/stb/ -static inline int ImTextCharToUtf8(char* buf, int buf_size, unsigned int c) +static inline int ImTextCharToUtf8_inline(char* buf, int buf_size, unsigned int c) { if (c < 0x80) { @@ -1769,6 +1797,13 @@ static inline int ImTextCharToUtf8(char* buf, int buf_size, unsigned int c) return 0; } +const char* ImTextCharToUtf8(char out_buf[5], unsigned int c) +{ + int count = ImTextCharToUtf8_inline(out_buf, 5, c); + out_buf[count] = 0; + return out_buf; +} + // Not optimal but we very rarely use this function. int ImTextCountUtf8BytesFromChar(const char* in_text, const char* in_text_end) { @@ -1785,20 +1820,20 @@ static inline int ImTextCountUtf8BytesFromChar(unsigned int c) return 3; } -int ImTextStrToUtf8(char* buf, int buf_size, const ImWchar* in_text, const ImWchar* in_text_end) +int ImTextStrToUtf8(char* out_buf, int out_buf_size, const ImWchar* in_text, const ImWchar* in_text_end) { - char* buf_out = buf; - const char* buf_end = buf + buf_size; - while (buf_out < buf_end - 1 && (!in_text_end || in_text < in_text_end) && *in_text) + char* buf_p = out_buf; + const char* buf_end = out_buf + out_buf_size; + while (buf_p < buf_end - 1 && (!in_text_end || in_text < in_text_end) && *in_text) { unsigned int c = (unsigned int)(*in_text++); if (c < 0x80) - *buf_out++ = (char)c; + *buf_p++ = (char)c; else - buf_out += ImTextCharToUtf8(buf_out, (int)(buf_end - buf_out - 1), c); + buf_p += ImTextCharToUtf8_inline(buf_p, (int)(buf_end - buf_p - 1), c); } - *buf_out = 0; - return (int)(buf_out - buf); + *buf_p = 0; + return (int)(buf_p - out_buf); } int ImTextCountUtf8BytesFromStr(const ImWchar* in_text, const ImWchar* in_text_end) @@ -2540,6 +2575,7 @@ static const ImGuiCol GWindowDockStyleColors[ImGuiWindowDockStyleCol_COUNT] = static const ImGuiStyleVarInfo GStyleVarInfo[] = { { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, Alpha) }, // ImGuiStyleVar_Alpha + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, DisabledAlpha) }, // ImGuiStyleVar_DisabledAlpha { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowPadding) }, // ImGuiStyleVar_WindowPadding { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowRounding) }, // ImGuiStyleVar_WindowRounding { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowBorderSize) }, // ImGuiStyleVar_WindowBorderSize @@ -2819,7 +2855,7 @@ void ImGui::RenderTextEllipsis(ImDrawList* draw_list, const ImVec2& pos_min, con int ellipsis_char_count = 1; if (ellipsis_char == (ImWchar)-1) { - ellipsis_char = (ImWchar)'.'; + ellipsis_char = font->DotChar; ellipsis_char_count = 3; } const ImFontGlyph* glyph = font->FindGlyph(ellipsis_char); @@ -2964,8 +3000,7 @@ ImGuiWindow::~ImGuiWindow() { IM_ASSERT(DrawList == &DrawListInst); IM_DELETE(Name); - for (int i = 0; i != ColumnsStorage.Size; i++) - ColumnsStorage[i].~ImGuiOldColumns(); + ColumnsStorage.clear_destruct(); } ImGuiID ImGuiWindow::GetID(const char* str, const char* str_end) @@ -3165,7 +3200,7 @@ void ImGui::MarkItemEdited(ImGuiID id) //IM_ASSERT(g.CurrentWindow->DC.LastItemId == id); g.ActiveIdHasBeenEditedThisFrame = true; g.ActiveIdHasBeenEditedBefore = true; - g.CurrentWindow->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_Edited; + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_Edited; } static inline bool IsWindowContentHoverable(ImGuiWindow* window, ImGuiHoveredFlags flags) @@ -3201,10 +3236,14 @@ bool ImGui::IsItemHovered(ImGuiHoveredFlags flags) ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; if (g.NavDisableMouseHover && !g.NavDisableHighlight) + { + if ((g.LastItemData.InFlags & ImGuiItemFlags_Disabled) && !(flags & ImGuiHoveredFlags_AllowWhenDisabled)) + return false; return IsItemFocused(); + } // Test for bounding box overlap, as updated as ItemAdd() - ImGuiItemStatusFlags status_flags = window->DC.LastItemStatusFlags; + ImGuiItemStatusFlags status_flags = g.LastItemData.StatusFlags; if (!(status_flags & ImGuiItemStatusFlags_HoveredRect)) return false; IM_ASSERT((flags & (ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows)) == 0); // Flags not supported by this function @@ -3220,7 +3259,7 @@ bool ImGui::IsItemHovered(ImGuiHoveredFlags flags) // Test if another item is active (e.g. being dragged) if ((flags & ImGuiHoveredFlags_AllowWhenBlockedByActiveItem) == 0) - if (g.ActiveId != 0 && g.ActiveId != window->DC.LastItemId && !g.ActiveIdAllowOverlap && g.ActiveId != window->MoveId) + if (g.ActiveId != 0 && g.ActiveId != g.LastItemData.ID && !g.ActiveIdAllowOverlap && g.ActiveId != window->MoveId) return false; // Test if interactions on this window are blocked by an active popup or modal. @@ -3229,12 +3268,12 @@ bool ImGui::IsItemHovered(ImGuiHoveredFlags flags) return false; // Test if the item is disabled - if ((g.CurrentItemFlags & ImGuiItemFlags_Disabled) && !(flags & ImGuiHoveredFlags_AllowWhenDisabled)) + if ((g.LastItemData.InFlags & ImGuiItemFlags_Disabled) && !(flags & ImGuiHoveredFlags_AllowWhenDisabled)) return false; // Special handling for calling after Begin() which represent the title bar or tab. // When the window is collapsed (SkipItems==true) that last item will never be overwritten so we need to detect the case. - if ((window->DC.LastItemId == window->ID || window->DC.LastItemId == window->MoveId) && window->WriteAccessed) + if ((g.LastItemData.ID == window->ID || g.LastItemData.ID == window->MoveId) && window->WriteAccessed) return false; return true; } @@ -3255,7 +3294,7 @@ bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id) return false; if (g.NavDisableMouseHover) return false; - if (!IsWindowContentHoverable(window, ImGuiHoveredFlags_None) || (g.CurrentItemFlags & ImGuiItemFlags_Disabled)) + if (!IsWindowContentHoverable(window, ImGuiHoveredFlags_None)) { g.HoveredIdDisabled = true; return false; @@ -3264,9 +3303,21 @@ bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id) // We exceptionally allow this function to be called with id==0 to allow using it for easy high-level // hover test in widgets code. We could also decide to split this function is two. if (id != 0) - { SetHoveredID(id); + // When disabled we'll return false but still set HoveredId + ImGuiItemFlags item_flags = (g.LastItemData.ID == id ? g.LastItemData.InFlags : g.CurrentItemFlags); + if (item_flags & ImGuiItemFlags_Disabled) + { + // Release active id if turning disabled + if (g.ActiveId == id) + ClearActiveID(); + g.HoveredIdDisabled = true; + return false; + } + + if (id != 0) + { // [DEBUG] Item Picker tool! // We perform the check here because SetHoveredID() is not frequently called (1~ time a frame), making // the cost of this tool near-zero. We can get slightly better call-stack and support picking non-hovered @@ -3294,22 +3345,25 @@ bool ImGui::IsClippedEx(const ImRect& bb, ImGuiID id, bool clip_even_when_logged // This is also inlined in ItemAdd() // Note: if ImGuiItemStatusFlags_HasDisplayRect is set, user needs to set window->DC.LastItemDisplayRect! -void ImGui::SetLastItemData(ImGuiWindow* window, ImGuiID item_id, ImGuiItemStatusFlags item_flags, const ImRect& item_rect) +void ImGui::SetLastItemData(ImGuiID item_id, ImGuiItemFlags in_flags, ImGuiItemStatusFlags item_flags, const ImRect& item_rect) { - window->DC.LastItemId = item_id; - window->DC.LastItemStatusFlags = item_flags; - window->DC.LastItemRect = item_rect; + ImGuiContext& g = *GImGui; + g.LastItemData.ID = item_id; + g.LastItemData.InFlags = in_flags; + g.LastItemData.StatusFlags = item_flags; + g.LastItemData.Rect = item_rect; } +// Called by ItemAdd() // Process TAB/Shift+TAB. Be mindful that this function may _clear_ the ActiveID when tabbing out. void ImGui::ItemFocusable(ImGuiWindow* window, ImGuiID id) { ImGuiContext& g = *GImGui; - IM_ASSERT(id != 0 && id == window->DC.LastItemId); + IM_ASSERT(id != 0 && id == g.LastItemData.ID); // Increment counters // FIXME: ImGuiItemFlags_Disabled should disable more. - const bool is_tab_stop = (g.CurrentItemFlags & (ImGuiItemFlags_NoTabStop | ImGuiItemFlags_Disabled)) == 0; + const bool is_tab_stop = (g.LastItemData.InFlags & (ImGuiItemFlags_NoTabStop | ImGuiItemFlags_Disabled)) == 0; window->DC.FocusCounterRegular++; if (is_tab_stop) { @@ -3331,13 +3385,13 @@ void ImGui::ItemFocusable(ImGuiWindow* window, ImGuiID id) { if (window->DC.FocusCounterRegular == g.TabFocusRequestCurrCounterRegular) { - window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_FocusedByCode; + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_FocusedByCode; return; } if (is_tab_stop && window->DC.FocusCounterTabStop == g.TabFocusRequestCurrCounterTabStop) { g.NavJustTabbedId = id; - window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_FocusedByTabbing; + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_FocusedByTabbing; return; } @@ -3576,8 +3630,9 @@ void ImGui::StartMouseMovingWindow(ImGuiWindow* window) FocusWindow(window); SetActiveID(window->MoveId, window); g.NavDisableHighlight = true; - g.ActiveIdNoClearOnFocusLoss = true; g.ActiveIdClickOffset = g.IO.MouseClickedPos[0] - window->RootWindowDockTree->Pos; + g.ActiveIdNoClearOnFocusLoss = true; + SetActiveIdUsingNavAndKeys(); bool can_move_window = true; if ((window->Flags & ImGuiWindowFlags_NoMove) || (window->RootWindowDockTree->Flags & ImGuiWindowFlags_NoMove)) @@ -3659,8 +3714,8 @@ void ImGui::UpdateMouseMovingWindowNewFrame() // Clear the NoInput window flag set by the Viewport system moving_window->Viewport->Flags &= ~ImGuiViewportFlags_NoInputs; // FIXME-VIEWPORT: Test engine managed to crash here because Viewport was NULL. - ClearActiveID(); g.MovingWindow = NULL; + ClearActiveID(); } } else @@ -3739,8 +3794,6 @@ static void TranslateWindow(ImGuiWindow* window, const ImVec2& delta) window->DC.CursorPos += delta; window->DC.CursorStartPos += delta; window->DC.CursorMaxPos += delta; - window->DC.LastItemRect.Translate(delta); - window->DC.LastItemDisplayRect.Translate(delta); } static void ScaleWindow(ImGuiWindow* window, float scale) @@ -4138,6 +4191,14 @@ void ImGui::NewFrame() g.DragDropWithinTarget = false; g.DragDropHoldJustPressedId = 0; + // 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) + if (g.IO.AppFocusLost) + { + g.IO.ClearInputKeys(); + g.IO.AppFocusLost = false; + } + // Update keyboard input state // Synchronize io.KeyMods with individual modifiers io.KeyXXX bools g.IO.KeyMods = GetMergedKeyModFlags(); @@ -4217,7 +4278,6 @@ void ImGui::NewFrame() g.ItemFlagsStack.resize(0); g.ItemFlagsStack.push_back(ImGuiItemFlags_None); g.GroupStack.resize(0); - ClosePopupsOverWindow(g.NavWindow, false); // Docking DockContextNewFrameUpdateDocking(&g); @@ -4336,9 +4396,7 @@ void ImGui::Shutdown(ImGuiContext* context) CallContextHooks(&g, ImGuiContextHookType_Shutdown); // Clear everything else - for (int i = 0; i < g.Windows.Size; i++) - IM_DELETE(g.Windows[i]); - g.Windows.clear(); + g.Windows.clear_delete(); g.WindowsFocusOrder.clear(); g.WindowsTempSortBuffer.clear(); g.CurrentWindow = NULL; @@ -4355,18 +4413,14 @@ void ImGui::Shutdown(ImGuiContext* context) g.BeginPopupStack.clear(); g.CurrentViewport = g.MouseViewport = g.MouseLastHoveredViewport = NULL; - for (int i = 0; i < g.Viewports.Size; i++) - IM_DELETE(g.Viewports[i]); - g.Viewports.clear(); + g.Viewports.clear_delete(); g.TabBars.Clear(); g.CurrentTabBarStack.clear(); g.ShrinkWidthBuffer.clear(); g.Tables.Clear(); - for (int i = 0; i < g.TablesTempDataStack.Size; i++) - g.TablesTempDataStack[i].~ImGuiTableTempData(); - g.TablesTempDataStack.clear(); + g.TablesTempDataStack.clear_destruct(); g.DrawChannelsTempMergeBuffer.clear(); g.ClipboardHandlerData.clear(); @@ -4691,11 +4745,15 @@ void ImGui::EndFrame() // Clear Input data for next frame g.IO.MouseWheel = g.IO.MouseWheelH = 0.0f; g.IO.InputQueueCharacters.resize(0); + g.IO.KeyModsPrev = g.IO.KeyMods; // doing it here is better than in NewFrame() as we'll tolerate backend writing to KeyMods. If we want to firmly disallow it we should detect it. memset(g.IO.NavInputs, 0, sizeof(g.IO.NavInputs)); CallContextHooks(&g, ImGuiContextHookType_EndFramePost); } +// Prepare the data for rendering so you can call GetDrawData() +// (As with anything within the ImGui:: namspace this doesn't touch your GPU or graphics API at all: +// it is the role of the ImGui_ImplXXXX_RenderDrawData() function provided by the renderer backend) void ImGui::Render() { ImGuiContext& g = *GImGui; @@ -5091,10 +5149,7 @@ bool ImGui::IsItemActive() { ImGuiContext& g = *GImGui; if (g.ActiveId) - { - ImGuiWindow* window = g.CurrentWindow; - return g.ActiveId == window->DC.LastItemId; - } + return g.ActiveId == g.LastItemData.ID; return false; } @@ -5102,21 +5157,17 @@ bool ImGui::IsItemActivated() { ImGuiContext& g = *GImGui; if (g.ActiveId) - { - ImGuiWindow* window = g.CurrentWindow; - if (g.ActiveId == window->DC.LastItemId && g.ActiveIdPreviousFrame != window->DC.LastItemId) + if (g.ActiveId == g.LastItemData.ID && g.ActiveIdPreviousFrame != g.LastItemData.ID) return true; - } return false; } bool ImGui::IsItemDeactivated() { ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - if (window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HasDeactivated) - return (window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_Deactivated) != 0; - return (g.ActiveIdPreviousFrame == window->DC.LastItemId && g.ActiveIdPreviousFrame != 0 && g.ActiveId != window->DC.LastItemId); + if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HasDeactivated) + return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Deactivated) != 0; + return (g.ActiveIdPreviousFrame == g.LastItemData.ID && g.ActiveIdPreviousFrame != 0 && g.ActiveId != g.LastItemData.ID); } bool ImGui::IsItemDeactivatedAfterEdit() @@ -5129,14 +5180,13 @@ bool ImGui::IsItemDeactivatedAfterEdit() bool ImGui::IsItemFocused() { ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - - if (g.NavId != window->DC.LastItemId || g.NavId == 0) + if (g.NavId != g.LastItemData.ID || g.NavId == 0) return false; // Special handling for the dummy item after Begin() which represent the title bar or tab. // When the window is collapsed (SkipItems==true) that last item will never be overwritten so we need to detect the case. - if (window->DC.LastItemId == window->ID && window->WriteAccessed) + ImGuiWindow* window = g.CurrentWindow; + if (g.LastItemData.ID == window->ID && window->WriteAccessed) return false; return true; @@ -5152,13 +5202,13 @@ bool ImGui::IsItemClicked(ImGuiMouseButton mouse_button) bool ImGui::IsItemToggledOpen() { ImGuiContext& g = *GImGui; - return (g.CurrentWindow->DC.LastItemStatusFlags & ImGuiItemStatusFlags_ToggledOpen) ? true : false; + return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_ToggledOpen) ? true : false; } bool ImGui::IsItemToggledSelection() { ImGuiContext& g = *GImGui; - return (g.CurrentWindow->DC.LastItemStatusFlags & ImGuiItemStatusFlags_ToggledSelection) ? true : false; + return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_ToggledSelection) ? true : false; } bool ImGui::IsAnyItemHovered() @@ -5181,14 +5231,14 @@ bool ImGui::IsAnyItemFocused() bool ImGui::IsItemVisible() { - ImGuiWindow* window = GetCurrentWindowRead(); - return window->ClipRect.Overlaps(window->DC.LastItemRect); + ImGuiContext& g = *GImGui; + return g.CurrentWindow->ClipRect.Overlaps(g.LastItemData.Rect); } bool ImGui::IsItemEdited() { - ImGuiWindow* window = GetCurrentWindowRead(); - return (window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_Edited) != 0; + ImGuiContext& g = *GImGui; + return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Edited) != 0; } // Allow last item to be overlapped by a subsequent item. Both may be activated during the same frame before the later one takes priority. @@ -5196,7 +5246,7 @@ bool ImGui::IsItemEdited() void ImGui::SetItemAllowOverlap() { ImGuiContext& g = *GImGui; - ImGuiID id = g.CurrentWindow->DC.LastItemId; + ImGuiID id = g.LastItemData.ID; if (g.HoveredId == id) g.HoveredIdAllowOverlap = true; if (g.ActiveId == id) @@ -5206,29 +5256,39 @@ void ImGui::SetItemAllowOverlap() void ImGui::SetItemUsingMouseWheel() { ImGuiContext& g = *GImGui; - ImGuiID id = g.CurrentWindow->DC.LastItemId; + ImGuiID id = g.LastItemData.ID; if (g.HoveredId == id) g.HoveredIdUsingMouseWheel = true; if (g.ActiveId == id) g.ActiveIdUsingMouseWheel = true; } +void ImGui::SetActiveIdUsingNavAndKeys() +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.ActiveId != 0); + g.ActiveIdUsingNavDirMask = ~(ImU32)0; + g.ActiveIdUsingNavInputMask = ~(ImU32)0; + g.ActiveIdUsingKeyInputMask = ~(ImU64)0; + NavMoveRequestCancel(); +} + ImVec2 ImGui::GetItemRectMin() { - ImGuiWindow* window = GetCurrentWindowRead(); - return window->DC.LastItemRect.Min; + ImGuiContext& g = *GImGui; + return g.LastItemData.Rect.Min; } ImVec2 ImGui::GetItemRectMax() { - ImGuiWindow* window = GetCurrentWindowRead(); - return window->DC.LastItemRect.Max; + ImGuiContext& g = *GImGui; + return g.LastItemData.Rect.Max; } ImVec2 ImGui::GetItemRectSize() { - ImGuiWindow* window = GetCurrentWindowRead(); - return window->DC.LastItemRect.GetSize(); + ImGuiContext& g = *GImGui; + return g.LastItemData.Rect.GetSize(); } bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, bool border, ImGuiWindowFlags flags) @@ -5333,7 +5393,7 @@ void ImGui::EndChild() ItemAdd(bb, 0); } if (g.HoveredWindow == window) - parent_window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_HoveredWindow; + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredWindow; } g.WithinEndChild = false; g.LogLinePosY = -FLT_MAX; // To enforce a carriage return @@ -6011,8 +6071,7 @@ void ImGui::RenderWindowTitleBarContents(ImGuiWindow* window, const ImRect& titl // Title bar text (with: horizontal alignment, avoiding collapse/close button, optional "unsaved document" marker) // FIXME: Refactor text alignment facilities along with RenderText helpers, this is WAY too much messy code.. - const char* UNSAVED_DOCUMENT_MARKER = "*"; - const float marker_size_x = (flags & ImGuiWindowFlags_UnsavedDocument) ? CalcTextSize(UNSAVED_DOCUMENT_MARKER, NULL, false).x : 0.0f; + const float marker_size_x = (flags & ImGuiWindowFlags_UnsavedDocument) ? button_sz * 0.80f : 0.0f; const ImVec2 text_size = CalcTextSize(name, NULL, true) + ImVec2(marker_size_x, 0.0f); // As a nice touch we try to ensure that centered title text doesn't get affected by visibility of Close/Collapse button, @@ -6031,15 +6090,20 @@ void ImGui::RenderWindowTitleBarContents(ImGuiWindow* window, const ImRect& titl ImRect layout_r(title_bar_rect.Min.x + pad_l, title_bar_rect.Min.y, title_bar_rect.Max.x - pad_r, title_bar_rect.Max.y); ImRect clip_r(layout_r.Min.x, layout_r.Min.y, ImMin(layout_r.Max.x + g.Style.ItemInnerSpacing.x, title_bar_rect.Max.x), layout_r.Max.y); - //if (g.IO.KeyShift) window->DrawList->AddRect(layout_r.Min, layout_r.Max, IM_COL32(255, 128, 0, 255)); // [DEBUG] - //if (g.IO.KeyCtrl) window->DrawList->AddRect(clip_r.Min, clip_r.Max, IM_COL32(255, 128, 0, 255)); // [DEBUG] - RenderTextClipped(layout_r.Min, layout_r.Max, name, NULL, &text_size, style.WindowTitleAlign, &clip_r); if (flags & ImGuiWindowFlags_UnsavedDocument) { - ImVec2 marker_pos = ImVec2(ImMax(layout_r.Min.x, layout_r.Min.x + (layout_r.GetWidth() - text_size.x) * style.WindowTitleAlign.x) + text_size.x, layout_r.Min.y) + ImVec2(2 - marker_size_x, 0.0f); - ImVec2 off = ImVec2(0.0f, IM_FLOOR(-g.FontSize * 0.25f)); - RenderTextClipped(marker_pos + off, layout_r.Max + off, UNSAVED_DOCUMENT_MARKER, NULL, NULL, ImVec2(0, style.WindowTitleAlign.y), &clip_r); + ImVec2 marker_pos; + marker_pos.x = ImClamp(layout_r.Min.x + (layout_r.GetWidth() - text_size.x) * style.WindowTitleAlign.x + text_size.x, layout_r.Min.x, layout_r.Max.x); + marker_pos.y = (layout_r.Min.y + layout_r.Max.y) * 0.5f; + if (marker_pos.x > layout_r.Min.x) + { + RenderBullet(window->DrawList, marker_pos, GetColorU32(ImGuiCol_Text)); + clip_r.Max.x = ImMin(clip_r.Max.x, marker_pos.x - (int)(marker_size_x * 0.5f)); + } } + //if (g.IO.KeyShift) window->DrawList->AddRect(layout_r.Min, layout_r.Max, IM_COL32(255, 128, 0, 255)); // [DEBUG] + //if (g.IO.KeyCtrl) window->DrawList->AddRect(clip_r.Min, clip_r.Max, IM_COL32(255, 128, 0, 255)); // [DEBUG] + RenderTextClipped(layout_r.Min, layout_r.Max, name, NULL, &text_size, style.WindowTitleAlign, &clip_r); } void ImGui::UpdateWindowParentAndRootLinks(ImGuiWindow* window, ImGuiWindowFlags flags, ImGuiWindow* parent_window) @@ -6093,21 +6157,23 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) const bool first_begin_of_the_frame = (window->LastFrameActive != current_frame); window->IsFallbackWindow = (g.CurrentWindowStack.Size == 0 && g.WithinFrameScopeWithImplicitWindow); - // Update the Appearing flag - bool window_just_activated_by_user = (window->LastFrameActive < current_frame - 1); // Not using !WasActive because the implicit "Debug" window would always toggle off->on + // Update the Appearing flag (note: the BeginDocked() path may also set this to true later) + bool window_just_activated_by_user = (window->LastFrameActive < current_frame - 1); // Not using !WasActive because the implicit "Debug" window would always toggle off->on if (flags & ImGuiWindowFlags_Popup) { ImGuiPopupData& popup_ref = g.OpenPopupStack[g.BeginPopupStack.Size]; window_just_activated_by_user |= (window->PopupId != popup_ref.PopupId); // We recycle popups so treat window as activated if popup id changed window_just_activated_by_user |= (window != popup_ref.Window); } - window->Appearing = window_just_activated_by_user; - if (window->Appearing) - SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, true); // Update Flags, LastFrameActive, BeginOrderXXX fields + const bool window_was_appearing = window->Appearing; if (first_begin_of_the_frame) { + window->Appearing = window_just_activated_by_user; + if (window->Appearing) + SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, true); + window->FlagsPreviousFrame = window->Flags; window->Flags = (ImGuiWindowFlags)flags; window->LastFrameActive = current_frame; @@ -6129,6 +6195,8 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) { bool has_dock_node = (window->DockId != 0 || window->DockNode != NULL); bool new_auto_dock_node = !has_dock_node && GetWindowAlwaysWantOwnTabBar(window); + bool dock_node_was_visible = window->DockNodeIsVisible; + bool dock_tab_was_visible = window->DockTabIsVisible; if (has_dock_node || new_auto_dock_node) { BeginDocked(window, p_open); @@ -6138,11 +6206,22 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // Docking currently override constraints g.NextWindowData.Flags &= ~ImGuiNextWindowDataFlags_HasSizeConstraint; + + // Amend the Appearing flag + if (window->DockTabIsVisible && !dock_tab_was_visible && dock_node_was_visible && !window->Appearing && !window_was_appearing) + { + window->Appearing = true; + SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, true); + } + } + else + { + window->DockIsActive = window->DockNodeIsVisible = window->DockTabIsVisible = false; } } // Parent window is latched only on the first call to Begin() of the frame, so further append-calls can be done from a different window stack - ImGuiWindow* parent_window_in_stack = window->DockIsActive ? window->DockNode->HostWindow : g.CurrentWindowStack.empty() ? NULL : g.CurrentWindowStack.back(); + ImGuiWindow* parent_window_in_stack = window->DockIsActive ? window->DockNode->HostWindow : g.CurrentWindowStack.empty() ? NULL : g.CurrentWindowStack.back().Window; ImGuiWindow* parent_window = first_begin_of_the_frame ? ((flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup)) ? parent_window_in_stack : NULL) : window->ParentWindow; IM_ASSERT(parent_window != NULL || !(flags & ImGuiWindowFlags_ChildWindow)); @@ -6152,7 +6231,10 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // Add to stack // We intentionally set g.CurrentWindow to NULL to prevent usage until when the viewport is set, then will call SetCurrentWindow() - g.CurrentWindowStack.push_back(window); + ImGuiWindowStackData window_stack_data; + window_stack_data.Window = window; + window_stack_data.ParentLastItemDataBackup = g.LastItemData; + g.CurrentWindowStack.push_back(window_stack_data); g.CurrentWindow = window; window->DC.StackSizesOnBegin.SetToCurrentState(); g.CurrentWindow = NULL; @@ -6289,7 +6371,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // SELECT VIEWPORT // We need to do this before using any style/font sizes, as viewport with a different DPI may affect font sizes. - UpdateSelectWindowViewport(window); + WindowSelectViewport(window); SetCurrentViewport(window, window->Viewport); window->FontDpiScale = (g.IO.ConfigFlags & ImGuiConfigFlags_DpiEnableScaleFonts) ? window->Viewport->DpiScale : 1.0f; SetCurrentWindow(window); @@ -6421,85 +6503,8 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) SetCurrentWindow(window); } - bool viewport_rect_changed = false; if (window->ViewportOwned) - { - // Synchronize window --> viewport in most situations - // Synchronize viewport -> window in case the platform window has been moved or resized from the OS/WM - if (window->Viewport->PlatformRequestMove) - { - window->Pos = window->Viewport->Pos; - MarkIniSettingsDirty(window); - } - else if (memcmp(&window->Viewport->Pos, &window->Pos, sizeof(window->Pos)) != 0) - { - viewport_rect_changed = true; - window->Viewport->Pos = window->Pos; - } - - if (window->Viewport->PlatformRequestResize) - { - window->Size = window->SizeFull = window->Viewport->Size; - MarkIniSettingsDirty(window); - } - else if (memcmp(&window->Viewport->Size, &window->Size, sizeof(window->Size)) != 0) - { - viewport_rect_changed = true; - window->Viewport->Size = window->Size; - } - window->Viewport->UpdateWorkRect(); - - // The viewport may have changed monitor since the global update in UpdateViewportsNewFrame() - // Either a SetNextWindowPos() call in the current frame or a SetWindowPos() call in the previous frame may have this effect. - if (viewport_rect_changed) - UpdateViewportPlatformMonitor(window->Viewport); - - // Update common viewport flags - const ImGuiViewportFlags viewport_flags_to_clear = ImGuiViewportFlags_TopMost | ImGuiViewportFlags_NoTaskBarIcon | ImGuiViewportFlags_NoDecoration | ImGuiViewportFlags_NoRendererClear; - ImGuiViewportFlags viewport_flags = window->Viewport->Flags & ~viewport_flags_to_clear; - const bool is_modal = (flags & ImGuiWindowFlags_Modal) != 0; - const bool is_short_lived_floating_window = (flags & (ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_Popup)) != 0; - if (flags & ImGuiWindowFlags_Tooltip) - viewport_flags |= ImGuiViewportFlags_TopMost; - if ((g.IO.ConfigViewportsNoTaskBarIcon || is_short_lived_floating_window) && !is_modal) - viewport_flags |= ImGuiViewportFlags_NoTaskBarIcon; - if (g.IO.ConfigViewportsNoDecoration || is_short_lived_floating_window) - viewport_flags |= ImGuiViewportFlags_NoDecoration; - - // Not correct to set modal as topmost because: - // - Because other popups can be stacked above a modal (e.g. combo box in a modal) - // - ImGuiViewportFlags_TopMost is currently handled different in backends: in Win32 it is "appear top most" whereas in GLFW and SDL it is "stay topmost" - //if (flags & ImGuiWindowFlags_Modal) - // viewport_flags |= ImGuiViewportFlags_TopMost; - - // For popups and menus that may be protruding out of their parent viewport, we enable _NoFocusOnClick so that clicking on them - // won't steal the OS focus away from their parent window (which may be reflected in OS the title bar decoration). - // Setting _NoFocusOnClick would technically prevent us from bringing back to front in case they are being covered by an OS window from a different app, - // but it shouldn't be much of a problem considering those are already popups that are closed when clicking elsewhere. - if (is_short_lived_floating_window && !is_modal) - viewport_flags |= ImGuiViewportFlags_NoFocusOnAppearing | ImGuiViewportFlags_NoFocusOnClick; - - // We can overwrite viewport flags using ImGuiWindowClass (advanced users) - // We don't default to the main viewport because. - if (window->WindowClass.ParentViewportId) - window->Viewport->ParentViewportId = window->WindowClass.ParentViewportId; - else if ((flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) && parent_window_in_stack) - window->Viewport->ParentViewportId = parent_window_in_stack->Viewport->ID; - else - window->Viewport->ParentViewportId = g.IO.ConfigViewportsNoDefaultParent ? 0 : IMGUI_VIEWPORT_DEFAULT_ID; - if (window->WindowClass.ViewportFlagsOverrideSet) - viewport_flags |= window->WindowClass.ViewportFlagsOverrideSet; - if (window->WindowClass.ViewportFlagsOverrideClear) - viewport_flags &= ~window->WindowClass.ViewportFlagsOverrideClear; - - // We can also tell the backend that clearing the platform window won't be necessary, - // as our window background is filling the viewport and we have disabled BgAlpha. - // FIXME: Work on support for per-viewport transparency (#2766) - if (!(flags & ImGuiWindowFlags_NoBackground)) - viewport_flags |= ImGuiViewportFlags_NoRendererClear; - - window->Viewport->Flags = viewport_flags; - } + WindowSyncOwnedViewport(window, parent_window_in_stack); // Calculate the range of allowed position for that window (to be movable and visible past safe area padding) // When clamping to stay visible, we will enforce that window->Pos stays inside of visibility_rect. @@ -6766,7 +6771,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->DC.NavHasScroll = (window->ScrollMax.y > 0.0f); window->DC.MenuBarAppending = false; - window->DC.MenuColumns.Update(3, style.ItemSpacing.x, window_just_activated_by_user); + window->DC.MenuColumns.Update(style.ItemSpacing.x, window_just_activated_by_user); window->DC.TreeDepth = 0; window->DC.TreeJumpToParentOnPopMask = 0x00; window->DC.ChildWindows.resize(0); @@ -6840,13 +6845,13 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // We fill last item data based on Title Bar/Tab, in order for IsItemHovered() and IsItemActive() to be usable after Begin(). // This is useful to allow creating context menus on title bar only, etc. if (window->DockIsActive) - SetLastItemData(window, window->ID, window->DockTabItemStatusFlags, window->DockTabItemRect); + SetLastItemData(window->ID, g.CurrentItemFlags, window->DockTabItemStatusFlags, window->DockTabItemRect); else - SetLastItemData(window, window->MoveId, IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max, false) ? ImGuiItemStatusFlags_HoveredRect : 0, title_bar_rect); + SetLastItemData(window->MoveId, g.CurrentItemFlags, IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max, false) ? ImGuiItemStatusFlags_HoveredRect : 0, title_bar_rect); #ifdef IMGUI_ENABLE_TEST_ENGINE if (!(window->Flags & ImGuiWindowFlags_NoTitleBar)) - IMGUI_TEST_ENGINE_ITEM_ADD(window->DC.LastItemRect, window->DC.LastItemId); + IMGUI_TEST_ENGINE_ITEM_ADD(g.LastItemData.Rect, g.LastItemData.ID); #endif } else @@ -6857,8 +6862,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) } // Pull/inherit current state - g.CurrentItemFlags = g.ItemFlagsStack.back(); // Inherit from shared stack - window->DC.NavFocusScopeIdCurrent = (flags & ImGuiWindowFlags_ChildWindow) ? parent_window->DC.NavFocusScopeIdCurrent : 0; // Inherit from parent only // -V595 + 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); @@ -6920,6 +6924,12 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) if (window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0 && window->HiddenFramesCannotSkipItems <= 0) skip_items = true; window->SkipItems = skip_items; + + // Sanity check: there are two spots which can set Appearing = true + // - when 'window_just_activated_by_user' is set -> HiddenFramesCannotSkipItems is set -> SkipItems always false + // - in BeginDocked() path when DockNodeIsVisible == DockTabIsVisible == true -> hidden _should_ be all zero // FIXME: Not formally proven, hence the assert. + if (window->SkipItems && !window->Appearing) + IM_ASSERT(window->Appearing == false); // Please report on GitHub if this triggers: https://github.com/ocornut/imgui/issues/4177 } return !window->SkipItems; @@ -6958,11 +6968,12 @@ void ImGui::End() host_window->DC.CursorMaxPos = window->DC.CursorMaxPos + window->WindowPadding - host_window->WindowPadding; // Pop from window stack + g.LastItemData = g.CurrentWindowStack.back().ParentLastItemDataBackup; g.CurrentWindowStack.pop_back(); if (window->Flags & ImGuiWindowFlags_Popup) g.BeginPopupStack.pop_back(); window->DC.StackSizesOnBegin.CompareWithCurrentState(); - SetCurrentWindow(g.CurrentWindowStack.empty() ? NULL : g.CurrentWindowStack.back()); + SetCurrentWindow(g.CurrentWindowStack.Size == 0 ? NULL : g.CurrentWindowStack.back().Window); if (g.CurrentWindow) SetCurrentViewport(g.CurrentWindow, g.CurrentWindow->Viewport); } @@ -7150,6 +7161,35 @@ void ImGui::PopItemFlag() g.CurrentItemFlags = g.ItemFlagsStack.back(); } +// BeginDisabled()/EndDisabled() +// - Those can be nested but this cannot be used to enable an already disabled section (a single BeginDisabled(true) in the stack is enough to keep things disabled) +// - Visually this is currently altering alpha, but it is expected that in a future styling system this would work differently. +// - Feedback welcome at https://github.com/ocornut/imgui/issues/211 +// - BeginDisabled(false) essentially does nothing useful but is provided to facilitate use of boolean expressions. If you can avoid calling BeginDisabled(False)/EndDisabled() best to avoid it. +// - Optimized shortcuts instead of PushStyleVar() + PushItemFlag() +void ImGui::BeginDisabled(bool disabled) +{ + ImGuiContext& g = *GImGui; + bool was_disabled = (g.CurrentItemFlags & ImGuiItemFlags_Disabled) != 0; + g.DisabledAlphaBackup = g.Style.Alpha; + if (!was_disabled && disabled) + g.Style.Alpha *= g.Style.DisabledAlpha; // PushStyleVar(ImGuiStyleVar_Alpha, g.Style.Alpha * g.Style.DisabledAlpha); + if (was_disabled || disabled) + g.CurrentItemFlags |= ImGuiItemFlags_Disabled; + g.ItemFlagsStack.push_back(g.CurrentItemFlags); +} + +void ImGui::EndDisabled() +{ + ImGuiContext& g = *GImGui; + bool was_disabled = (g.CurrentItemFlags & ImGuiItemFlags_Disabled) != 0; + //PopItemFlag(); + g.ItemFlagsStack.pop_back(); + g.CurrentItemFlags &= ~ImGuiItemFlags_Disabled; + if (was_disabled && (g.CurrentItemFlags & ImGuiItemFlags_Disabled) == 0) + g.Style.Alpha = g.DisabledAlphaBackup; //PopStyleVar(); +} + // FIXME: Look into renaming this once we have settled the new Focus/Activation/TabStop system. void ImGui::PushAllowKeyboardFocus(bool allow_keyboard_focus) { @@ -7185,6 +7225,7 @@ void ImGui::PopTextWrapPos() window->DC.TextWrapPosStack.pop_back(); } +// FIXME: We are exposing the docking hierarchy to end-user here (via IsWindowHovered, IsWindowFocused) which is unusual. bool ImGui::IsWindowChildOf(ImGuiWindow* window, ImGuiWindow* potential_parent) { if (window->RootWindowDockTree == potential_parent) @@ -7626,8 +7667,8 @@ void ImGui::SetItemDefaultFocus() if (g.NavWindow == window->RootWindowForNav && (g.NavInitRequest || g.NavInitResultId != 0) && g.NavLayer == window->DC.NavLayerCurrent) { g.NavInitRequest = false; - g.NavInitResultId = window->DC.LastItemId; - g.NavInitResultRectRel = ImRect(window->DC.LastItemRect.Min - window->Pos, window->DC.LastItemRect.Max - window->Pos); + g.NavInitResultId = g.LastItemData.ID; + g.NavInitResultRectRel = ImRect(g.LastItemData.Rect.Min - window->Pos, g.LastItemData.Rect.Max - window->Pos); NavUpdateAnyRequestFlag(); if (!IsItemVisible()) SetScrollHereY(); @@ -7778,8 +7819,7 @@ static void ImGui::ErrorCheckNewFrameSanityChecks() IM_ASSERT((g.IO.DeltaTime > 0.0f || g.FrameCount == 0) && "Need a positive DeltaTime!"); IM_ASSERT((g.FrameCount == 0 || g.FrameCountEnded == g.FrameCount) && "Forgot to call Render() or EndFrame() at the end of the previous frame?"); IM_ASSERT(g.IO.DisplaySize.x >= 0.0f && g.IO.DisplaySize.y >= 0.0f && "Invalid DisplaySize value!"); - IM_ASSERT(g.IO.Fonts->Fonts.Size > 0 && "Font Atlas not built. Did you call io.Fonts->GetTexDataAsRGBA32() / GetTexDataAsAlpha8()?"); - IM_ASSERT(g.IO.Fonts->Fonts[0]->IsLoaded() && "Font Atlas not built. Did you call io.Fonts->GetTexDataAsRGBA32() / GetTexDataAsAlpha8()?"); + IM_ASSERT(g.IO.Fonts->IsBuilt() && "Font Atlas not built! Make sure you called ImGui_ImplXXXX_NewFrame() function for renderer backend, which should call io.Fonts->GetTexDataAsRGBA32() / GetTexDataAsAlpha8()"); IM_ASSERT(g.Style.CurveTessellationTol > 0.0f && "Invalid style setting!"); IM_ASSERT(g.Style.CircleTessellationMaxError > 0.0f && "Invalid style setting!"); IM_ASSERT(g.Style.Alpha >= 0.0f && g.Style.Alpha <= 1.0f && "Invalid style setting!"); // Allows us to avoid a few clamps in color computations @@ -8063,9 +8103,16 @@ bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGu ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; + // Set item data + g.LastItemData.ID = id; + g.LastItemData.Rect = bb; + g.LastItemData.InFlags = g.CurrentItemFlags; + g.LastItemData.StatusFlags = ImGuiItemStatusFlags_None; + + // Directional navigation processing if (id != 0) { - // Navigation processing runs prior to clipping early-out + // Runs prior to clipping early-out // (a) So that NavInitRequest can be honored, for newly opened windows to select a default widget // (b) So that we can scroll up/down past clipped items. This adds a small O(N) cost to regular navigation requests // unfortunately, but it is still limited to one window. It may not scale very well for windows with ten of @@ -8089,11 +8136,6 @@ bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGu } #endif } - - // Equivalent to calling SetLastItemData() - window->DC.LastItemId = id; - window->DC.LastItemRect = bb; - window->DC.LastItemStatusFlags = ImGuiItemStatusFlags_None; g.NextItemData.Flags = ImGuiNextItemDataFlags_None; #ifdef IMGUI_ENABLE_TEST_ENGINE @@ -8114,7 +8156,7 @@ bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGu // We need to calculate this now to take account of the current clipping rectangle (as items like Selectable may change them) if (IsMouseHoveringRect(bb.Min, bb.Max)) - window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_HoveredRect; + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredRect; return true; } @@ -8446,24 +8488,24 @@ void ImGui::EndGroup() const bool group_contains_curr_active_id = (group_data.BackupActiveIdIsAlive != g.ActiveId) && (g.ActiveIdIsAlive == g.ActiveId) && g.ActiveId; const bool group_contains_prev_active_id = (group_data.BackupActiveIdPreviousFrameIsAlive == false) && (g.ActiveIdPreviousFrameIsAlive == true); if (group_contains_curr_active_id) - window->DC.LastItemId = g.ActiveId; + g.LastItemData.ID = g.ActiveId; else if (group_contains_prev_active_id) - window->DC.LastItemId = g.ActiveIdPreviousFrame; - window->DC.LastItemRect = group_bb; + g.LastItemData.ID = g.ActiveIdPreviousFrame; + g.LastItemData.Rect = group_bb; // Forward Hovered flag const bool group_contains_curr_hovered_id = (group_data.BackupHoveredIdIsAlive == false) && g.HoveredId != 0; if (group_contains_curr_hovered_id) - window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_HoveredWindow; + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredWindow; // Forward Edited flag if (group_contains_curr_active_id && g.ActiveIdHasBeenEditedThisFrame) - window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_Edited; + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_Edited; // Forward Deactivated flag - window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_HasDeactivated; + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasDeactivated; if (group_contains_prev_active_id && g.ActiveId != g.ActiveIdPreviousFrame) - window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_Deactivated; + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_Deactivated; g.GroupStack.pop_back(); //window->DrawList->AddRect(group_bb.Min, group_bb.Max, IM_COL32(255,0,255,255)); // [Debug] @@ -8652,7 +8694,7 @@ void ImGui::SetScrollHereX(float center_x_ratio) ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; float spacing_x = ImMax(window->WindowPadding.x, g.Style.ItemSpacing.x); - float target_pos_x = ImLerp(window->DC.LastItemRect.Min.x - spacing_x, window->DC.LastItemRect.Max.x + spacing_x, center_x_ratio); + float target_pos_x = ImLerp(g.LastItemData.Rect.Min.x - spacing_x, g.LastItemData.Rect.Max.x + spacing_x, center_x_ratio); SetScrollFromPosX(window, target_pos_x - window->Pos.x, center_x_ratio); // Convert from absolute to local pos // Tweak: snap on edges when aiming at an item very close to the edge @@ -9048,12 +9090,13 @@ void ImGui::EndPopup() // - This is essentially the same as BeginPopupContextItem() but without the trailing BeginPopup() void ImGui::OpenPopupOnItemClick(const char* str_id, ImGuiPopupFlags popup_flags) { - ImGuiWindow* window = GImGui->CurrentWindow; + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; int mouse_button = (popup_flags & ImGuiPopupFlags_MouseButtonMask_); if (IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) { - ImGuiID id = str_id ? window->GetID(str_id) : window->DC.LastItemId; // If user hasn't passed an ID, we can use the LastItemID. Using LastItemID as a Popup ID won't conflict! - IM_ASSERT(id != 0); // You cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item) + ImGuiID id = str_id ? window->GetID(str_id) : g.LastItemData.ID; // If user hasn't passed an ID, we can use the LastItemID. Using LastItemID as a Popup ID won't conflict! + IM_ASSERT(id != 0); // You cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item) OpenPopupEx(id, popup_flags); } } @@ -9076,11 +9119,12 @@ void ImGui::OpenPopupOnItemClick(const char* str_id, ImGuiPopupFlags popup_flags // The main difference being that this is tweaked to avoid computing the ID twice. bool ImGui::BeginPopupContextItem(const char* str_id, ImGuiPopupFlags popup_flags) { - ImGuiWindow* window = GImGui->CurrentWindow; + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; if (window->SkipItems) return false; - ImGuiID id = str_id ? window->GetID(str_id) : window->DC.LastItemId; // If user hasn't passed an ID, we can use the LastItemID. Using LastItemID as a Popup ID won't conflict! - IM_ASSERT(id != 0); // You cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item) + ImGuiID id = str_id ? window->GetID(str_id) : g.LastItemData.ID; // If user hasn't passed an ID, we can use the LastItemID. Using LastItemID as a Popup ID won't conflict! + IM_ASSERT(id != 0); // You cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item) int mouse_button = (popup_flags & ImGuiPopupFlags_MouseButtonMask_); if (IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) OpenPopupEx(id, popup_flags); @@ -9089,7 +9133,8 @@ bool ImGui::BeginPopupContextItem(const char* str_id, ImGuiPopupFlags popup_flag bool ImGui::BeginPopupContextWindow(const char* str_id, ImGuiPopupFlags popup_flags) { - ImGuiWindow* window = GImGui->CurrentWindow; + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; if (!str_id) str_id = "window_context"; ImGuiID id = window->GetID(str_id); @@ -9102,7 +9147,8 @@ bool ImGui::BeginPopupContextWindow(const char* str_id, ImGuiPopupFlags popup_fl bool ImGui::BeginPopupContextVoid(const char* str_id, ImGuiPopupFlags popup_flags) { - ImGuiWindow* window = GImGui->CurrentWindow; + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; if (!str_id) str_id = "void_context"; ImGuiID id = window->GetID(str_id); @@ -9193,7 +9239,7 @@ ImVec2 ImGui::FindBestWindowPosForPopupEx(const ImVec2& ref_pos, const ImVec2& s } // Note that this is used for popups, which can overlap the non work-area of individual viewports. -ImRect ImGui::GetWindowAllowedExtentRect(ImGuiWindow* window) +ImRect ImGui::GetPopupAllowedExtentRect(ImGuiWindow* window) { ImGuiContext& g = *GImGui; ImRect r_screen; @@ -9218,7 +9264,7 @@ ImVec2 ImGui::FindBestWindowPosForPopup(ImGuiWindow* window) { ImGuiContext& g = *GImGui; - ImRect r_outer = GetWindowAllowedExtentRect(window); + ImRect r_outer = GetPopupAllowedExtentRect(window); if (window->Flags & ImGuiWindowFlags_ChildMenu) { // Child menus typically request _any_ position within the parent menu item, and then we move the new menu outside the parent bounds. @@ -9287,8 +9333,8 @@ void ImGui::SetFocusID(ImGuiID id, ImGuiWindow* window) g.NavLayer = nav_layer; g.NavFocusScopeId = window->DC.NavFocusScopeIdCurrent; window->NavLastIds[nav_layer] = id; - if (window->DC.LastItemId == id) - window->NavRectRel[nav_layer] = ImRect(window->DC.LastItemRect.Min - window->Pos, window->DC.LastItemRect.Max - window->Pos); + if (g.LastItemData.ID == id) + window->NavRectRel[nav_layer] = ImRect(g.LastItemData.Rect.Min - window->Pos, g.LastItemData.Rect.Max - window->Pos); if (g.ActiveIdSource == ImGuiInputSource_Nav) g.NavDisableMouseHover = true; @@ -9385,7 +9431,7 @@ static bool ImGui::NavScoreItem(ImGuiNavItemData* result, ImRect cand) else { // Degenerate case: two overlapping buttons with same center, break ties arbitrarily (note that LastItemId here is really the _previous_ item order, but it doesn't matter) - quadrant = (window->DC.LastItemId < g.NavId) ? ImGuiDir_Left : ImGuiDir_Right; + quadrant = (g.LastItemData.ID < g.NavId) ? ImGuiDir_Left : ImGuiDir_Right; } #if IMGUI_DEBUG_NAV_SCORING @@ -9473,19 +9519,20 @@ static void ImGui::NavProcessItem(ImGuiWindow* window, const ImRect& nav_bb, con //if (!g.IO.NavActive) // [2017/10/06] Removed this possibly redundant test but I am not sure of all the side-effects yet. Some of the feature here will need to work regardless of using a _NoNavInputs flag. // return; - const ImGuiItemFlags item_flags = g.CurrentItemFlags; + const ImGuiItemFlags item_flags = g.LastItemData.InFlags; const ImRect nav_bb_rel(nav_bb.Min - window->Pos, nav_bb.Max - window->Pos); // Process Init Request if (g.NavInitRequest && g.NavLayer == window->DC.NavLayerCurrent) { // Even if 'ImGuiItemFlags_NoNavDefaultFocus' is on (typically collapse/close button) we record the first ResultId so they can be used as a fallback - if (!(item_flags & ImGuiItemFlags_NoNavDefaultFocus) || g.NavInitResultId == 0) + const bool candidate_for_nav_default_focus = (item_flags & (ImGuiItemFlags_NoNavDefaultFocus | ImGuiItemFlags_Disabled)) == 0; + if (candidate_for_nav_default_focus || g.NavInitResultId == 0) { g.NavInitResultId = id; g.NavInitResultRectRel = nav_bb_rel; } - if (!(item_flags & ImGuiItemFlags_NoNavDefaultFocus)) + if (candidate_for_nav_default_focus) { g.NavInitRequest = false; // Found a match, clear request NavUpdateAnyRequestFlag(); @@ -9741,16 +9788,6 @@ static void ImGui::NavUpdate() io.NavInputs[ImGuiNavInput_TweakSlow] = 1.0f; if (io.KeyShift) io.NavInputs[ImGuiNavInput_TweakFast] = 1.0f; - - // AltGR is normally Alt+Ctrl but we can't reliably detect it (not all backends/systems/layout emit it as Alt+Ctrl) - // But also even on keyboards without AltGR we don't want Alt+Ctrl to open menu anyway. - if (io.KeyAlt && !io.KeyCtrl) - io.NavInputs[ImGuiNavInput_KeyMenu_] = 1.0f; - - // We automatically cancel toggling nav layer when any text has been typed while holding Alt. (See #370) - if (io.KeyAlt && !io.KeyCtrl && g.NavWindowingToggleLayer && io.InputQueueCharacters.Size > 0) - g.NavWindowingToggleLayer = false; - #undef NAV_MAP_KEY } memcpy(io.NavInputsDownDurationPrev, io.NavInputsDownDuration, sizeof(io.NavInputsDownDuration)); @@ -10243,6 +10280,8 @@ static void NavUpdateWindowingHighlightWindow(int focus_change_dir) static void ImGui::NavUpdateWindowing() { ImGuiContext& g = *GImGui; + ImGuiIO& io = g.IO; + ImGuiWindow* apply_focus_window = NULL; bool apply_toggle_layer = false; @@ -10254,25 +10293,25 @@ static void ImGui::NavUpdateWindowing() // Fade out if (g.NavWindowingTargetAnim && g.NavWindowingTarget == NULL) { - g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha - g.IO.DeltaTime * 10.0f, 0.0f); + g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha - io.DeltaTime * 10.0f, 0.0f); if (g.DimBgRatio <= 0.0f && g.NavWindowingHighlightAlpha <= 0.0f) g.NavWindowingTargetAnim = NULL; } // Start CTRL-TAB or Square+L/R window selection bool start_windowing_with_gamepad = allow_windowing && !g.NavWindowingTarget && IsNavInputTest(ImGuiNavInput_Menu, ImGuiInputReadMode_Pressed); - bool start_windowing_with_keyboard = allow_windowing && !g.NavWindowingTarget && g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_Tab) && (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard); + bool start_windowing_with_keyboard = allow_windowing && !g.NavWindowingTarget && io.KeyCtrl && IsKeyPressedMap(ImGuiKey_Tab) && (io.ConfigFlags & ImGuiConfigFlags_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)) { g.NavWindowingTarget = g.NavWindowingTargetAnim = window->RootWindow; g.NavWindowingTimer = g.NavWindowingHighlightAlpha = 0.0f; - g.NavWindowingToggleLayer = start_windowing_with_keyboard ? false : true; + g.NavWindowingToggleLayer = start_windowing_with_gamepad ? true : false; // Gamepad starts toggling layer g.NavInputSource = start_windowing_with_keyboard ? ImGuiInputSource_Keyboard : ImGuiInputSource_Gamepad; } // Gamepad update - g.NavWindowingTimer += g.IO.DeltaTime; + g.NavWindowingTimer += io.DeltaTime; if (g.NavWindowingTarget && g.NavInputSource == ImGuiInputSource_Gamepad) { // Highlight only appears after a brief time holding the button, so that a fast tap on PadMenu (to toggle NavLayer) doesn't add visual noise @@ -10304,31 +10343,49 @@ static void ImGui::NavUpdateWindowing() // Visuals only appears after a brief time after pressing TAB the first time, so that a fast CTRL+TAB doesn't add visual noise g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha, ImSaturate((g.NavWindowingTimer - NAV_WINDOWING_HIGHLIGHT_DELAY) / 0.05f)); // 1.0f if (IsKeyPressedMap(ImGuiKey_Tab, true)) - NavUpdateWindowingHighlightWindow(g.IO.KeyShift ? +1 : -1); - if (!g.IO.KeyCtrl) + NavUpdateWindowingHighlightWindow(io.KeyShift ? +1 : -1); + if (!io.KeyCtrl) apply_focus_window = g.NavWindowingTarget; } // Keyboard: Press and Release ALT to toggle menu layer - // FIXME: We lack an explicit IO variable for "is the imgui window focused", so compare mouse validity to detect the common case of backend clearing releases all keys on ALT-TAB - if (IsNavInputTest(ImGuiNavInput_KeyMenu_, ImGuiInputReadMode_Pressed)) + // - 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 (io.KeyMods == ImGuiKeyModFlags_Alt && (io.KeyModsPrev & ImGuiKeyModFlags_Alt) == 0) + { g.NavWindowingToggleLayer = true; - if ((g.ActiveId == 0 || g.ActiveIdAllowOverlap) && g.NavWindowingToggleLayer && IsNavInputTest(ImGuiNavInput_KeyMenu_, ImGuiInputReadMode_Released)) - if (IsMousePosValid(&g.IO.MousePos) == IsMousePosValid(&g.IO.MousePosPrev)) - apply_toggle_layer = true; + g.NavInputSource = ImGuiInputSource_Keyboard; + } + if (g.NavWindowingToggleLayer && g.NavInputSource == ImGuiInputSource_Keyboard) + { + // 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) + g.NavWindowingToggleLayer = false; + + // Apply layer toggle on release + // Important: we don't assume that Alt was previously held in order to handle loss of focus when backend calls io.AddFocusEvent(false) + // Important: as before version <18314 we lacked an explicit IO event for focus gain/loss, we also compare mouse validity to detect old backends clearing mouse pos on focus loss. + if (!(io.KeyMods & ImGuiKeyModFlags_Alt) && (io.KeyModsPrev & ImGuiKeyModFlags_Alt) && g.NavWindowingToggleLayer) + if (g.ActiveId == 0 || g.ActiveIdAllowOverlap) + if (IsMousePosValid(&io.MousePos) == IsMousePosValid(&io.MousePosPrev)) + apply_toggle_layer = true; + if (!io.KeyAlt) + g.NavWindowingToggleLayer = false; + } // Move window if (g.NavWindowingTarget && !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_NoMove)) { ImVec2 move_delta; - if (g.NavInputSource == ImGuiInputSource_Keyboard && !g.IO.KeyShift) + if (g.NavInputSource == ImGuiInputSource_Keyboard && !io.KeyShift) move_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard, ImGuiInputReadMode_Down); if (g.NavInputSource == ImGuiInputSource_Gamepad) move_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_PadLStick, ImGuiInputReadMode_Down); if (move_delta.x != 0.0f || move_delta.y != 0.0f) { const float NAV_MOVE_SPEED = 800.0f; - const float move_speed = ImFloor(NAV_MOVE_SPEED * g.IO.DeltaTime * ImMin(g.IO.DisplayFramebufferScale.x, g.IO.DisplayFramebufferScale.y)); // FIXME: Doesn't handle variable framerate very well + const float move_speed = ImFloor(NAV_MOVE_SPEED * io.DeltaTime * ImMin(io.DisplayFramebufferScale.x, io.DisplayFramebufferScale.y)); // FIXME: Doesn't handle variable framerate very well ImGuiWindow* moving_window = g.NavWindowingTarget->RootWindowDockTree; SetWindowPos(moving_window, moving_window->Pos + move_delta * move_speed, ImGuiCond_Always); MarkIniSettingsDirty(moving_window); @@ -10479,7 +10536,7 @@ bool ImGui::BeginDragDropSource(ImGuiDragDropFlags flags) ImGuiID source_parent_id = 0; if (!(flags & ImGuiDragDropFlags_SourceExtern)) { - source_id = window->DC.LastItemId; + source_id = g.LastItemData.ID; if (source_id != 0) { // Common path: items with ID @@ -10506,15 +10563,16 @@ bool ImGui::BeginDragDropSource(ImGuiDragDropFlags flags) } // Early out - if ((window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect) == 0 && (g.ActiveId == 0 || g.ActiveIdWindow != window)) + if ((g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredRect) == 0 && (g.ActiveId == 0 || g.ActiveIdWindow != window)) return false; // Magic fallback (=somehow reprehensible) to handle items with no assigned ID, e.g. Text(), Image() // We build a throwaway ID based on current ID stack + relative AABB of items in window. // THE IDENTIFIER WON'T SURVIVE ANY REPOSITIONING OF THE WIDGET, so if your widget moves your dragging operation will be canceled. // We don't need to maintain/call ClearActiveID() as releasing the button will early out this function and trigger !ActiveIdIsAlive. - source_id = window->DC.LastItemId = window->GetIDFromRectangle(window->DC.LastItemRect); - bool is_hovered = ItemHoverable(window->DC.LastItemRect, source_id); + // Rely on keeping other window->LastItemXXX fields intact. + source_id = g.LastItemData.ID = window->GetIDFromRectangle(g.LastItemData.Rect); + bool is_hovered = ItemHoverable(g.LastItemData.Rect, source_id); if (is_hovered && g.IO.MouseClicked[mouse_button]) { SetActiveID(source_id, window); @@ -10528,10 +10586,8 @@ bool ImGui::BeginDragDropSource(ImGuiDragDropFlags flags) source_parent_id = window->IDStack.back(); source_drag_active = IsMouseDragging(mouse_button); - // Disable navigation and key inputs while dragging - g.ActiveIdUsingNavDirMask = ~(ImU32)0; - g.ActiveIdUsingNavInputMask = ~(ImU32)0; - g.ActiveIdUsingKeyInputMask = ~(ImU64)0; + // Disable navigation and key inputs while dragging + cancel existing request if any + SetActiveIdUsingNavAndKeys(); } else { @@ -10566,13 +10622,13 @@ bool ImGui::BeginDragDropSource(ImGuiDragDropFlags flags) if (g.DragDropAcceptIdPrev && (g.DragDropAcceptFlags & ImGuiDragDropFlags_AcceptNoPreviewTooltip)) { ImGuiWindow* tooltip_window = g.CurrentWindow; - tooltip_window->SkipItems = true; + tooltip_window->Hidden = tooltip_window->SkipItems = true; tooltip_window->HiddenFramesCanSkipItems = 1; } } if (!(flags & ImGuiDragDropFlags_SourceNoDisableHover) && !(flags & ImGuiDragDropFlags_SourceExtern)) - window->DC.LastItemStatusFlags &= ~ImGuiItemStatusFlags_HoveredRect; + g.LastItemData.StatusFlags &= ~ImGuiItemStatusFlags_HoveredRect; return true; } @@ -10672,14 +10728,14 @@ bool ImGui::BeginDragDropTarget() return false; ImGuiWindow* window = g.CurrentWindow; - if (!(window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect)) + if (!(g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredRect)) return false; ImGuiWindow* hovered_window = g.HoveredWindowUnderMovingWindow; if (hovered_window == NULL || window->RootWindowDockTree != hovered_window->RootWindowDockTree) return false; - const ImRect& display_rect = (window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HasDisplayRect) ? window->DC.LastItemDisplayRect : window->DC.LastItemRect; - ImGuiID id = window->DC.LastItemId; + const ImRect& display_rect = (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HasDisplayRect) ? g.LastItemData.DisplayRect : g.LastItemData.Rect; + ImGuiID id = g.LastItemData.ID; if (id == 0) id = window->GetIDFromRectangle(display_rect); if (g.DragDropPayload.SourceId == id) @@ -10721,17 +10777,11 @@ 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 lives for 1 frame) if (!(flags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect) && payload.Preview) - { - // FIXME-DRAGDROP: Settle on a proper default visuals for drop target. - r.Expand(3.5f); - bool push_clip_rect = !window->ClipRect.Contains(r); - if (push_clip_rect) window->DrawList->PushClipRect(r.Min - ImVec2(1, 1), r.Max + ImVec2(1, 1)); - window->DrawList->AddRect(r.Min, r.Max, GetColorU32(ImGuiCol_DragDropTarget), 0.0f, 0, 2.0f); - if (push_clip_rect) window->DrawList->PopClipRect(); - } + window->DrawList->AddRect(r.Min - ImVec2(3.5f,3.5f), r.Max + ImVec2(3.5f, 3.5f), GetColorU32(ImGuiCol_DragDropTarget), 0.0f, 0, 2.0f); g.DragDropAcceptFrameCount = g.FrameCount; payload.Delivery = was_accepted_previously && !IsMouseDown(g.DragDropMouseButton); // For extern drag sources affecting os window focus, it's easier to just test !IsMouseDown() instead of IsMouseReleased() @@ -11357,7 +11407,8 @@ static void WindowSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandl // - UpdateViewportsNewFrame() [Internal] // - UpdateViewportsEndFrame() [Internal] // - AddUpdateViewport() [Internal] -// - UpdateSelectWindowViewport() [Internal] +// - WindowSelectViewport() [Internal] +// - WindowSyncOwnedViewport() [Internal] // - UpdatePlatformWindows() // - RenderPlatformWindowsDefault() // - FindPlatformMonitorForPos() [Internal] @@ -11373,6 +11424,7 @@ ImGuiViewport* ImGui::GetMainViewport() return g.Viewports[0]; } +// FIXME: This leaks access to viewports not listed in PlatformIO.Viewports[]. Problematic? (#4236) ImGuiViewport* ImGui::FindViewportByID(ImGuiID id) { ImGuiContext& g = *GImGui; @@ -11804,7 +11856,7 @@ ImGuiViewportP* ImGui::AddUpdateViewport(ImGuiWindow* window, ImGuiID id, const } // FIXME-VIEWPORT: This is all super messy and ought to be clarified or rewritten. -static void ImGui::UpdateSelectWindowViewport(ImGuiWindow* window) +static void ImGui::WindowSelectViewport(ImGuiWindow* window) { ImGuiContext& g = *GImGui; ImGuiWindowFlags flags = window->Flags; @@ -11829,7 +11881,7 @@ static void ImGui::UpdateSelectWindowViewport(ImGuiWindow* window) if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasViewport) == 0) { // By default inherit from parent window - if (window->Viewport == NULL && window->ParentWindow && !window->ParentWindow->IsFallbackWindow) + if (window->Viewport == NULL && window->ParentWindow && (!window->ParentWindow->IsFallbackWindow || window->ParentWindow->WasActive)) window->Viewport = window->ParentWindow->Viewport; // Attempt to restore saved viewport id (= window that hasn't been activated yet), try to restore the viewport based on saved 'window->ViewportPos' restored from .ini file @@ -11931,6 +11983,92 @@ static void ImGui::UpdateSelectWindowViewport(ImGuiWindow* window) // window->Flags |= ImGuiWindowFlags_NoTitleBar; } +void ImGui::WindowSyncOwnedViewport(ImGuiWindow* window, ImGuiWindow* parent_window_in_stack) +{ + ImGuiContext& g = *GImGui; + + bool viewport_rect_changed = false; + + // Synchronize window --> viewport in most situations + // Synchronize viewport -> window in case the platform window has been moved or resized from the OS/WM + if (window->Viewport->PlatformRequestMove) + { + window->Pos = window->Viewport->Pos; + MarkIniSettingsDirty(window); + } + else if (memcmp(&window->Viewport->Pos, &window->Pos, sizeof(window->Pos)) != 0) + { + viewport_rect_changed = true; + window->Viewport->Pos = window->Pos; + } + + if (window->Viewport->PlatformRequestResize) + { + window->Size = window->SizeFull = window->Viewport->Size; + MarkIniSettingsDirty(window); + } + else if (memcmp(&window->Viewport->Size, &window->Size, sizeof(window->Size)) != 0) + { + viewport_rect_changed = true; + window->Viewport->Size = window->Size; + } + window->Viewport->UpdateWorkRect(); + + // The viewport may have changed monitor since the global update in UpdateViewportsNewFrame() + // Either a SetNextWindowPos() call in the current frame or a SetWindowPos() call in the previous frame may have this effect. + if (viewport_rect_changed) + UpdateViewportPlatformMonitor(window->Viewport); + + // Update common viewport flags + const ImGuiViewportFlags viewport_flags_to_clear = ImGuiViewportFlags_TopMost | ImGuiViewportFlags_NoTaskBarIcon | ImGuiViewportFlags_NoDecoration | ImGuiViewportFlags_NoRendererClear; + ImGuiViewportFlags viewport_flags = window->Viewport->Flags & ~viewport_flags_to_clear; + ImGuiWindowFlags window_flags = window->Flags; + const bool is_modal = (window_flags & ImGuiWindowFlags_Modal) != 0; + const bool is_short_lived_floating_window = (window_flags & (ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_Popup)) != 0; + if (window_flags & ImGuiWindowFlags_Tooltip) + viewport_flags |= ImGuiViewportFlags_TopMost; + if ((g.IO.ConfigViewportsNoTaskBarIcon || is_short_lived_floating_window) && !is_modal) + viewport_flags |= ImGuiViewportFlags_NoTaskBarIcon; + if (g.IO.ConfigViewportsNoDecoration || is_short_lived_floating_window) + viewport_flags |= ImGuiViewportFlags_NoDecoration; + + // Not correct to set modal as topmost because: + // - Because other popups can be stacked above a modal (e.g. combo box in a modal) + // - ImGuiViewportFlags_TopMost is currently handled different in backends: in Win32 it is "appear top most" whereas in GLFW and SDL it is "stay topmost" + //if (flags & ImGuiWindowFlags_Modal) + // viewport_flags |= ImGuiViewportFlags_TopMost; + + // For popups and menus that may be protruding out of their parent viewport, we enable _NoFocusOnClick so that clicking on them + // won't steal the OS focus away from their parent window (which may be reflected in OS the title bar decoration). + // Setting _NoFocusOnClick would technically prevent us from bringing back to front in case they are being covered by an OS window from a different app, + // but it shouldn't be much of a problem considering those are already popups that are closed when clicking elsewhere. + if (is_short_lived_floating_window && !is_modal) + viewport_flags |= ImGuiViewportFlags_NoFocusOnAppearing | ImGuiViewportFlags_NoFocusOnClick; + + // We can overwrite viewport flags using ImGuiWindowClass (advanced users) + if (window->WindowClass.ViewportFlagsOverrideSet) + viewport_flags |= window->WindowClass.ViewportFlagsOverrideSet; + if (window->WindowClass.ViewportFlagsOverrideClear) + viewport_flags &= ~window->WindowClass.ViewportFlagsOverrideClear; + + // We can also tell the backend that clearing the platform window won't be necessary, + // as our window background is filling the viewport and we have disabled BgAlpha. + // FIXME: Work on support for per-viewport transparency (#2766) + if (!(window_flags & ImGuiWindowFlags_NoBackground)) + viewport_flags |= ImGuiViewportFlags_NoRendererClear; + + window->Viewport->Flags = viewport_flags; + + // Update parent viewport ID + // (the !IsFallbackWindow test mimic the one done in WindowSelectViewport()) + if (window->WindowClass.ParentViewportId) + window->Viewport->ParentViewportId = window->WindowClass.ParentViewportId; + else if ((window_flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) && parent_window_in_stack && (!parent_window_in_stack->IsFallbackWindow || parent_window_in_stack->WasActive)) + window->Viewport->ParentViewportId = parent_window_in_stack->Viewport->ID; + else + window->Viewport->ParentViewportId = g.IO.ConfigViewportsNoDefaultParent ? 0 : IMGUI_VIEWPORT_DEFAULT_ID; +} + // Called by user at the end of the main loop, after EndFrame() // This will handle the creation/update of all OS windows via function defined in the ImGuiPlatformIO api. void ImGui::UpdatePlatformWindows() @@ -12546,7 +12684,8 @@ static ImGuiDockNode* ImGui::DockContextFindNodeByID(ImGuiContext* ctx, ImGuiID ImGuiID ImGui::DockContextGenNodeID(ImGuiContext* ctx) { // Generate an ID for new node (the exact ID value doesn't matter as long as it is not already used) - // FIXME-OPT FIXME-DOCK: This is suboptimal, even if the node count is small enough not to be a worry. We should poke in ctx->Nodes to find a suitable ID faster. + // FIXME-OPT FIXME-DOCK: This is suboptimal, even if the node count is small enough not to be a worry.0 + // We should poke in ctx->Nodes to find a suitable ID faster. Even more so trivial that ctx->Nodes lookup is already sorted. ImGuiID id = 0x0001; while (DockContextFindNodeByID(ctx, id) != NULL) id++; @@ -12699,7 +12838,7 @@ static void ImGui::DockContextBuildNodesFromSettings(ImGuiContext* ctx, ImGuiDoc node->ParentNode->ChildNodes[1] = node; node->SelectedTabId = settings->SelectedTabId; node->SplitAxis = (ImGuiAxis)settings->SplitAxis; - node->LocalFlags |= (settings->Flags & ImGuiDockNodeFlags_SavedFlagsMask_); + node->SetLocalFlags(settings->Flags & ImGuiDockNodeFlags_SavedFlagsMask_); // Bind host window immediately if it already exist (in case of a rebuild) // This is useful as the RootWindowForTitleBarHighlight links necessary to highlight the currently focused node requires node->HostWindow to be set. @@ -12841,7 +12980,7 @@ void ImGui::DockContextProcessDock(ImGuiContext* ctx, ImGuiDockRequest* req) new_node->HostWindow = node->HostWindow; node = new_node; } - node->LocalFlags &= ~ImGuiDockNodeFlags_HiddenTabBar; + node->SetLocalFlags(node->LocalFlags & ~ImGuiDockNodeFlags_HiddenTabBar); if (node != payload_node) { @@ -12879,8 +13018,8 @@ void ImGui::DockContextProcessDock(ImGuiContext* ctx, ImGuiDockRequest* req) IM_ASSERT(last_focused_node != NULL); ImGuiDockNode* last_focused_root_node = DockNodeGetRootNode(last_focused_node); IM_ASSERT(last_focused_root_node == DockNodeGetRootNode(payload_node)); - last_focused_node->LocalFlags |= ImGuiDockNodeFlags_CentralNode; - node->LocalFlags &= ~ImGuiDockNodeFlags_CentralNode; + last_focused_node->SetLocalFlags(last_focused_node->LocalFlags |= ImGuiDockNodeFlags_CentralNode); + node->SetLocalFlags(node->LocalFlags & ~ImGuiDockNodeFlags_CentralNode); last_focused_root_node->CentralNode = last_focused_node; } @@ -12950,7 +13089,7 @@ void ImGui::DockContextProcessUndockWindow(ImGuiContext* ctx, ImGuiWindow* windo window->DockId = 0; window->Collapsed = false; window->DockIsActive = false; - window->DockTabIsVisible = false; + window->DockNodeIsVisible = window->DockTabIsVisible = false; window->Size = window->SizeFull = FixLargeWindowsWhenUndocking(window->SizeFull, window->Viewport); MarkIniSettingsDirty(); @@ -13041,7 +13180,7 @@ bool ImGui::DockContextCalcDropPosForDocking(ImGuiWindow* target, ImGuiDockNode* ImGuiDockNode::ImGuiDockNode(ImGuiID id) { ID = id; - SharedFlags = LocalFlags = ImGuiDockNodeFlags_None; + SharedFlags = LocalFlags = LocalFlagsInWindows = MergedFlags = ImGuiDockNodeFlags_None; ParentNode = ChildNodes[0] = ChildNodes[1] = NULL; TabBar = NULL; SplitAxis = ImGuiAxis_None; @@ -13345,6 +13484,7 @@ static void ImGui::DockNodeUpdateVisibleFlagAndInactiveChilds(ImGuiDockNode* nod // Remove inactive windows // Merge node flags overrides stored in windows + node->LocalFlagsInWindows = ImGuiDockNodeFlags_None; for (int window_n = 0; window_n < node->Windows.Size; window_n++) { ImGuiWindow* window = node->Windows[window_n]; @@ -13371,13 +13511,14 @@ static void ImGui::DockNodeUpdateVisibleFlagAndInactiveChilds(ImGuiDockNode* nod else { // FIXME-DOCKING: Missing policies for conflict resolution, hence the "Experimental" tag on this. - node->LocalFlags &= ~window->WindowClass.DockNodeFlagsOverrideClear; - node->LocalFlags |= window->WindowClass.DockNodeFlagsOverrideSet; + //node->LocalFlagsInWindow &= ~window->WindowClass.DockNodeFlagsOverrideClear; + node->LocalFlagsInWindows |= window->WindowClass.DockNodeFlagsOverrideSet; } } + node->UpdateMergedFlags(); // Auto-hide tab bar option - ImGuiDockNodeFlags node_flags = node->GetMergedFlags(); + ImGuiDockNodeFlags node_flags = node->MergedFlags; if (node->WantHiddenTabBarUpdate && node->Windows.Size == 1 && (node_flags & ImGuiDockNodeFlags_AutoHideTabBar) && !node->IsHiddenTabBar()) node->WantHiddenTabBarToggle = true; node->WantHiddenTabBarUpdate = false; @@ -13388,9 +13529,9 @@ static void ImGui::DockNodeUpdateVisibleFlagAndInactiveChilds(ImGuiDockNode* nod // Apply toggles at a single point of the frame (here!) if (node->Windows.Size > 1) - node->LocalFlags &= ~ImGuiDockNodeFlags_HiddenTabBar; + node->SetLocalFlags(node->LocalFlags & ~ImGuiDockNodeFlags_HiddenTabBar); else if (node->WantHiddenTabBarToggle) - node->LocalFlags ^= ImGuiDockNodeFlags_HiddenTabBar; + node->SetLocalFlags(node->LocalFlags ^ ImGuiDockNodeFlags_HiddenTabBar); node->WantHiddenTabBarToggle = false; DockNodeUpdateVisibleFlag(node); @@ -13446,6 +13587,20 @@ static void ImGui::DockNodeUpdateForRootNode(ImGuiDockNode* node) } } +static void DockNodeSetupHostWindow(ImGuiDockNode* node, ImGuiWindow* host_window) +{ + // Remove ourselves from any previous different host window + // This can happen if a user mistakenly does (see #4295 for details): + // - N+0: DockBuilderAddNode(id, 0) // missing ImGuiDockNodeFlags_DockSpace + // - N+1: NewFrame() // will create floating host window for that node + // - N+1: DockSpace(id) // requalify node as dockspace, moving host window + if (node->HostWindow && node->HostWindow != host_window && node->HostWindow->DockNodeAsHost == node) + node->HostWindow->DockNodeAsHost = NULL; + + host_window->DockNodeAsHost = node; + node->HostWindow = host_window; +} + static void ImGui::DockNodeUpdate(ImGuiDockNode* node) { ImGuiContext& g = *GImGui; @@ -13528,7 +13683,7 @@ static void ImGui::DockNodeUpdate(ImGuiDockNode* node) } } - const ImGuiDockNodeFlags node_flags = node->GetMergedFlags(); + const ImGuiDockNodeFlags node_flags = node->MergedFlags; // Decide if the node will have a close button and a window menu button node->HasWindowMenuButton = (node->Windows.Size > 0) && (node_flags & ImGuiDockNodeFlags_NoWindowMenuButton) == 0; @@ -13594,8 +13749,8 @@ static void ImGui::DockNodeUpdate(ImGuiDockNode* node) PopStyleVar(); beginned_into_host_window = true; - node->HostWindow = host_window = g.CurrentWindow; - host_window->DockNodeAsHost = node; + host_window = g.CurrentWindow; + DockNodeSetupHostWindow(node, host_window); host_window->DC.CursorPos = host_window->Pos; node->Pos = host_window->Pos; node->Size = host_window->Size; @@ -13780,7 +13935,7 @@ bool ImGui::DockNodeBeginAmendTabBar(ImGuiDockNode* node) { if (node->TabBar == NULL || node->HostWindow == NULL) return false; - if (node->SharedFlags & ImGuiDockNodeFlags_KeepAliveOnly) + if (node->MergedFlags & ImGuiDockNodeFlags_KeepAliveOnly) return false; Begin(node->HostWindow->Name); PushOverrideID(node->ID); @@ -13858,7 +14013,7 @@ static void ImGui::DockNodeUpdateTabBar(ImGuiDockNode* node, ImGuiWindow* host_w ImGuiID focus_tab_id = 0; node->IsFocused = is_focused; - const ImGuiDockNodeFlags node_flags = node->GetMergedFlags(); + const ImGuiDockNodeFlags node_flags = node->MergedFlags; const bool has_window_menu_button = (node_flags & ImGuiDockNodeFlags_NoWindowMenuButton) == 0 && (style.WindowMenuButtonPosition != ImGuiDir_None); // In a dock node, the Collapse Button turns into the Window Menu button. @@ -13968,8 +14123,8 @@ static void ImGui::DockNodeUpdateTabBar(ImGuiDockNode* node, ImGuiWindow* host_w node->VisibleWindow = window; // Store last item data so it can be queried with IsItemXXX functions after the user Begin() call - window->DockTabItemStatusFlags = host_window->DC.LastItemStatusFlags; - window->DockTabItemRect = host_window->DC.LastItemRect; + window->DockTabItemStatusFlags = g.LastItemData.StatusFlags; + window->DockTabItemRect = g.LastItemData.Rect; // Update navigation ID on menu layer if (g.NavWindow && g.NavWindow->RootWindow == window && (window->DC.NavLayersActiveMask & (1 << ImGuiNavLayer_Menu)) == 0) @@ -13999,11 +14154,11 @@ static void ImGui::DockNodeUpdateTabBar(ImGuiDockNode* node, ImGuiWindow* host_w PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_Text] * ImVec4(1.0f,1.0f,1.0f,0.4f)); } if (CloseButton(host_window->GetID("#CLOSE"), close_button_pos)) - if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_bar->VisibleTabId)) - { - node->WantCloseTabId = tab->ID; - TabBarCloseTab(tab_bar, tab); - } + { + node->WantCloseAll = true; + for (int n = 0; n < tab_bar->Tabs.Size; n++) + TabBarCloseTab(tab_bar, &tab_bar->Tabs[n]); + } //if (IsItemActive()) // focus_tab_id = tab_bar->SelectedTabId; if (!close_button_is_enabled) @@ -14024,7 +14179,7 @@ static void ImGui::DockNodeUpdateTabBar(ImGuiDockNode* node, ImGuiWindow* host_w { // ImGuiButtonFlags_AllowItemOverlap + SetItemAllowOverlap() required for appending into dock node tab bar, // otherwise dragging window will steal HoveredId and amended tabs cannot get them. - host_window->DC.LastItemId = title_bar_id; + g.LastItemData.ID = title_bar_id; SetItemAllowOverlap(); } if (held) @@ -14252,8 +14407,8 @@ static void ImGui::DockNodePreviewDockSetup(ImGuiWindow* host_window, ImGuiDockN IM_ASSERT(ref_node_for_rect->IsVisible); // Filter, figure out where we are allowed to dock - ImGuiDockNodeFlags src_node_flags = root_payload_as_host ? root_payload_as_host->GetMergedFlags() : root_payload->WindowClass.DockNodeFlagsOverrideSet; - ImGuiDockNodeFlags dst_node_flags = host_node ? host_node->GetMergedFlags() : host_window->WindowClass.DockNodeFlagsOverrideSet; + ImGuiDockNodeFlags src_node_flags = root_payload_as_host ? root_payload_as_host->MergedFlags : root_payload->WindowClass.DockNodeFlagsOverrideSet; + ImGuiDockNodeFlags dst_node_flags = host_node ? host_node->MergedFlags : host_window->WindowClass.DockNodeFlagsOverrideSet; data->IsCenterAvailable = true; if (is_outer_docking) data->IsCenterAvailable = false; @@ -14263,7 +14418,11 @@ static void ImGui::DockNodePreviewDockSetup(ImGuiWindow* host_window, ImGuiDockN data->IsCenterAvailable = false; else if ((!host_node || !host_node->IsEmpty()) && root_payload_as_host && root_payload_as_host->IsSplitNode() && (root_payload_as_host->OnlyNodeWithWindows == NULL)) // Is _visibly_ split? data->IsCenterAvailable = false; - else if ((dst_node_flags & ImGuiDockNodeFlags_NoDockingOverMe) || (src_node_flags & ImGuiDockNodeFlags_NoDockingOverOther)) + else if (dst_node_flags & ImGuiDockNodeFlags_NoDockingOverMe) + data->IsCenterAvailable = false; + else if ((src_node_flags & ImGuiDockNodeFlags_NoDockingOverOther) && (!host_node || !host_node->IsEmpty())) + data->IsCenterAvailable = false; + else if ((src_node_flags & ImGuiDockNodeFlags_NoDockingOverEmpty) && host_node && host_node->IsEmpty()) data->IsCenterAvailable = false; data->IsSidesAvailable = true; @@ -14433,7 +14592,7 @@ static void ImGui::DockNodePreviewDockRender(ImGuiWindow* host_window, ImGuiDock } // Stop after ImGuiDir_None - if ((host_node && (host_node->GetMergedFlags() & ImGuiDockNodeFlags_NoSplit)) || g.IO.ConfigDockingNoSplit) + if ((host_node && (host_node->MergedFlags & ImGuiDockNodeFlags_NoSplit)) || g.IO.ConfigDockingNoSplit) return; } } @@ -14486,6 +14645,9 @@ void ImGui::DockNodeTreeSplit(ImGuiContext* ctx, ImGuiDockNode* parent_node, ImG child_1->SharedFlags = parent_node->SharedFlags & ImGuiDockNodeFlags_SharedFlagsInheritMask_; child_inheritor->LocalFlags = parent_node->LocalFlags & ImGuiDockNodeFlags_LocalFlagsTransferMask_; parent_node->LocalFlags &= ~ImGuiDockNodeFlags_LocalFlagsTransferMask_; + child_0->UpdateMergedFlags(); + child_1->UpdateMergedFlags(); + parent_node->UpdateMergedFlags(); if (child_inheritor->IsCentralNode()) DockNodeGetRootNode(parent_node)->CentralNode = child_inheritor; } @@ -14525,6 +14687,8 @@ void ImGui::DockNodeTreeMerge(ImGuiContext* ctx, ImGuiDockNode* parent_node, ImG parent_node->LocalFlags &= ~ImGuiDockNodeFlags_LocalFlagsTransferMask_; // Preserve Dockspace flag parent_node->LocalFlags |= (child_0 ? child_0->LocalFlags : 0) & ImGuiDockNodeFlags_LocalFlagsTransferMask_; parent_node->LocalFlags |= (child_1 ? child_1->LocalFlags : 0) & ImGuiDockNodeFlags_LocalFlagsTransferMask_; + parent_node->LocalFlagsInWindows = (child_0 ? child_0->LocalFlagsInWindows : 0) | (child_1 ? child_1->LocalFlagsInWindows : 0); // FIXME: Would be more consistent to update from actual windows + parent_node->UpdateMergedFlags(); if (child_0) { @@ -14656,7 +14820,7 @@ void ImGui::DockNodeTreeUpdateSplitter(ImGuiDockNode* node) bb.Max[axis ^ 1] += child_1->Size[axis ^ 1]; //if (g.IO.KeyCtrl) GetForegroundDrawList(g.CurrentWindow->Viewport)->AddRect(bb.Min, bb.Max, IM_COL32(255,0,255,255)); - const ImGuiDockNodeFlags merged_flags = child_0->GetMergedFlags() | child_1->GetMergedFlags(); + const ImGuiDockNodeFlags merged_flags = child_0->MergedFlags | child_1->MergedFlags; // Merged flags for BOTH childs const ImGuiDockNodeFlags no_resize_axis_flag = (axis == ImGuiAxis_X) ? ImGuiDockNodeFlags_NoResizeX : ImGuiDockNodeFlags_NoResizeY; if ((merged_flags & ImGuiDockNodeFlags_NoResize) || (merged_flags & no_resize_axis_flag)) { @@ -14850,7 +15014,7 @@ ImGuiID ImGui::DockSpace(ImGuiID id, const ImVec2& size_arg, ImGuiDockNodeFlags { IMGUI_DEBUG_LOG_DOCKING("DockSpace: dockspace node 0x%08X created\n", id); node = DockContextAddNode(ctx, id); - node->LocalFlags |= ImGuiDockNodeFlags_CentralNode; + node->SetLocalFlags(ImGuiDockNodeFlags_CentralNode); } if (window_class && window_class->ClassId != node->WindowClass.ClassId) IMGUI_DEBUG_LOG_DOCKING("DockSpace: dockspace node 0x%08X: setup WindowClass 0x%08X -> 0x%08X\n", id, node->WindowClass.ClassId, window_class->ClassId); @@ -14862,10 +15026,10 @@ ImGuiID ImGui::DockSpace(ImGuiID id, const ImVec2& size_arg, ImGuiDockNodeFlags if (node->LastFrameActive == g.FrameCount && !(flags & ImGuiDockNodeFlags_KeepAliveOnly)) { IM_ASSERT(node->IsDockSpace() == false && "Cannot call DockSpace() twice a frame with the same ID"); - node->LocalFlags |= ImGuiDockNodeFlags_DockSpace; + node->SetLocalFlags(node->LocalFlags | ImGuiDockNodeFlags_DockSpace); return id; } - node->LocalFlags |= ImGuiDockNodeFlags_DockSpace; + node->SetLocalFlags(node->LocalFlags | ImGuiDockNodeFlags_DockSpace); // Keep alive mode, this is allow windows docked into this node so stay docked even if they are not visible if (flags & ImGuiDockNodeFlags_KeepAliveOnly) @@ -14903,9 +15067,8 @@ ImGuiID ImGui::DockSpace(ImGuiID id, const ImVec2& size_arg, ImGuiDockNodeFlags PopStyleVar(); ImGuiWindow* host_window = g.CurrentWindow; - host_window->DockNodeAsHost = node; + DockNodeSetupHostWindow(node, host_window); host_window->ChildId = window->GetID(title); - node->HostWindow = host_window; node->OnlyNodeWithWindows = NULL; IM_ASSERT(node->IsRootNode()); @@ -14917,7 +15080,7 @@ ImGuiID ImGui::DockSpace(ImGuiID id, const ImVec2& size_arg, ImGuiDockNodeFlags // The specific sub-property of _CentralNode we are interested in recovering here is the "Don't delete when empty" property, // as it doesn't make sense for an empty dockspace to not have this property. if (node->IsLeafNode() && !node->IsCentralNode()) - node->LocalFlags |= ImGuiDockNodeFlags_CentralNode; + node->SetLocalFlags(node->LocalFlags | ImGuiDockNodeFlags_CentralNode); // Update the node DockNodeUpdate(node); @@ -15043,11 +15206,11 @@ void ImGui::DockBuilderSetNodeSize(ImGuiID node_id, ImVec2 size) ImGuiID ImGui::DockBuilderAddNode(ImGuiID id, ImGuiDockNodeFlags flags) { ImGuiContext* ctx = GImGui; - ImGuiDockNode* node = NULL; if (id != 0) DockBuilderRemoveNode(id); + ImGuiDockNode* node = NULL; if (flags & ImGuiDockNodeFlags_DockSpace) { DockSpace(id, ImVec2(0, 0), (flags & ~ImGuiDockNodeFlags_DockSpace) | ImGuiDockNodeFlags_KeepAliveOnly); @@ -15056,7 +15219,7 @@ ImGuiID ImGui::DockBuilderAddNode(ImGuiID id, ImGuiDockNodeFlags flags) else { node = DockContextAddNode(ctx, id); - node->LocalFlags = flags; + node->SetLocalFlags(flags); } node->LastFrameAlive = ctx->FrameCount; // Set this otherwise BeginDocked will undock during the same frame. return node->ID; @@ -15070,8 +15233,12 @@ void ImGui::DockBuilderRemoveNode(ImGuiID node_id) return; DockBuilderRemoveNodeDockedWindows(node_id, true); DockBuilderRemoveNodeChildNodes(node_id); + // Node may have moved or deleted if e.g. any merge happened + node = DockContextFindNodeByID(ctx, node_id); + if (node == NULL) + return; if (node->IsCentralNode() && node->ParentNode) - node->ParentNode->LocalFlags |= ImGuiDockNodeFlags_CentralNode; + node->ParentNode->SetLocalFlags(node->ParentNode->LocalFlags | ImGuiDockNodeFlags_CentralNode); DockContextRemoveNode(ctx, node, true); } @@ -15141,8 +15308,8 @@ void ImGui::DockBuilderRemoveNodeChildNodes(ImGuiID root_id) } else if (has_central_node) { - root_node->LocalFlags |= ImGuiDockNodeFlags_CentralNode; root_node->CentralNode = root_node; + root_node->SetLocalFlags(root_node->LocalFlags | ImGuiDockNodeFlags_CentralNode); } } @@ -15224,10 +15391,12 @@ static ImGuiDockNode* DockBuilderCopyNodeRec(ImGuiDockNode* src_node, ImGuiID ds ImGuiDockNode* dst_node = ImGui::DockContextAddNode(ctx, dst_node_id_if_known); dst_node->SharedFlags = src_node->SharedFlags; dst_node->LocalFlags = src_node->LocalFlags; + dst_node->LocalFlagsInWindows = ImGuiDockNodeFlags_None; dst_node->Pos = src_node->Pos; dst_node->Size = src_node->Size; dst_node->SizeRef = src_node->SizeRef; dst_node->SplitAxis = src_node->SplitAxis; + dst_node->UpdateMergedFlags(); out_node_remap_pairs->push_back(src_node->ID); out_node_remap_pairs->push_back(dst_node->ID); @@ -15250,11 +15419,12 @@ void ImGui::DockBuilderCopyNode(ImGuiID src_node_id, ImGuiID dst_node_id, ImVect IM_ASSERT(dst_node_id != 0); IM_ASSERT(out_node_remap_pairs != NULL); + DockBuilderRemoveNode(dst_node_id); + ImGuiDockNode* src_node = DockContextFindNodeByID(ctx, src_node_id); IM_ASSERT(src_node != NULL); out_node_remap_pairs->clear(); - DockBuilderRemoveNode(dst_node_id); DockBuilderCopyNodeRec(src_node, dst_node_id, out_node_remap_pairs); IM_ASSERT((out_node_remap_pairs->Size % 2) == 0); @@ -15441,6 +15611,9 @@ void ImGui::BeginDocked(ImGuiWindow* window, bool* p_open) ImGuiContext* ctx = GImGui; ImGuiContext& g = *ctx; + // Clear fields ahead so most early-out paths don't have to do it + window->DockIsActive = window->DockNodeIsVisible = window->DockTabIsVisible = false; + const bool auto_dock_node = GetWindowAlwaysWantOwnTabBar(window); if (auto_dock_node) { @@ -15490,14 +15663,9 @@ void ImGui::BeginDocked(ImGuiWindow* window, bool* p_open) // If the window has been orphaned, transition the docknode to an implicit node processed in DockContextNewFrameUpdateDocking() ImGuiDockNode* root_node = DockNodeGetRootNode(node); if (root_node->LastFrameAlive < g.FrameCount) - { DockContextProcessUndockWindow(ctx, window); - } else - { window->DockIsActive = true; - window->DockTabIsVisible = false; - } return; } @@ -15510,8 +15678,8 @@ void ImGui::BeginDocked(ImGuiWindow* window, bool* p_open) // FIXME-DOCK: replace ->HostWindow NULL compare with something more explicit (~was initially intended as a first frame test) if (node->HostWindow == NULL) { - window->DockIsActive = (node->State == ImGuiDockNodeState_HostWindowHiddenBecauseWindowsAreResizing); - window->DockTabIsVisible = false; + if (node->State == ImGuiDockNodeState_HostWindowHiddenBecauseWindowsAreResizing) + window->DockIsActive = true; if (node->Windows.Size > 1) DockNodeHideWindowDuringHostWindowCreation(window); return; @@ -15535,8 +15703,9 @@ void ImGui::BeginDocked(ImGuiWindow* window, bool* p_open) SetNextWindowSize(node->Size); g.NextWindowData.PosUndock = false; // Cancel implicit undocking of SetNextWindowPos() window->DockIsActive = true; + window->DockNodeIsVisible = true; window->DockTabIsVisible = false; - if (node->SharedFlags & ImGuiDockNodeFlags_KeepAliveOnly) + if (node->MergedFlags & ImGuiDockNodeFlags_KeepAliveOnly) return; // When the window is selected we mark it as visible. @@ -15569,8 +15738,9 @@ void ImGui::BeginDockableDragDropSource(ImGuiWindow* window) ImGuiContext& g = *GImGui; IM_ASSERT(g.ActiveId == window->MoveId); IM_ASSERT(g.MovingWindow == window); + IM_ASSERT(g.CurrentWindow == window); - window->DC.LastItemId = window->MoveId; + g.LastItemData.ID = window->MoveId; window = window->RootWindowDockTree; IM_ASSERT((window->Flags & ImGuiWindowFlags_NoDocking) == 0); bool is_drag_docking = ImRect(0, 0, window->SizeFull.x, GetFrameHeight()).Contains(g.ActiveIdClickOffset); // FIXME-DOCKING: Need to make this stateful and explicit @@ -16222,10 +16392,10 @@ void ImGui::ShowMetricsWindow(bool* p_open) cfg->ShowTablesRects |= Combo("##show_table_rects_type", &cfg->ShowTablesRectsType, trt_rects_names, TRT_Count, TRT_Count); if (cfg->ShowTablesRects && g.NavWindow != NULL) { - for (int table_n = 0; table_n < g.Tables.GetSize(); table_n++) + for (int table_n = 0; table_n < g.Tables.GetMapSize(); table_n++) { - ImGuiTable* table = g.Tables.GetByIndex(table_n); - if (table->LastFrameActive < g.FrameCount - 1 || (table->OuterWindow != g.NavWindow && table->InnerWindow != g.NavWindow)) + ImGuiTable* table = g.Tables.TryGetMapData(table_n); + if (table == NULL || table->LastFrameActive < g.FrameCount - 1 || (table->OuterWindow != g.NavWindow && table->InnerWindow != g.NavWindow)) continue; BulletText("Table 0x%08X (%d columns, in '%s')", table->ID, table->ColumnsCount, table->OuterWindow->Name); @@ -16342,23 +16512,24 @@ void ImGui::ShowMetricsWindow(bool* p_open) } // Details for TabBars - if (TreeNode("TabBars", "Tab Bars (%d)", g.TabBars.GetSize())) + if (TreeNode("TabBars", "Tab Bars (%d)", g.TabBars.GetAliveCount())) { - for (int n = 0; n < g.TabBars.GetSize(); n++) - { - ImGuiTabBar* tab_bar = g.TabBars.GetByIndex(n); - PushID(tab_bar); - DebugNodeTabBar(tab_bar, "TabBar"); - PopID(); - } + for (int n = 0; n < g.TabBars.GetMapSize(); n++) + if (ImGuiTabBar* tab_bar = g.TabBars.TryGetMapData(n)) + { + PushID(tab_bar); + DebugNodeTabBar(tab_bar, "TabBar"); + PopID(); + } TreePop(); } // Details for Tables - if (TreeNode("Tables", "Tables (%d)", g.Tables.GetSize())) + if (TreeNode("Tables", "Tables (%d)", g.Tables.GetAliveCount())) { - for (int n = 0; n < g.Tables.GetSize(); n++) - DebugNodeTable(g.Tables.GetByIndex(n)); + for (int n = 0; n < g.Tables.GetMapSize(); n++) + if (ImGuiTable* table = g.Tables.TryGetMapData(n)) + DebugNodeTable(table); TreePop(); } @@ -16481,7 +16652,8 @@ void ImGui::ShowMetricsWindow(bool* p_open) Indent(); Text("ActiveId: 0x%08X/0x%08X (%.2f sec), AllowOverlap: %d, Source: %s", g.ActiveId, g.ActiveIdPreviousFrame, g.ActiveIdTimer, g.ActiveIdAllowOverlap, input_source_names[g.ActiveIdSource]); Text("ActiveIdWindow: '%s'", g.ActiveIdWindow ? g.ActiveIdWindow->Name : "NULL"); - Text("HoveredId: 0x%08X/0x%08X (%.2f sec), AllowOverlap: %d", g.HoveredId, g.HoveredIdPreviousFrame, g.HoveredIdTimer, g.HoveredIdAllowOverlap); // Data is "in-flight" so depending on when the Metrics window is called we may see current frame information or not + Text("ActiveIdUsing: Wheel: %d, NavDirMask: %X, NavInputMask: %X, KeyInputMask: %llX", g.ActiveIdUsingMouseWheel, g.ActiveIdUsingNavDirMask, g.ActiveIdUsingNavInputMask, g.ActiveIdUsingKeyInputMask); + 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("DragDrop: %d, SourceId = 0x%08X, Payload \"%s\" (%d bytes)", g.DragDropActive, g.DragDropPayload.SourceId, g.DragDropPayload.DataType, g.DragDropPayload.DataSize); Unindent(); @@ -16528,10 +16700,10 @@ void ImGui::ShowMetricsWindow(bool* p_open) // Overlay: Display Tables Rectangles if (cfg->ShowTablesRects) { - for (int table_n = 0; table_n < g.Tables.GetSize(); table_n++) + for (int table_n = 0; table_n < g.Tables.GetMapSize(); table_n++) { - ImGuiTable* table = g.Tables.GetByIndex(table_n); - if (table->LastFrameActive < g.FrameCount - 1) + ImGuiTable* table = g.Tables.TryGetMapData(table_n); + if (table == NULL || table->LastFrameActive < g.FrameCount - 1) continue; ImDrawList* draw_list = GetForegroundDrawList(table->OuterWindow); if (cfg->ShowTablesRectsType >= TRT_ColumnsRect) @@ -16575,6 +16747,25 @@ void ImGui::ShowMetricsWindow(bool* p_open) End(); } +// [DEBUG] List fonts in a font atlas and display its texture +void ImGui::ShowFontAtlas(ImFontAtlas* atlas) +{ + for (int i = 0; i < atlas->Fonts.Size; i++) + { + ImFont* font = atlas->Fonts[i]; + PushID(font); + DebugNodeFont(font); + PopID(); + } + if (TreeNode("Atlas texture", "Atlas texture (%dx%d pixels)", atlas->TexWidth, atlas->TexHeight)) + { + ImVec4 tint_col = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); + ImVec4 border_col = ImVec4(1.0f, 1.0f, 1.0f, 0.5f); + Image(atlas->TexID, ImVec2((float)atlas->TexWidth, (float)atlas->TexHeight), ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), tint_col, border_col); + TreePop(); + } +} + // [DEBUG] Display contents of Columns void ImGui::DebugNodeColumns(ImGuiOldColumns* columns) { @@ -16586,6 +16777,34 @@ void ImGui::DebugNodeColumns(ImGuiOldColumns* columns) TreePop(); } +static void DebugNodeDockNodeFlags(ImGuiDockNodeFlags* p_flags, const char* label, bool enabled) +{ + using namespace ImGui; + PushID(label); + PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 0.0f)); + Text("%s:", label); + if (!enabled) + BeginDisabled(); + CheckboxFlags("NoSplit", p_flags, ImGuiDockNodeFlags_NoSplit); + CheckboxFlags("NoResize", p_flags, ImGuiDockNodeFlags_NoResize); + CheckboxFlags("NoResizeX", p_flags, ImGuiDockNodeFlags_NoResizeX); + CheckboxFlags("NoResizeY",p_flags, ImGuiDockNodeFlags_NoResizeY); + CheckboxFlags("NoTabBar", p_flags, ImGuiDockNodeFlags_NoTabBar); + CheckboxFlags("HiddenTabBar", p_flags, ImGuiDockNodeFlags_HiddenTabBar); + CheckboxFlags("NoWindowMenuButton", p_flags, ImGuiDockNodeFlags_NoWindowMenuButton); + CheckboxFlags("NoCloseButton", p_flags, ImGuiDockNodeFlags_NoCloseButton); + CheckboxFlags("NoDocking", p_flags, ImGuiDockNodeFlags_NoDocking); + CheckboxFlags("NoDockingSplitMe", p_flags, ImGuiDockNodeFlags_NoDockingSplitMe); + CheckboxFlags("NoDockingSplitOther", p_flags, ImGuiDockNodeFlags_NoDockingSplitOther); + CheckboxFlags("NoDockingOverMe", p_flags, ImGuiDockNodeFlags_NoDockingOverMe); + CheckboxFlags("NoDockingOverOther", p_flags, ImGuiDockNodeFlags_NoDockingOverOther); + CheckboxFlags("NoDockingOverEmpty", p_flags, ImGuiDockNodeFlags_NoDockingOverEmpty); + if (!enabled) + EndDisabled(); + PopStyleVar(); + PopID(); +} + // [DEBUG] Display contents of ImDockNode void ImGui::DebugNodeDockNode(ImGuiDockNode* node, const char* label) { @@ -16616,17 +16835,16 @@ void ImGui::DebugNodeDockNode(ImGuiDockNode* node, const char* label) node->IsCentralNode() ? " IsCentralNode" : "", is_alive ? " IsAlive" : "", is_active ? " IsActive" : "", node->WantLockSizeOnce ? " WantLockSizeOnce" : ""); - if (TreeNode("flags", "LocalFlags: 0x%04X SharedFlags: 0x%04X", node->LocalFlags, node->SharedFlags)) + if (TreeNode("flags", "Flags Merged: 0x%04X, Local: 0x%04X, InWindows: 0x%04X, Shared: 0x%04X", node->MergedFlags, node->LocalFlags, node->LocalFlagsInWindows, node->SharedFlags)) { - CheckboxFlags("LocalFlags: NoDocking", &node->LocalFlags, ImGuiDockNodeFlags_NoDocking); - CheckboxFlags("LocalFlags: NoSplit", &node->LocalFlags, ImGuiDockNodeFlags_NoSplit); - CheckboxFlags("LocalFlags: NoResize", &node->LocalFlags, ImGuiDockNodeFlags_NoResize); - CheckboxFlags("LocalFlags: NoResizeX", &node->LocalFlags, ImGuiDockNodeFlags_NoResizeX); - CheckboxFlags("LocalFlags: NoResizeY", &node->LocalFlags, ImGuiDockNodeFlags_NoResizeY); - CheckboxFlags("LocalFlags: NoTabBar", &node->LocalFlags, ImGuiDockNodeFlags_NoTabBar); - CheckboxFlags("LocalFlags: HiddenTabBar", &node->LocalFlags, ImGuiDockNodeFlags_HiddenTabBar); - CheckboxFlags("LocalFlags: NoWindowMenuButton", &node->LocalFlags, ImGuiDockNodeFlags_NoWindowMenuButton); - CheckboxFlags("LocalFlags: NoCloseButton", &node->LocalFlags, ImGuiDockNodeFlags_NoCloseButton); + if (BeginTable("flags", 4)) + { + TableNextColumn(); DebugNodeDockNodeFlags(&node->MergedFlags, "MergedFlags", false); + TableNextColumn(); DebugNodeDockNodeFlags(&node->LocalFlags, "LocalFlags", true); + TableNextColumn(); DebugNodeDockNodeFlags(&node->LocalFlagsInWindows, "LocalFlagsInWindows", false); + TableNextColumn(); DebugNodeDockNodeFlags(&node->SharedFlags, "SharedFlags", true); + EndTable(); + } TreePop(); } if (node->ParentNode) @@ -16740,16 +16958,17 @@ void ImGui::DebugNodeDrawList(ImGuiWindow* window, ImGuiViewportP* viewport, con void ImGui::DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList* out_draw_list, const ImDrawList* draw_list, const ImDrawCmd* draw_cmd, bool show_mesh, bool show_aabb) { IM_ASSERT(show_mesh || show_aabb); - ImDrawIdx* idx_buffer = (draw_list->IdxBuffer.Size > 0) ? draw_list->IdxBuffer.Data : NULL; - ImDrawVert* vtx_buffer = draw_list->VtxBuffer.Data + draw_cmd->VtxOffset; // Draw wire-frame version of all triangles ImRect clip_rect = draw_cmd->ClipRect; ImRect vtxs_rect(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX); ImDrawListFlags backup_flags = out_draw_list->Flags; out_draw_list->Flags &= ~ImDrawListFlags_AntiAliasedLines; // Disable AA on triangle outlines is more readable for very large and thin triangles. - for (unsigned int idx_n = draw_cmd->IdxOffset; idx_n < draw_cmd->IdxOffset + draw_cmd->ElemCount; ) + for (unsigned int idx_n = draw_cmd->IdxOffset, idx_end = draw_cmd->IdxOffset + draw_cmd->ElemCount; idx_n < idx_end; ) { + ImDrawIdx* idx_buffer = (draw_list->IdxBuffer.Size > 0) ? draw_list->IdxBuffer.Data : NULL; // We don't hold on those pointers past iterations as ->AddPolyline() may invalidate them if out_draw_list==draw_list + ImDrawVert* vtx_buffer = draw_list->VtxBuffer.Data + draw_cmd->VtxOffset; + ImVec2 triangle[3]; for (int n = 0; n < 3; n++, idx_n++) vtxs_rect.Add((triangle[n] = vtx_buffer[idx_buffer ? idx_buffer[idx_n] : idx_n].pos)); @@ -16765,6 +16984,102 @@ void ImGui::DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList* out_draw_list, co out_draw_list->Flags = backup_flags; } +// [DEBUG] Display details for a single font, called by ShowStyleEditor(). +void ImGui::DebugNodeFont(ImFont* font) +{ + bool opened = TreeNode(font, "Font: \"%s\"\n%.2f px, %d glyphs, %d file(s)", + font->ConfigData ? font->ConfigData[0].Name : "", font->FontSize, font->Glyphs.Size, font->ConfigDataCount); + SameLine(); + if (SmallButton("Set as default")) + GetIO().FontDefault = font; + if (!opened) + return; + + // Display preview text + PushFont(font); + Text("The quick brown fox jumps over the lazy dog"); + PopFont(); + + // Display details + SetNextItemWidth(GetFontSize() * 8); + DragFloat("Font scale", &font->Scale, 0.005f, 0.3f, 2.0f, "%.1f"); + SameLine(); MetricsHelpMarker( + "Note than the default embedded font is NOT meant to be scaled.\n\n" + "Font are currently rendered into bitmaps at a given size at the time of building the atlas. " + "You may oversample them to get some flexibility with scaling. " + "You can also render at multiple sizes and select which one to use at runtime.\n\n" + "(Glimmer of hope: the atlas system will be rewritten in the future to make scaling more flexible.)"); + Text("Ascent: %f, Descent: %f, Height: %f", font->Ascent, font->Descent, font->Ascent - font->Descent); + char c_str[5]; + Text("Fallback character: '%s' (U+%04X)", ImTextCharToUtf8(c_str, font->FallbackChar), font->FallbackChar); + Text("Ellipsis character: '%s' (U+%04X)", ImTextCharToUtf8(c_str, font->EllipsisChar), font->EllipsisChar); + const int surface_sqrt = (int)ImSqrt((float)font->MetricsTotalSurface); + Text("Texture Area: about %d px ~%dx%d px", font->MetricsTotalSurface, surface_sqrt, surface_sqrt); + for (int config_i = 0; config_i < font->ConfigDataCount; config_i++) + if (font->ConfigData) + if (const ImFontConfig* cfg = &font->ConfigData[config_i]) + BulletText("Input %d: \'%s\', Oversample: (%d,%d), PixelSnapH: %d, Offset: (%.1f,%.1f)", + config_i, cfg->Name, cfg->OversampleH, cfg->OversampleV, cfg->PixelSnapH, cfg->GlyphOffset.x, cfg->GlyphOffset.y); + + // Display all glyphs of the fonts in separate pages of 256 characters + if (TreeNode("Glyphs", "Glyphs (%d)", font->Glyphs.Size)) + { + ImDrawList* draw_list = GetWindowDrawList(); + const ImU32 glyph_col = GetColorU32(ImGuiCol_Text); + const float cell_size = font->FontSize * 1; + const float cell_spacing = GetStyle().ItemSpacing.y; + for (unsigned int base = 0; base <= IM_UNICODE_CODEPOINT_MAX; base += 256) + { + // Skip ahead if a large bunch of glyphs are not present in the font (test in chunks of 4k) + // This is only a small optimization to reduce the number of iterations when IM_UNICODE_MAX_CODEPOINT + // is large // (if ImWchar==ImWchar32 we will do at least about 272 queries here) + if (!(base & 4095) && font->IsGlyphRangeUnused(base, base + 4095)) + { + base += 4096 - 256; + continue; + } + + int count = 0; + for (unsigned int n = 0; n < 256; n++) + if (font->FindGlyphNoFallback((ImWchar)(base + n))) + count++; + if (count <= 0) + continue; + if (!TreeNode((void*)(intptr_t)base, "U+%04X..U+%04X (%d %s)", base, base + 255, count, count > 1 ? "glyphs" : "glyph")) + continue; + + // Draw a 16x16 grid of glyphs + ImVec2 base_pos = GetCursorScreenPos(); + for (unsigned int n = 0; n < 256; n++) + { + // We use ImFont::RenderChar as a shortcut because we don't have UTF-8 conversion functions + // available here and thus cannot easily generate a zero-terminated UTF-8 encoded string. + ImVec2 cell_p1(base_pos.x + (n % 16) * (cell_size + cell_spacing), base_pos.y + (n / 16) * (cell_size + cell_spacing)); + ImVec2 cell_p2(cell_p1.x + cell_size, cell_p1.y + cell_size); + const ImFontGlyph* glyph = font->FindGlyphNoFallback((ImWchar)(base + n)); + draw_list->AddRect(cell_p1, cell_p2, glyph ? IM_COL32(255, 255, 255, 100) : IM_COL32(255, 255, 255, 50)); + if (glyph) + font->RenderChar(draw_list, cell_size, cell_p1, glyph_col, (ImWchar)(base + n)); + if (glyph && IsMouseHoveringRect(cell_p1, cell_p2)) + { + BeginTooltip(); + Text("Codepoint: U+%04X", base + n); + Separator(); + Text("Visible: %d", glyph->Visible); + Text("AdvanceX: %.1f", glyph->AdvanceX); + Text("Pos: (%.2f,%.2f)->(%.2f,%.2f)", glyph->X0, glyph->Y0, glyph->X1, glyph->Y1); + Text("UV: (%.3f,%.3f)->(%.3f,%.3f)", glyph->U0, glyph->V0, glyph->U1, glyph->V1); + EndTooltip(); + } + } + Dummy(ImVec2((cell_size + cell_spacing) * 16, (cell_size + cell_spacing) * 16)); + TreePop(); + } + TreePop(); + } + TreePop(); +} + // [DEBUG] Display contents of ImGuiStorage void ImGui::DebugNodeStorage(ImGuiStorage* storage, const char* label) { @@ -16943,9 +17258,11 @@ void ImGui::DebugNodeWindowsList(ImVector<ImGuiWindow*>* windows, const char* la #else void ImGui::ShowMetricsWindow(bool*) {} +void ImGui::ShowFontAtlas(ImFontAtlas*) {} void ImGui::DebugNodeColumns(ImGuiOldColumns*) {} void ImGui::DebugNodeDrawList(ImGuiWindow*, ImGuiViewportP*, const ImDrawList*, const char*) {} void ImGui::DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList*, const ImDrawList*, const ImDrawCmd*, bool, bool) {} +void ImGui::DebugNodeFont(ImFont*) {} void ImGui::DebugNodeStorage(ImGuiStorage*, const char*) {} void ImGui::DebugNodeTabBar(ImGuiTabBar*, const char*) {} void ImGui::DebugNodeWindow(ImGuiWindow*, const char*) {} |