From b60dd7fd99dca6c30a32890b3d32562a0178322b Mon Sep 17 00:00:00 2001 From: Ronan Cailleau Date: Thu, 5 Feb 2026 16:47:52 +0100 Subject: [PATCH 1/4] Windows: Add `GetResizeBorderRect()` version with an `ImRect` parameter --- imgui.cpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index d45bc03fb689..30634ccff6e1 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -6805,19 +6805,24 @@ static const ImGuiResizeBorderDef resize_border_def[4] = { ImVec2(0, -1), ImVec2(1, 1), ImVec2(0, 1), IM_PI * 0.50f } // Down }; -static ImRect GetResizeBorderRect(ImGuiWindow* window, int border_n, float perp_padding, float thickness) +static inline ImRect GetResizeBorderRect(ImRect rect, int border_n, float perp_padding, float thickness) { - ImRect rect = window->Rect(); if (thickness == 0.0f) rect.Max -= ImVec2(1, 1); - if (border_n == ImGuiDir_Left) { return ImRect(rect.Min.x - thickness, rect.Min.y + perp_padding, rect.Min.x + thickness, rect.Max.y - perp_padding); } - if (border_n == ImGuiDir_Right) { return ImRect(rect.Max.x - thickness, rect.Min.y + perp_padding, rect.Max.x + thickness, rect.Max.y - perp_padding); } - if (border_n == ImGuiDir_Up) { return ImRect(rect.Min.x + perp_padding, rect.Min.y - thickness, rect.Max.x - perp_padding, rect.Min.y + thickness); } - if (border_n == ImGuiDir_Down) { return ImRect(rect.Min.x + perp_padding, rect.Max.y - thickness, rect.Max.x - perp_padding, rect.Max.y + thickness); } + if (border_n == ImGuiDir_Left) { return ImRect(rect.Min.x - thickness, rect.Min.y + perp_padding, rect.Min.x + thickness, rect.Max.y - perp_padding); } + if (border_n == ImGuiDir_Right) { return ImRect(rect.Max.x - thickness, rect.Min.y + perp_padding, rect.Max.x + thickness, rect.Max.y - perp_padding); } + if (border_n == ImGuiDir_Up) { return ImRect(rect.Min.x + perp_padding, rect.Min.y - thickness, rect.Max.x - perp_padding, rect.Min.y + thickness); } + if (border_n == ImGuiDir_Down) { return ImRect(rect.Min.x + perp_padding, rect.Max.y - thickness, rect.Max.x - perp_padding, rect.Max.y + thickness); } IM_ASSERT(0); return ImRect(); } +static inline ImRect GetResizeBorderRect(ImGuiWindow* window, int border_n, float perp_padding, float thickness) +{ + ImRect rect = window->Rect(); + return GetResizeBorderRect(rect, border_n, perp_padding, thickness); +} + // 0..3: corners (Lower-right, Lower-left, Unused, Unused) ImGuiID ImGui::GetWindowResizeCornerID(ImGuiWindow* window, int n) { From 4936dec4c553dc0cef8ddd90bd26a958a6dbe1dc Mon Sep 17 00:00:00 2001 From: Ronan Cailleau Date: Fri, 6 Feb 2026 11:46:03 +0100 Subject: [PATCH 2/4] Windows: Add `ImGuiChildFlags_TopLabel` to render child window with a labeled-border. - Add `ImGuiChildFlags_TopLabel` flag. - `ImGuiWindow`: add `float BorderDecoOffset` and `int LabelOffset, LabelLen` - Add `bool WindowUsesLabeledBorder()` (might be extended in the future) - Add helpers `GetWindowBorderRect()` and `RenderLabeledFrame()` - Reworked some code in window rendering to work with labeled borders (use the border rect for background, menu bar, resize borders). --- imgui.cpp | 135 ++++++++++++++++++++++++++++++++++++++++++----- imgui.h | 2 +- imgui_internal.h | 3 ++ 3 files changed, 127 insertions(+), 13 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 30634ccff6e1..946d447479ff 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -6366,7 +6366,7 @@ bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, I IM_ASSERT(id != 0); // Sanity check as it is likely that some user will accidentally pass ImGuiWindowFlags into the ImGuiChildFlags argument. - const ImGuiChildFlags ImGuiChildFlags_SupportedMask_ = ImGuiChildFlags_Borders | ImGuiChildFlags_AlwaysUseWindowPadding | ImGuiChildFlags_ResizeX | ImGuiChildFlags_ResizeY | ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_FrameStyle | ImGuiChildFlags_NavFlattened; + const ImGuiChildFlags ImGuiChildFlags_SupportedMask_ = ImGuiChildFlags_Borders | ImGuiChildFlags_AlwaysUseWindowPadding | ImGuiChildFlags_ResizeX | ImGuiChildFlags_ResizeY | ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_FrameStyle | ImGuiChildFlags_NavFlattened | ImGuiChildFlags_TopLabel; IM_UNUSED(ImGuiChildFlags_SupportedMask_); IM_ASSERT((child_flags & ~ImGuiChildFlags_SupportedMask_) == 0 && "Illegal ImGuiChildFlags value. Did you pass ImGuiWindowFlags values instead of ImGuiChildFlags?"); IM_ASSERT((window_flags & ImGuiWindowFlags_AlwaysAutoResize) == 0 && "Cannot specify ImGuiWindowFlags_AlwaysAutoResize for BeginChild(). Use ImGuiChildFlags_AlwaysAutoResize!"); @@ -6375,6 +6375,10 @@ bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, I IM_ASSERT((child_flags & (ImGuiChildFlags_ResizeX | ImGuiChildFlags_ResizeY)) == 0 && "Cannot use ImGuiChildFlags_ResizeX or ImGuiChildFlags_ResizeY with ImGuiChildFlags_AlwaysAutoResize!"); IM_ASSERT((child_flags & (ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY)) != 0 && "Must use ImGuiChildFlags_AutoResizeX or ImGuiChildFlags_AutoResizeY with ImGuiChildFlags_AlwaysAutoResize!"); } + if (child_flags & ImGuiChildFlags_TopLabel) + { + IM_ASSERT((!!name || (ImStrlen(name) > 0)) && "Cannot use ImGuiChildFlags_TopLabel without a name"); + } #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS //if (window_flags & ImGuiWindowFlags_AlwaysUseWindowPadding) { child_flags |= ImGuiChildFlags_AlwaysUseWindowPadding; } //if (window_flags & ImGuiWindowFlags_NavFlattened) { child_flags |= ImGuiChildFlags_NavFlattened; } @@ -6400,9 +6404,15 @@ bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, I PushStyleVar(ImGuiStyleVar_ChildBorderSize, g.Style.FrameBorderSize); PushStyleVar(ImGuiStyleVar_WindowPadding, g.Style.FramePadding); child_flags |= ImGuiChildFlags_Borders | ImGuiChildFlags_AlwaysUseWindowPadding; + child_flags &= ~ImGuiChildFlags_TopLabel; window_flags |= ImGuiWindowFlags_NoMove; } + if (child_flags & ImGuiChildFlags_TopLabel && !(child_flags & ImGuiChildFlags_Borders)) + { + child_flags &= ~ImGuiChildFlags_TopLabel; + } + // Forward size // Important: Begin() has special processing to switch condition to ImGuiCond_FirstUseEver for a given axis when ImGuiChildFlags_ResizeXXX is set. // (the alternative would to store conditional flags per axis, which is possible but more code) @@ -6442,11 +6452,21 @@ bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, I /*if (name && parent_window->IDStack.back() == parent_window->ID) ImFormatStringToTempBuffer(&temp_window_name, NULL, "%s/%s", parent_window->Name, name); // May omit ID if in root of ID stack else*/ + int labelOffset = 0; + int labelLen = 0; if (name) + { ImFormatStringToTempBuffer(&temp_window_name, NULL, "%s/%s_%08X", parent_window->Name, name, id); + labelLen = static_cast(FindRenderedTextEnd(name) - name); + if (labelLen) + { + labelOffset = static_cast(ImStrlen(parent_window->Name) + 1); // len of "%s/" % parent_window-Name + } + } else ImFormatStringToTempBuffer(&temp_window_name, NULL, "%s/%08X", parent_window->Name, id); + // Set style const float backup_border_size = g.Style.ChildBorderSize; if ((child_flags & ImGuiChildFlags_Borders) == 0) @@ -6466,6 +6486,10 @@ bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, I ImGuiWindow* child_window = g.CurrentWindow; child_window->ChildId = id; + // Problem: this only has an effect next frame! So for the first frame, an empty label is used + child_window->LabelOffset = labelOffset; + child_window->LabelLen = labelLen; + // Set the cursor to handle case where the user called SetNextWindowPos()+BeginChild() manually. // While this is not really documented/defined, it seems that the expected thing to do. if (child_window->BeginCount == 1) @@ -6537,6 +6561,23 @@ void ImGui::EndChild() g.LogLinePosY = -FLT_MAX; // To enforce a carriage return } +static inline bool WindowUsesLabeledBorder(const ImGuiWindow* window) +{ + bool res = false; + res |= (((window->Flags & ImGuiWindowFlags_ChildWindow) == ImGuiWindowFlags_ChildWindow) && ((window->ChildFlags & ImGuiChildFlags_TopLabel) == ImGuiChildFlags_TopLabel)); + return res; +} + +static inline ImRect GetWindowBorderRect(const ImGuiWindow* window) +{ + ImRect res = window->Rect(); + if (WindowUsesLabeledBorder(window)) + { + res.Min.y = ImMin(res.Min.y + window->BorderDecoOffset, res.Max.y); + } + return res; +} + static void SetWindowConditionAllowFlags(ImGuiWindow* window, ImGuiCond flags, bool enabled) { window->SetWindowPosAllowFlags = enabled ? (window->SetWindowPosAllowFlags | flags) : (window->SetWindowPosAllowFlags & ~flags); @@ -7060,14 +7101,58 @@ static inline void ClampWindowPos(ImGuiWindow* window, const ImRect& visibility_ window->Pos = ImClamp(window->Pos, visibility_rect.Min - size_for_clamping, visibility_rect.Max); } -static void RenderWindowOuterSingleBorder(ImGuiWindow* window, int border_n, ImU32 border_col, float border_size) +static inline void RenderLabeledFrame(ImDrawList* draw_list, const char* label, const char* label_end, ImRect const& frame_rect, ImU32 col, float thickness, float rounding, ImVec2 label_align, ImVec2 padding = ImVec2(0, 0), ImVec2 spacing = ImVec2(1, 1), float extra_w = 0) +{ + if (!label_end) label_end = ImGui::FindRenderedTextEnd(label); + ImVec2 label_size = ImGui::CalcTextSize(label, label_end, true); + float padding2 = ImMax(padding.x, rounding); + float frame_width = frame_rect.GetWidth(); + float label_avail_w = ImMax(0.0f, frame_width - padding2 * 2.0f); + const ImVec2 label_pos = frame_rect.GetTL() + ImVec2(padding2 + ImMax(0.0f, label_avail_w - label_size.x - extra_w) * label_align.x, padding.y); + ImRect rect = frame_rect; + rect.Min.y += padding.y + ImFloor(label_align.y * label_size.y); + + // Draw frame rect, with a space for the label + if((col & IM_COL32_A_MASK) != 0) + { + rect.Min += ImVec2(0.50f, 0.50f); + if (draw_list->Flags & ImDrawListFlags_AntiAliasedLines) + rect.Max -= ImVec2(0.50f, 0.50f); + else + rect.Max -= ImVec2(0.49f, 0.49f); // Better looking lower-right corner and rounded non-AA shapes. + float label_left = label_pos.x - spacing.x; + float label_right = label_pos.x + label_size.x + spacing.x + extra_w; + if (label_right < (rect.GetTR().x - rounding)) + draw_list->PathLineTo(ImVec2(label_right, rect.GetTR().y)); + if (rounding < 0.5f) + { + draw_list->PathLineTo(rect.GetTR()); + draw_list->PathLineTo(rect.GetBR()); + draw_list->PathLineTo(rect.GetBL()); + draw_list->PathLineTo(rect.GetTL()); + } + else + { + draw_list->PathArcToFast(rect.GetTR() + ImVec2(-rounding, +rounding), rounding, 9, 12); + draw_list->PathArcToFast(rect.GetBR() + ImVec2(-rounding, -rounding), rounding, 0, 3); + draw_list->PathArcToFast(rect.GetBL() + ImVec2(+rounding, -rounding), rounding, 3, 6); + draw_list->PathArcToFast(rect.GetTL() + ImVec2(+rounding, +rounding), rounding, 6, 9); + } + if (label_left > (rect.GetTL().x + rounding)) + draw_list->PathLineTo(ImVec2(label_left, rect.GetTL().y)); + draw_list->PathStroke(col, ImDrawFlags_None, thickness); + } + + ImGui::RenderTextEllipsis(draw_list, label_pos, ImVec2(frame_rect.Max.x, label_pos.y + label_size.y + ImMax(1.0f, spacing.y)), frame_rect.Max.x, label, label_end, &label_size); +} + +static void RenderWindowOuterSingleBorder(ImDrawList* draw_list, ImRect const& window_rect, int border_n, ImU32 border_col, float border_size, float rounding) { const ImGuiResizeBorderDef& def = resize_border_def[border_n]; - const float rounding = window->WindowRounding; - const ImRect border_r = GetResizeBorderRect(window, border_n, rounding, 0.0f); - window->DrawList->PathArcTo(ImLerp(border_r.Min, border_r.Max, def.SegmentN1) + ImVec2(0.5f, 0.5f) + def.InnerDir * rounding, rounding, def.OuterAngle - IM_PI * 0.25f, def.OuterAngle); - window->DrawList->PathArcTo(ImLerp(border_r.Min, border_r.Max, def.SegmentN2) + ImVec2(0.5f, 0.5f) + def.InnerDir * rounding, rounding, def.OuterAngle, def.OuterAngle + IM_PI * 0.25f); - window->DrawList->PathStroke(border_col, ImDrawFlags_None, border_size); + ImRect border_r = GetResizeBorderRect(window_rect, border_n, rounding, 0.0f); + draw_list->PathArcTo(ImLerp(border_r.Min, border_r.Max, def.SegmentN1) + ImVec2(0.5f, 0.5f) + def.InnerDir * rounding, rounding, def.OuterAngle - IM_PI * 0.25f, def.OuterAngle); + draw_list->PathArcTo(ImLerp(border_r.Min, border_r.Max, def.SegmentN2) + ImVec2(0.5f, 0.5f) + def.InnerDir * rounding, rounding, def.OuterAngle, def.OuterAngle + IM_PI * 0.25f); + draw_list->PathStroke(border_col, ImDrawFlags_None, border_size); } static void ImGui::RenderWindowOuterBorders(ImGuiWindow* window) @@ -7075,20 +7160,35 @@ static void ImGui::RenderWindowOuterBorders(ImGuiWindow* window) ImGuiContext& g = *GImGui; const float border_size = window->WindowBorderSize; const ImU32 border_col = GetColorU32(ImGuiCol_Border); + ImRect window_rect = GetWindowBorderRect(window); if (border_size > 0.0f && (window->Flags & ImGuiWindowFlags_NoBackground) == 0) - window->DrawList->AddRect(window->Pos, window->Pos + window->Size, border_col, window->WindowRounding, 0, window->WindowBorderSize); + { + if (WindowUsesLabeledBorder(window)) + { + ImVec2 label_align = g.Style.SeparatorTextAlign; + ImVec2 padding = ImVec2(g.Style.SeparatorTextPadding.x, g.Style.FramePadding.y); + ImVec2 spacing = g.Style.ItemSpacing; + const char* label = window->Name + window->LabelOffset; + const char* label_end = label + window->LabelLen; + RenderLabeledFrame(window->DrawList, label, label_end, window->Rect(), border_col, window->WindowBorderSize, window->WindowRounding, label_align, padding, spacing); + } + else + { + window->DrawList->AddRect(window_rect.Min, window_rect.Max, border_col, window->WindowRounding, 0, window->WindowBorderSize); + } + } else if (border_size > 0.0f) { if (window->ChildFlags & ImGuiChildFlags_ResizeX) // Similar code as 'resize_border_mask' computation in UpdateWindowManualResize() but we specifically only always draw explicit child resize border. - RenderWindowOuterSingleBorder(window, 1, border_col, border_size); + RenderWindowOuterSingleBorder(window->DrawList, window_rect, 1, border_col, border_size, window->WindowRounding); if (window->ChildFlags & ImGuiChildFlags_ResizeY) - RenderWindowOuterSingleBorder(window, 3, border_col, border_size); + RenderWindowOuterSingleBorder(window->DrawList, window_rect, 3, border_col, border_size, window->WindowRounding); } if (window->ResizeBorderHovered != -1 || window->ResizeBorderHeld != -1) { const int border_n = (window->ResizeBorderHeld != -1) ? window->ResizeBorderHeld : window->ResizeBorderHovered; const ImU32 border_col_resizing = GetColorU32((window->ResizeBorderHeld != -1) ? ImGuiCol_SeparatorActive : ImGuiCol_SeparatorHovered); - RenderWindowOuterSingleBorder(window, border_n, border_col_resizing, ImMax(2.0f, window->WindowBorderSize)); // Thicker than usual + RenderWindowOuterSingleBorder(window->DrawList, window_rect, border_n, border_col_resizing, ImMax(2.0f, window->WindowBorderSize), window->WindowRounding); // Thicker than usual } if (g.Style.FrameBorderSize > 0 && !(window->Flags & ImGuiWindowFlags_NoTitleBar)) { @@ -7140,7 +7240,7 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar bg_col = (bg_col & ~IM_COL32_A_MASK) | (IM_F32_TO_INT8_SAT(alpha) << IM_COL32_A_SHIFT); if (bg_col & IM_COL32_A_MASK) { - ImRect bg_rect(window->Pos + ImVec2(0, window->TitleBarHeight), window->Pos + window->Size); + ImRect bg_rect = GetWindowBorderRect(window); ImDrawFlags bg_rounding_flags = (flags & ImGuiWindowFlags_NoTitleBar) ? 0 : ImDrawFlags_RoundCornersBottom; ImDrawList* bg_draw_list = window->DrawList; bg_draw_list->AddRectFilled(bg_rect.Min, bg_rect.Max, bg_col, window_rounding, bg_rounding_flags); @@ -7159,6 +7259,7 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar { ImRect menu_bar_rect = window->MenuBarRect(); menu_bar_rect.ClipWith(window->Rect()); // Soft clipping, in particular child window don't have minimum size covering the menu bar so this is useful for them. + menu_bar_rect.Min.y -= (window->TitleBarHeight - window->BorderDecoOffset); window->DrawList->AddRectFilled(menu_bar_rect.Min, menu_bar_rect.Max, GetColorU32(ImGuiCol_MenuBarBg), (flags & ImGuiWindowFlags_NoTitleBar) ? window_rounding : 0.0f, ImDrawFlags_RoundCornersTop); if (style.FrameBorderSize > 0.0f && menu_bar_rect.Max.y < window->Pos.y + window->Size.y) window->DrawList->AddLine(menu_bar_rect.GetBL() + ImVec2(window_border_size * 0.5f, 0.0f), menu_bar_rect.GetBR() - ImVec2(window_border_size * 0.5f, 0.0f), GetColorU32(ImGuiCol_Border), style.FrameBorderSize); @@ -7641,8 +7742,18 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // Outer Decoration Sizes // (we need to clear ScrollbarSize immediately as CalcWindowAutoFitSize() needs it and can be called from other locations). const ImVec2 scrollbar_sizes_from_last_frame = window->ScrollbarSizes; + window->BorderDecoOffset = 0.0f; window->DecoOuterSizeX1 = 0.0f; window->DecoOuterSizeX2 = 0.0f; + if (WindowUsesLabeledBorder(window)) + { + const float padding = g.Style.FramePadding.y; + const float align = g.Style.SeparatorTextAlign.y; + const float f = g.FontSize; + // To Render the window background (in RenderWindowDecorations())under the top border, which is lowered by the align + window->TitleBarHeight += (padding * 2 + f); + window->BorderDecoOffset = (padding + ImFloor(f * align)); + } window->DecoOuterSizeY1 = window->TitleBarHeight + window->MenuBarHeight; window->DecoOuterSizeY2 = 0.0f; window->ScrollbarSizes = ImVec2(0.0f, 0.0f); diff --git a/imgui.h b/imgui.h index 0706da641275..789e8acabc65 100644 --- a/imgui.h +++ b/imgui.h @@ -1221,7 +1221,7 @@ enum ImGuiChildFlags_ ImGuiChildFlags_AlwaysAutoResize = 1 << 6, // Combined with AutoResizeX/AutoResizeY. Always measure size even when child is hidden, always return true, always disable clipping optimization! NOT RECOMMENDED. ImGuiChildFlags_FrameStyle = 1 << 7, // Style the child window like a framed item: use FrameBg, FrameRounding, FrameBorderSize, FramePadding instead of ChildBg, ChildRounding, ChildBorderSize, WindowPadding. ImGuiChildFlags_NavFlattened = 1 << 8, // [BETA] Share focus scope, allow keyboard/gamepad navigation to cross over parent border to this child or between sibling child windows. - + ImGuiChildFlags_TopLabel = 1 << 9, // Obsolete names #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS //ImGuiChildFlags_Border = ImGuiChildFlags_Borders, // Renamed in 1.91.1 (August 2024) for consistency. diff --git a/imgui_internal.h b/imgui_internal.h index 50691e1b925c..dcd1fd1601b3 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2651,10 +2651,13 @@ struct IMGUI_API ImGuiWindow float WindowRounding; // Window rounding at the time of Begin(). May be clamped lower to avoid rendering artifacts with title bar, menu bar etc. float WindowBorderSize; // Window border size at the time of Begin(). float TitleBarHeight, MenuBarHeight; // Note that those used to be function before 2024/05/28. If you have old code calling TitleBarHeight() you can change it to TitleBarHeight. + float BorderDecoOffset; // Vertical offset of the top border from the top of the window frame (used when rendering the window with a labled frame) float DecoOuterSizeX1, DecoOuterSizeY1; // Left/Up offsets. Sum of non-scrolling outer decorations (X1 generally == 0.0f. Y1 generally = TitleBarHeight + MenuBarHeight). Locked during Begin(). float DecoOuterSizeX2, DecoOuterSizeY2; // Right/Down offsets (X2 generally == ScrollbarSize.x, Y2 == ScrollbarSizes.y). float DecoInnerSizeX1, DecoInnerSizeY1; // Applied AFTER/OVER InnerRect. Specialized for Tables as they use specialized form of clipping and frozen rows/columns are inside InnerRect (and not part of regular decoration sizes). int NameBufLen; // Size of buffer storing Name. May be larger than strlen(Name)! + int LabelOffset; // Where the label of the child window is stored in the Name buffer + int LabelLen; // Len of the child window label ImGuiID MoveId; // == window->GetID("#MOVE") ImGuiID ChildId; // ID of corresponding item in parent window (for navigation to return from child window to parent window) ImGuiID PopupId; // ID in the popup stack when this window is used as a popup/menu (because we use generic Name/ID for recycling) From 67ef68cd86ed3282b970f03feded0ec82e7e11f4 Mon Sep 17 00:00:00 2001 From: Ronan Cailleau Date: Fri, 6 Feb 2026 11:46:27 +0100 Subject: [PATCH 3/4] Demo: Add child window labeled border demo. --- imgui_demo.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/imgui_demo.cpp b/imgui_demo.cpp index b88e7bf2a27d..4f44c74acbad 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -4356,8 +4356,10 @@ static void DemoWindowLayout() HelpMarker("Use child windows to begin into a self-contained independent scrolling/clipping regions within a host window."); static bool disable_mouse_wheel = false; static bool disable_menu = false; + static bool enable_top_label = false; ImGui::Checkbox("Disable Mouse Wheel", &disable_mouse_wheel); ImGui::Checkbox("Disable Menu", &disable_menu); + ImGui::Checkbox("Enable Top Label", &enable_top_label); // Child 1: no border, enable horizontal scrollbar { @@ -4375,12 +4377,15 @@ static void DemoWindowLayout() // Child 2: rounded border { ImGuiWindowFlags window_flags = ImGuiWindowFlags_None; + ImGuiChildFlags child_flags = ImGuiChildFlags_None; if (disable_mouse_wheel) window_flags |= ImGuiWindowFlags_NoScrollWithMouse; if (!disable_menu) window_flags |= ImGuiWindowFlags_MenuBar; + if(enable_top_label) + child_flags |= ImGuiChildFlags_TopLabel; ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 5.0f); - ImGui::BeginChild("ChildR", ImVec2(0, 260), ImGuiChildFlags_Borders, window_flags); + ImGui::BeginChild("ChildR", ImVec2(0, 260), ImGuiChildFlags_Borders | child_flags, window_flags); if (!disable_menu && ImGui::BeginMenuBar()) { if (ImGui::BeginMenu("Menu")) @@ -4461,6 +4466,7 @@ static void DemoWindowLayout() ImGui::SameLine(); HelpMarker("Style the child window like a framed item: use FrameBg, FrameRounding, FrameBorderSize, FramePadding instead of ChildBg, ChildRounding, ChildBorderSize, WindowPadding."); if (child_flags & ImGuiChildFlags_FrameStyle) override_bg_color = false; + ImGui::CheckboxFlags("ImGuiChildFlags_TopLabel", &child_flags, ImGuiChildFlags_TopLabel); ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (float)offset_x); if (override_bg_color) From de7e90c5a63e6ccd4e97f247cbd94b62fff1bfbe Mon Sep 17 00:00:00 2001 From: Ronan Cailleau Date: Fri, 6 Feb 2026 11:47:41 +0100 Subject: [PATCH 4/4] Window: Adjust some window decorations when using labeled borders to improve visuals. --- imgui.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/imgui.cpp b/imgui.cpp index 946d447479ff..223998649c2a 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -7210,6 +7210,8 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar window->SkipItems = false; window->DC.NavLayerCurrent = ImGuiNavLayer_Menu; + const bool enable_labeled_border_background_hack = true; + // Draw window + handle manual resize // As we highlight the title bar when want_focus is set, multiple reappearing windows will have their title bar highlighted on their reappearing frame. const float window_rounding = window->WindowRounding; @@ -7241,6 +7243,14 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar if (bg_col & IM_COL32_A_MASK) { ImRect bg_rect = GetWindowBorderRect(window); + if (enable_labeled_border_background_hack && WindowUsesLabeledBorder(window) && window->WindowBorderSize == 1.0f) + { + // Hack: The border of the window is centered on the edge of the rect, with the size equaly spread both sides of the edge. + // But if the border is 1px wide, the background should not overlap the border (it looks better). + // -> Reduce the backgroud rect to be inside the boders in this case. + bg_rect.Min += ImVec2(1, 1); + bg_rect.Max -= ImVec2(1, 1); + } ImDrawFlags bg_rounding_flags = (flags & ImGuiWindowFlags_NoTitleBar) ? 0 : ImDrawFlags_RoundCornersBottom; ImDrawList* bg_draw_list = window->DrawList; bg_draw_list->AddRectFilled(bg_rect.Min, bg_rect.Max, bg_col, window_rounding, bg_rounding_flags); @@ -7260,6 +7270,11 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar ImRect menu_bar_rect = window->MenuBarRect(); menu_bar_rect.ClipWith(window->Rect()); // Soft clipping, in particular child window don't have minimum size covering the menu bar so this is useful for them. menu_bar_rect.Min.y -= (window->TitleBarHeight - window->BorderDecoOffset); + if (enable_labeled_border_background_hack && WindowUsesLabeledBorder(window) && window->WindowBorderSize == 1.0f) + { + // See background Hack above + menu_bar_rect.Min.y += 1; + } window->DrawList->AddRectFilled(menu_bar_rect.Min, menu_bar_rect.Max, GetColorU32(ImGuiCol_MenuBarBg), (flags & ImGuiWindowFlags_NoTitleBar) ? window_rounding : 0.0f, ImDrawFlags_RoundCornersTop); if (style.FrameBorderSize > 0.0f && menu_bar_rect.Max.y < window->Pos.y + window->Size.y) window->DrawList->AddLine(menu_bar_rect.GetBL() + ImVec2(window_border_size * 0.5f, 0.0f), menu_bar_rect.GetBR() - ImVec2(window_border_size * 0.5f, 0.0f), GetColorU32(ImGuiCol_Border), style.FrameBorderSize);