-
Notifications
You must be signed in to change notification settings - Fork 7
Custom UI styling: hamburger menu, tab spacing, centered labels #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: hazel
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7736,7 +7736,8 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) | |
| // Lock menu offset so size calculation can use it as menu-bar windows need a minimum size. | ||
| window->DC.MenuBarOffset.x = ImMax(ImMax(window->WindowPadding.x, style.ItemSpacing.x), g.NextWindowData.MenuBarOffsetMinVal.x); | ||
| window->DC.MenuBarOffset.y = g.NextWindowData.MenuBarOffsetMinVal.y; | ||
| window->TitleBarHeight = (flags & ImGuiWindowFlags_NoTitleBar) ? 0.0f : g.FontSize + g.Style.FramePadding.y * 2.0f; | ||
| 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; | ||
| window->MenuBarHeight = (flags & ImGuiWindowFlags_MenuBar) ? window->DC.MenuBarOffset.y + g.FontSize + g.Style.FramePadding.y * 2.0f : 0.0f; | ||
| window->FontRefSize = g.FontSize; // Lock this to discourage calling window->CalcFontSize() outside of current window. | ||
|
|
||
|
|
@@ -18452,12 +18453,16 @@ static void ImGui::DockNodeWindowMenuUpdate(ImGuiDockNode* node, ImGuiTabBar* ta | |
| 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(); | ||
|
Comment on lines
18453
to
+18465
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Anchor the window menu popup to the new dock header height. The dock header is now Also applies to: 18865-18866 🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| // User helper to append/amend into a dock node tab bar. Most commonly used to add e.g. a "+" button. | ||
|
|
@@ -18857,28 +18862,30 @@ static void ImGui::DockNodeCalcTabBarLayout(const ImGuiDockNode* node, ImRect* o | |
| ImGuiContext& g = *GImGui; | ||
| ImGuiStyle& style = g.Style; | ||
|
|
||
| ImRect r = ImRect(node->Pos.x, node->Pos.y, node->Pos.x + node->Size.x, node->Pos.y + g.FontSize + g.Style.FramePadding.y * 2.0f); | ||
| const float tab_padding_y = 8.0f; | ||
| ImRect r = ImRect(node->Pos.x, node->Pos.y, node->Pos.x + node->Size.x, node->Pos.y + g.FontSize + tab_padding_y * 2.0f); | ||
| if (out_title_rect) { *out_title_rect = r; } | ||
|
|
||
| r.Min.x += style.WindowBorderSize; | ||
| r.Max.x -= style.WindowBorderSize; | ||
|
|
||
| float button_sz = g.FontSize; | ||
| float button_y = r.Min.y + (r.GetHeight() - button_sz) * 0.5f; | ||
| r.Min.x += style.FramePadding.x; | ||
| r.Max.x -= style.FramePadding.x; | ||
| ImVec2 window_menu_button_pos = ImVec2(r.Min.x, r.Min.y + style.FramePadding.y); | ||
| ImVec2 window_menu_button_pos = ImVec2(r.Min.x, button_y); | ||
| if (node->HasCloseButton) | ||
| { | ||
| if (out_close_button_pos) *out_close_button_pos = ImVec2(r.Max.x - button_sz, r.Min.y + style.FramePadding.y); | ||
| if (out_close_button_pos) *out_close_button_pos = ImVec2(r.Max.x - button_sz, button_y); | ||
| r.Max.x -= button_sz + style.ItemInnerSpacing.x; | ||
| } | ||
| if (node->HasWindowMenuButton && style.WindowMenuButtonPosition == ImGuiDir_Left) | ||
| { | ||
| r.Min.x += button_sz + style.ItemInnerSpacing.x; | ||
| r.Min.x += button_sz + style.FramePadding.x; | ||
| } | ||
| else if (node->HasWindowMenuButton && style.WindowMenuButtonPosition == ImGuiDir_Right) | ||
| { | ||
| window_menu_button_pos = ImVec2(r.Max.x - button_sz, r.Min.y + style.FramePadding.y); | ||
| window_menu_button_pos = ImVec2(r.Max.x - button_sz, button_y); | ||
| r.Max.x -= button_sz + style.ItemInnerSpacing.x; | ||
| } | ||
| if (out_tab_bar_rect) { *out_tab_bar_rect = r; } | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -903,13 +903,10 @@ bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos) | |||||||||||||||||||||||||||
| return pressed; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // Render | ||||||||||||||||||||||||||||
| ImU32 bg_col = GetColorU32(held ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered); | ||||||||||||||||||||||||||||
| if (hovered) | ||||||||||||||||||||||||||||
| window->DrawList->AddRectFilled(bb.Min, bb.Max, bg_col); | ||||||||||||||||||||||||||||
| RenderNavCursor(bb, id, ImGuiNavRenderCursorFlags_Compact); | ||||||||||||||||||||||||||||
| const ImU32 cross_col = GetColorU32(ImGuiCol_Text); | ||||||||||||||||||||||||||||
| const ImU32 cross_col = (hovered || held) ? GetColorU32(ImGuiCol_Text) : GetColorU32(ImGuiCol_TextDisabled); | ||||||||||||||||||||||||||||
| const ImVec2 cross_center = bb.GetCenter() - ImVec2(0.5f, 0.5f); | ||||||||||||||||||||||||||||
| const float cross_extent = g.FontSize * 0.5f * 0.7071f - 1.0f; | ||||||||||||||||||||||||||||
| const float cross_extent = g.FontSize * 0.5f * 0.5f - 1.0f; | ||||||||||||||||||||||||||||
| const float cross_thickness = 1.0f; // FIXME-DPI | ||||||||||||||||||||||||||||
| window->DrawList->AddLine(cross_center + ImVec2(+cross_extent, +cross_extent), cross_center + ImVec2(-cross_extent, -cross_extent), cross_col, cross_thickness); | ||||||||||||||||||||||||||||
| window->DrawList->AddLine(cross_center + ImVec2(+cross_extent, -cross_extent), cross_center + ImVec2(-cross_extent, +cross_extent), cross_col, cross_thickness); | ||||||||||||||||||||||||||||
|
|
@@ -932,10 +929,7 @@ bool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos, ImGuiDockNode* dock_no | |||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // Render | ||||||||||||||||||||||||||||
| //bool is_dock_menu = (window->DockNodeAsHost && !window->Collapsed); | ||||||||||||||||||||||||||||
| ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); | ||||||||||||||||||||||||||||
| ImU32 text_col = GetColorU32(ImGuiCol_Text); | ||||||||||||||||||||||||||||
| if (hovered || held) | ||||||||||||||||||||||||||||
| window->DrawList->AddRectFilled(bb.Min, bb.Max, bg_col); | ||||||||||||||||||||||||||||
| RenderNavCursor(bb, id, ImGuiNavRenderCursorFlags_Compact); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| if (dock_node) | ||||||||||||||||||||||||||||
|
|
@@ -9593,8 +9587,9 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) | |||||||||||||||||||||||||||
| ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerBySection); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // Calculate spacing between sections | ||||||||||||||||||||||||||||
| sections[0].Spacing = sections[0].TabCount > 0 && (sections[1].TabCount + sections[2].TabCount) > 0 ? g.Style.ItemInnerSpacing.x : 0.0f; | ||||||||||||||||||||||||||||
| sections[1].Spacing = sections[1].TabCount > 0 && sections[2].TabCount > 0 ? g.Style.ItemInnerSpacing.x : 0.0f; | ||||||||||||||||||||||||||||
| 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; | ||||||||||||||||||||||||||||
|
Comment on lines
+9590
to
+9592
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Keep zero-spacing math consistent across layout and hit-testing.
Also applies to: 9722-9722, 9983-9983 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // Setup next selected tab | ||||||||||||||||||||||||||||
| ImGuiID scroll_to_tab_id = 0; | ||||||||||||||||||||||||||||
|
|
@@ -9724,7 +9719,7 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) | |||||||||||||||||||||||||||
| ImGuiTabItem* tab = &tab_bar->Tabs[section_tab_index + tab_n]; | ||||||||||||||||||||||||||||
| tab->Offset = tab_offset; | ||||||||||||||||||||||||||||
| tab->NameOffset = -1; | ||||||||||||||||||||||||||||
| tab_offset += tab->Width + (tab_n < section->TabCount - 1 ? g.Style.ItemInnerSpacing.x : 0.0f); | ||||||||||||||||||||||||||||
| tab_offset += tab->Width + (tab_n < section->TabCount - 1 ? tab_spacing : 0.0f); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| tab_bar->WidthAllTabs += ImMax(section->Width + section->Spacing, 0.0f); | ||||||||||||||||||||||||||||
| tab_offset += section->Spacing; | ||||||||||||||||||||||||||||
|
|
@@ -9985,6 +9980,7 @@ void ImGui::TabBarQueueReorderFromMousePos(ImGuiTabBar* tab_bar, ImGuiTabItem* s | |||||||||||||||||||||||||||
| if ((tab_bar->Flags & ImGuiTabBarFlags_Reorderable) == 0) | ||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| const float tab_spacing = 0.0f; | ||||||||||||||||||||||||||||
| const bool is_central_section = (src_tab->Flags & ImGuiTabItemFlags_SectionMask_) == 0; | ||||||||||||||||||||||||||||
| const float bar_offset = tab_bar->BarRect.Min.x - (is_central_section ? tab_bar->ScrollingTarget : 0); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
@@ -10475,7 +10471,7 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, | |||||||||||||||||||||||||||
| if (is_visible) | ||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||
| ImDrawList* display_draw_list = window->DrawList; | ||||||||||||||||||||||||||||
| const ImU32 tab_col = GetColorU32((held || hovered) ? ImGuiCol_TabHovered : tab_contents_visible ? (tab_bar_focused ? ImGuiCol_TabSelected : ImGuiCol_TabDimmedSelected) : (tab_bar_focused ? ImGuiCol_Tab : ImGuiCol_TabDimmed)); | ||||||||||||||||||||||||||||
| const ImU32 tab_col = GetColorU32(tab_contents_visible ? (tab_bar_focused ? ImGuiCol_TabSelected : ImGuiCol_TabDimmedSelected) : (tab_bar_focused ? ImGuiCol_Tab : ImGuiCol_TabDimmed)); | ||||||||||||||||||||||||||||
| TabItemBackground(display_draw_list, bb, flags, tab_col); | ||||||||||||||||||||||||||||
| if (tab_contents_visible && (tab_bar->Flags & ImGuiTabBarFlags_DrawSelectedOverline) && style.TabBarOverlineSize > 0.0f) | ||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||
|
|
@@ -10572,11 +10568,12 @@ ImVec2 ImGui::TabItemCalcSize(const char* label, bool has_close_button_or_unsave | |||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||
| ImGuiContext& g = *GImGui; | ||||||||||||||||||||||||||||
| ImVec2 label_size = CalcTextSize(label, NULL, true); | ||||||||||||||||||||||||||||
| ImVec2 size = ImVec2(label_size.x + g.Style.FramePadding.x, label_size.y + g.Style.FramePadding.y * 2.0f); | ||||||||||||||||||||||||||||
| 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; | ||||||||||||||||||||||||||||
|
Comment on lines
+10571
to
+10576
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This 8px padding regresses non-docking tab bars.
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||
| return ImVec2(ImMin(size.x, TabBarCalcMaxTabWidth()), size.y); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
@@ -10602,11 +10599,7 @@ void ImGui::TabItemBackground(ImDrawList* draw_list, const ImRect& bb, ImGuiTabI | |||||||||||||||||||||||||||
| draw_list->PathFillConvex(col); | ||||||||||||||||||||||||||||
| 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); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
Comment on lines
10600
to
10603
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't hardcode the left border color.
🎨 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
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
@@ -10634,7 +10627,14 @@ void ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb, | |||||||||||||||||||||||||||
| #endif | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // Render text label (with clipping + alpha gradient) + unsaved marker | ||||||||||||||||||||||||||||
| ImRect text_ellipsis_clip_bb(bb.Min.x + frame_padding.x, bb.Min.y + frame_padding.y, bb.Max.x - frame_padding.x, bb.Max.y); | ||||||||||||||||||||||||||||
| // Center text vertically within visible tab area (background starts at bb.Min.y + 1.0f) | ||||||||||||||||||||||||||||
| float visible_top = bb.Min.y + 1.0f; | ||||||||||||||||||||||||||||
| float visible_height = bb.Max.y - visible_top; | ||||||||||||||||||||||||||||
| float text_y = visible_top + (visible_height - label_size.y) * 0.5f; | ||||||||||||||||||||||||||||
| // Center text horizontally within the tab | ||||||||||||||||||||||||||||
| float avail_width = bb.GetWidth() - frame_padding.x * 2.0f; | ||||||||||||||||||||||||||||
| float text_offset_x = (avail_width > label_size.x) ? (avail_width - label_size.x) * 0.5f : 0.0f; | ||||||||||||||||||||||||||||
| ImRect text_ellipsis_clip_bb(bb.Min.x + frame_padding.x + text_offset_x, text_y, bb.Max.x - frame_padding.x, bb.Max.y); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // Return clipped state ignoring the close button | ||||||||||||||||||||||||||||
| if (out_text_clipped) | ||||||||||||||||||||||||||||
|
|
@@ -10644,7 +10644,7 @@ void ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb, | |||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| const float button_sz = g.FontSize; | ||||||||||||||||||||||||||||
| const ImVec2 button_pos(ImMax(bb.Min.x, bb.Max.x - frame_padding.x - button_sz), bb.Min.y + frame_padding.y); | ||||||||||||||||||||||||||||
| const ImVec2 button_pos(ImMax(bb.Min.x, bb.Max.x - frame_padding.x - button_sz), bb.Min.y + (bb.GetHeight() - button_sz) * 0.5f); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // Close Button & Unsaved Marker | ||||||||||||||||||||||||||||
| // We are relying on a subtle and confusing distinction between 'hovered' and 'g.HoveredId' which happens because we are using ImGuiButtonFlags_AllowOverlapMode + SetItemAllowOverlap() | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: StudioCherno/imgui
Length of output: 71
🏁 Script executed:
# Get the line count to understand file size wc -l imgui.cppRepository: StudioCherno/imgui
Length of output: 77
🏁 Script executed:
Repository: StudioCherno/imgui
Length of output: 1116
🏁 Script executed:
Repository: StudioCherno/imgui
Length of output: 607
🏁 Script executed:
Repository: StudioCherno/imgui
Length of output: 961
🏁 Script executed:
Repository: StudioCherno/imgui
Length of output: 9853
🏁 Script executed:
Repository: StudioCherno/imgui
Length of output: 44
🏁 Script executed:
Repository: StudioCherno/imgui
Length of output: 2354
🏁 Script executed:
Repository: StudioCherno/imgui
Length of output: 2063
🏁 Script executed:
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.0ffor dock padding. This breaks two things:DPI/style scaling: Hard-coded pixels don't scale with
style.FramePaddingduring DPI changes or custom style configurations. The defaultFramePadding.yis3.0f, not8.0f, so dock headers will appear oversized or undersized relative to other UI elements.Popup misalignment: The dock window menu popup uses
GetFrameHeight()(which returnsFontSize + style.FramePadding.y * 2) for positioning, but the dock header is sized with hard-coded8.0f * 2padding. This creates a vertical mismatch between the popup anchor and the dock header boundary.Derive padding from
style.FramePaddingor expose it as a style value to maintain consistency across scaled and custom-styled UIs.🤖 Prompt for AI Agents