Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 149 additions & 18 deletions imgui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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!");
Expand All @@ -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; }
Expand All @@ -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)
Expand Down Expand Up @@ -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<int>(FindRenderedTextEnd(name) - name);
if (labelLen)
{
labelOffset = static_cast<int>(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)
Expand All @@ -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)
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -6805,19 +6846,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)
{
Expand Down Expand Up @@ -7055,35 +7101,94 @@ 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)
{
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))
{
Expand All @@ -7105,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;
Expand Down Expand Up @@ -7135,7 +7242,15 @@ 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);
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);
Expand All @@ -7154,6 +7269,12 @@ 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);
Expand Down Expand Up @@ -7636,8 +7757,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);
Expand Down
2 changes: 1 addition & 1 deletion imgui.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading