Skip to content

Custom UI styling: hamburger menu, tab spacing, centered labels#1

Open
noahbowers wants to merge 1 commit intoStudioCherno:hazelfrom
noahbowers:docking-custom
Open

Custom UI styling: hamburger menu, tab spacing, centered labels#1
noahbowers wants to merge 1 commit intoStudioCherno:hazelfrom
noahbowers:docking-custom

Conversation

@noahbowers
Copy link

@noahbowers noahbowers commented Mar 9, 2026

  • Replace dock menu arrow with hamburger icon
  • Set tab spacing to 0, center tab labels
  • Taller tab bar with 8px padding for docked windows
  • Simplified close/collapse button hover (no background rect)
  • Tab border: simple left-side vertical line
  • Opaque dock window menu popup

(Click "Preview" to turn any http URL into a clickable link)

  1. PLEASE CAREFULLY READ: Contributing Guidelines

  2. Make sure you're using a special branch just for this pull request. (In git, 1 PR = 1 branch. If you update the branch the PR will be updated.)

  3. Consider running the imgui_test_suite or adding new tests to test for expected behaviors.

  4. Clear this template before submitting your PR.

Summary by CodeRabbit

Release Notes

  • Improvements
    • Enhanced window docking system with improved stability and flexibility for positioning and layout operations.
    • Visual updates: dock menu icon redesigned to hamburger style for clearer visual indication.
    • Refined close button appearance and improved tab label centering with optimized tab spacing.

- Replace dock menu arrow with hamburger icon
- Set tab spacing to 0, center tab labels
- Taller tab bar with 8px padding for docked windows
- Simplified close/collapse button hover (no background rect)
- Tab border: simple left-side vertical line
- Opaque dock window menu popup

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Mar 9, 2026

Walkthrough

The changes refactor ImGui's docking system with improved window title bar handling, split node geometry calculations, and dock node hierarchy management. Additionally, the dock menu icon is redesigned from an arrow to a hamburger menu, and tab bar layout receives adjustments including simplified close button rendering and centered label positioning.

Changes

Cohort / File(s) Summary
Docking System Core Refactoring
imgui.cpp
Major refinements to dock node lifecycle management, including enhanced split rectangle calculations, improved node hierarchy during undocking/merging, refined preview rendering logic with explicit center vs. outer dock modes, and better hit-testing for docking interactions via new parameter calculations and assertions for state validity.
Dock Menu Visual Update
imgui_draw.cpp
Replaced arrow-based dock menu indicator with hamburger menu icon, computing centered hub and drawing three horizontal lines at calculated offsets instead of filled rectangle plus downward arrow.
Tab Bar and Close Button UI Updates
imgui_widgets.cpp
Introduced tab spacing constant (0.0f) replacing ItemInnerSpacing usage, added tab padding (8.0f), simplified close button visual feedback by removing background rect when hovered and adjusting cross color/extent, replaced complex border path drawing with single line, and improved text centering within tabs.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 Through docking nodes and splits we hop,
With hamburger icons serving up top,
Tabs align with precision and grace,
Geometry refined throughout the space! 🎨
The warren's preview renders so bright,
Each node finds its destined height! ✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description includes the template and lists specific UI styling changes, though it does not remove the template as instructed. Remove the template instructions (items 1-4) before submitting the PR, keeping only the custom bullet-point summary.
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the three main visual styling changes: hamburger menu icon, tab spacing adjustment, and centered tab labels.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ast-grep (0.41.0)
imgui_draw.cpp

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@imgui_widgets.cpp`:
- Around line 10571-10576: TabItemCalcSize() unconditionally adds tab_padding_y
= 8.0f making each tab FontSize+16 tall which regresses non-docking tab bars;
update the logic so the extra vertical padding is only applied for dock-node tab
bars (or alternatively make BeginTabBar() use the same padding). Modify
TabItemCalcSize() to check the tab bar type/state (the same condition
BeginTabBar() uses for docked bars) before adding tab_padding_y, or propagate
g.Style.FramePadding.y * 2 into the non-docking layout path so sizes match
BeginTabBar(); adjust the size computation around the tab_padding_y variable and
the calls/sites that rely on TabItemCalcSize() to ensure non-docked bars keep
the original FramePadding-based height.
- Around line 9590-9592: The zero tab_spacing path currently leaves leftover
inner spacing in layout and hit-testing: when tab_spacing == 0 you must also
remove g.Style.ItemInnerSpacing.x from sections[].Width calculation and from any
reorder threshold/expand computations so widths and hit targets match rendered
separators; update the code that computes sections[0].Width/sections[1].Width
(where g.Style.ItemInnerSpacing.x is added) and the reorder threshold math that
uses the old spacing (the code that expands reorder thresholds and uses
tab->Offset) to subtract or use tab_spacing instead of always adding
ItemInnerSpacing.x, and apply the same fix to the other occurrences mentioned
(the other sections[*].Spacing/Width and reorder threshold calculations).
- Around line 10600-10603: The left border color is hardcoded as
IM_COL32(0,0,0,255) which ignores theme colors and global alpha; replace that
literal with the style-derived color (e.g. use
ImGui::GetColorU32(ImGuiCol_Border) or read g.Style.Colors[ImGuiCol_Border] and
convert with ImGui::GetColorU32 to honor theme and global alpha) when calling
draw_list->AddLine in the branch that checks g.Style.TabBorderSize.

In `@imgui.cpp`:
- Around line 18453-18465: The popup for "#WindowMenu" is anchored using
GetFrameHeight(), which is wrong now that dock header height is g.FontSize +
tab_padding_y * 2; update the positioning call that uses GetFrameHeight() (the
SetNextWindowPos call that computes ImVec2(node->Pos.x, node->Pos.y +
GetFrameHeight()) and the alternate branch using node->Pos.y + GetFrameHeight())
to use the same header-height calculation as the dock tabs (compute
header_height = g.FontSize + tab_padding_y * 2 or read the header height from
the host window if available) so the popup sits immediately below the enlarged
dock header before calling BeginPopup("#WindowMenu") and
g.DockNodeWindowMenuHandler.
- Around line 7739-7740: Summary: Hard-coded dock padding (8.0f) causes
DPI/style scaling and popup alignment issues. Fix: replace the magic 8.0f used
when computing title_bar_padding (the expression setting window->TitleBarHeight
where window->DockIsActive is checked) with a value derived from the active
style e.g. use g.Style.FramePadding.y (or introduce a new ImGui style variable
like DockPadding and read it from g.Style) so the computed title_bar_padding
matches GetFrameHeight()’s use of g.FontSize + g.Style.FramePadding.y * 2 and
keep the same change at the other occurrence (the one noted around line 18865)
so dock header sizing and popup anchoring are consistent across DPI/style
changes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fd034ba2-6247-4fc7-a1e8-f6c7510caa2f

📥 Commits

Reviewing files that changed from the base of the PR and between 4acfcf2 and 325b7f9.

📒 Files selected for processing (3)
  • imgui.cpp
  • imgui_draw.cpp
  • imgui_widgets.cpp

Comment on lines +9590 to +9592
const float tab_spacing = 0.0f;
sections[0].Spacing = sections[0].TabCount > 0 && (sections[1].TabCount + sections[2].TabCount) > 0 ? tab_spacing : 0.0f;
sections[1].Spacing = sections[1].TabCount > 0 && sections[2].TabCount > 0 ? tab_spacing : 0.0f;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Keep zero-spacing math consistent across layout and hit-testing.

tab_spacing = 0 only changes section separators and the final tab->Offset increment here. Line 9648 still adds g.Style.ItemInnerSpacing.x into sections[*].Width, and Lines 10002-10003 still expand reorder thresholds by the old spacing. With the default 4px inner spacing, three central tabs over-report their width by 8px, so shrink/scroll/reorder start before the rendered tabs actually need it.

Also applies to: 9722-9722, 9983-9983

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@imgui_widgets.cpp` around lines 9590 - 9592, The zero tab_spacing path
currently leaves leftover inner spacing in layout and hit-testing: when
tab_spacing == 0 you must also remove g.Style.ItemInnerSpacing.x from
sections[].Width calculation and from any reorder threshold/expand computations
so widths and hit targets match rendered separators; update the code that
computes sections[0].Width/sections[1].Width (where g.Style.ItemInnerSpacing.x
is added) and the reorder threshold math that uses the old spacing (the code
that expands reorder thresholds and uses tab->Offset) to subtract or use
tab_spacing instead of always adding ItemInnerSpacing.x, and apply the same fix
to the other occurrences mentioned (the other sections[*].Spacing/Width and
reorder threshold calculations).

Comment on lines +10571 to +10576
const float tab_padding_y = 8.0f;
ImVec2 size = ImVec2(label_size.x + g.Style.FramePadding.x * 2.0f, label_size.y + tab_padding_y * 2.0f);
if (has_close_button_or_unsaved_marker)
size.x += g.Style.FramePadding.x + (g.Style.ItemInnerSpacing.x + g.FontSize); // We use Y intentionally to fit the close button circle.
size.x += g.Style.FramePadding.x * 2.0f + (g.Style.ItemInnerSpacing.x + g.FontSize); // We use Y intentionally to fit the close button circle.
else
size.x += g.Style.FramePadding.x + 1.0f;
size.x += g.Style.FramePadding.x * 2.0f + 1.0f;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

This 8px padding regresses non-docking tab bars.

TabItemCalcSize() now makes every tab FontSize + 16 tall, but BeginTabBar() still builds manual tab bars with g.Style.FramePadding.y * 2 at Line 9422. On the default style the tab item ends up noticeably taller than the bar/separator, and the extra height also applies outside dock nodes even though the PR goal was docked windows. Either gate this on dock-node bars or propagate the same padding into the generic tab-bar rect/layout path.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@imgui_widgets.cpp` around lines 10571 - 10576, TabItemCalcSize()
unconditionally adds tab_padding_y = 8.0f making each tab FontSize+16 tall which
regresses non-docking tab bars; update the logic so the extra vertical padding
is only applied for dock-node tab bars (or alternatively make BeginTabBar() use
the same padding). Modify TabItemCalcSize() to check the tab bar type/state (the
same condition BeginTabBar() uses for docked bars) before adding tab_padding_y,
or propagate g.Style.FramePadding.y * 2 into the non-docking layout path so
sizes match BeginTabBar(); adjust the size computation around the tab_padding_y
variable and the calls/sites that rely on TabItemCalcSize() to ensure non-docked
bars keep the original FramePadding-based height.

Comment on lines 10600 to 10603
if (g.Style.TabBorderSize > 0.0f)
{
draw_list->PathLineTo(ImVec2(bb.Min.x + 0.5f, y2));
draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding + 0.5f, y1 + rounding + 0.5f), rounding, 6, 9);
draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding - 0.5f, y1 + rounding + 0.5f), rounding, 9, 12);
draw_list->PathLineTo(ImVec2(bb.Max.x - 0.5f, y2));
draw_list->PathStroke(GetColorU32(ImGuiCol_Border), 0, g.Style.TabBorderSize);
draw_list->AddLine(ImVec2(bb.Min.x + 0.5f, y1), ImVec2(bb.Min.x + 0.5f, y2), IM_COL32(0, 0, 0, 255), g.Style.TabBorderSize);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Don't hardcode the left border color.

IM_COL32(0, 0, 0, 255) bypasses theme colors and global style alpha, so this strip stays opaque black in light themes or during fades. Pull it from the style instead.

🎨 Suggested fix
-        draw_list->AddLine(ImVec2(bb.Min.x + 0.5f, y1), ImVec2(bb.Min.x + 0.5f, y2), IM_COL32(0, 0, 0, 255), g.Style.TabBorderSize);
+        draw_list->AddLine(ImVec2(bb.Min.x + 0.5f, y1), ImVec2(bb.Min.x + 0.5f, y2), GetColorU32(ImGuiCol_Border), g.Style.TabBorderSize);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (g.Style.TabBorderSize > 0.0f)
{
draw_list->PathLineTo(ImVec2(bb.Min.x + 0.5f, y2));
draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding + 0.5f, y1 + rounding + 0.5f), rounding, 6, 9);
draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding - 0.5f, y1 + rounding + 0.5f), rounding, 9, 12);
draw_list->PathLineTo(ImVec2(bb.Max.x - 0.5f, y2));
draw_list->PathStroke(GetColorU32(ImGuiCol_Border), 0, g.Style.TabBorderSize);
draw_list->AddLine(ImVec2(bb.Min.x + 0.5f, y1), ImVec2(bb.Min.x + 0.5f, y2), IM_COL32(0, 0, 0, 255), g.Style.TabBorderSize);
}
if (g.Style.TabBorderSize > 0.0f)
{
draw_list->AddLine(ImVec2(bb.Min.x + 0.5f, y1), ImVec2(bb.Min.x + 0.5f, y2), GetColorU32(ImGuiCol_Border), g.Style.TabBorderSize);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@imgui_widgets.cpp` around lines 10600 - 10603, The left border color is
hardcoded as IM_COL32(0,0,0,255) which ignores theme colors and global alpha;
replace that literal with the style-derived color (e.g. use
ImGui::GetColorU32(ImGuiCol_Border) or read g.Style.Colors[ImGuiCol_Border] and
convert with ImGui::GetColorU32 to honor theme and global alpha) when calling
draw_list->AddLine in the branch that checks g.Style.TabBorderSize.

Comment on lines +7739 to +7740
const float title_bar_padding = (window->DockIsActive) ? 8.0f : g.Style.FramePadding.y;
window->TitleBarHeight = (flags & ImGuiWindowFlags_NoTitleBar) ? 0.0f : g.FontSize + title_bar_padding * 2.0f;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's check the structure of the repo and locate imgui.cpp
git ls-files | grep -E "imgui\.cpp$"

Repository: StudioCherno/imgui

Length of output: 71


🏁 Script executed:

# Get the line count to understand file size
wc -l imgui.cpp

Repository: StudioCherno/imgui

Length of output: 77


🏁 Script executed:

# View the code around lines 7739-7740
sed -n '7735,7745p' imgui.cpp

Repository: StudioCherno/imgui

Length of output: 1116


🏁 Script executed:

# View the code around lines 18865-18866
sed -n '18860,18870p' imgui.cpp

Repository: StudioCherno/imgui

Length of output: 607


🏁 Script executed:

# Check the context for popup anchor logic around lines 18453-18465
sed -n '18450,18470p' imgui.cpp

Repository: StudioCherno/imgui

Length of output: 961


🏁 Script executed:

# Search for existing dock padding or style-related constants
rg "DockIsActive|title_bar_padding|FramePadding" -A 2 -B 2 imgui.cpp | head -100

Repository: StudioCherno/imgui

Length of output: 9853


🏁 Script executed:

# Search for GetFrameHeight() definition
rg "float GetFrameHeight|ImFloat GetFrameHeight" -A 5 imgui.cpp

Repository: StudioCherno/imgui

Length of output: 44


🏁 Script executed:

# Also search in header files
fd "\.h$" | xargs rg "GetFrameHeight" -B 2 -A 2

Repository: StudioCherno/imgui

Length of output: 2354


🏁 Script executed:

# Check if there are any comments or TODOs around the dock padding lines
sed -n '7730,7750p' imgui.cpp

Repository: StudioCherno/imgui

Length of output: 2063


🏁 Script executed:

# Check comments around line 18865
sed -n '18855,18875p' imgui.cpp

Repository: StudioCherno/imgui

Length of output: 955


Avoid hard-coding dock header padding; it breaks style/DPI scaling and misaligns popups.

Line 7739 and line 18865 both use hard-coded 8.0f for dock padding. This breaks two things:

  1. DPI/style scaling: Hard-coded pixels don't scale with style.FramePadding during DPI changes or custom style configurations. The default FramePadding.y is 3.0f, not 8.0f, so dock headers will appear oversized or undersized relative to other UI elements.

  2. Popup misalignment: The dock window menu popup uses GetFrameHeight() (which returns FontSize + style.FramePadding.y * 2) for positioning, but the dock header is sized with hard-coded 8.0f * 2 padding. This creates a vertical mismatch between the popup anchor and the dock header boundary.

Derive padding from style.FramePadding or expose it as a style value to maintain consistency across scaled and custom-styled UIs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@imgui.cpp` around lines 7739 - 7740, Summary: Hard-coded dock padding (8.0f)
causes DPI/style scaling and popup alignment issues. Fix: replace the magic 8.0f
used when computing title_bar_padding (the expression setting
window->TitleBarHeight where window->DockIsActive is checked) with a value
derived from the active style e.g. use g.Style.FramePadding.y (or introduce a
new ImGui style variable like DockPadding and read it from g.Style) so the
computed title_bar_padding matches GetFrameHeight()’s use of g.FontSize +
g.Style.FramePadding.y * 2 and keep the same change at the other occurrence (the
one noted around line 18865) so dock header sizing and popup anchoring are
consistent across DPI/style changes.

Comment on lines 18453 to +18465
SetNextWindowPos(ImVec2(node->Pos.x, node->Pos.y + GetFrameHeight()), ImGuiCond_Always, ImVec2(0.0f, 0.0f));
else
SetNextWindowPos(ImVec2(node->Pos.x + node->Size.x, node->Pos.y + GetFrameHeight()), ImGuiCond_Always, ImVec2(1.0f, 0.0f));
ImVec4 popupBg = g.Style.Colors[ImGuiCol_PopupBg];
popupBg.w = 1.0f;
PushStyleColor(ImGuiCol_PopupBg, popupBg);
if (BeginPopup("#WindowMenu"))
{
node->IsFocused = true;
g.DockNodeWindowMenuHandler(&g, node, tab_bar);
EndPopup();
}
PopStyleColor();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Anchor the window menu popup to the new dock header height.

The dock header is now g.FontSize + tab_padding_y * 2, but #WindowMenu is still positioned with GetFrameHeight(), which is based on style.FramePadding.y. That leaves the popup a few pixels too high and overlapping the enlarged tab bar. Reuse the same header-height calculation here, or read it from the host window, instead of GetFrameHeight().

Also applies to: 18865-18866

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@imgui.cpp` around lines 18453 - 18465, The popup for "#WindowMenu" is
anchored using GetFrameHeight(), which is wrong now that dock header height is
g.FontSize + tab_padding_y * 2; update the positioning call that uses
GetFrameHeight() (the SetNextWindowPos call that computes ImVec2(node->Pos.x,
node->Pos.y + GetFrameHeight()) and the alternate branch using node->Pos.y +
GetFrameHeight()) to use the same header-height calculation as the dock tabs
(compute header_height = g.FontSize + tab_padding_y * 2 or read the header
height from the host window if available) so the popup sits immediately below
the enlarged dock header before calling BeginPopup("#WindowMenu") and
g.DockNodeWindowMenuHandler.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant