From 0935ae6bba74b68470c6d242a904a10545dd8503 Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Tue, 28 Apr 2026 18:28:22 +0200 Subject: [PATCH] Fixed layout/pixel-perfect issues in various widgets (w/ g_LEGACY_STROKES toggle) --- imgui.cpp | 16 ++++++++++++ imgui_tables.cpp | 22 +++++++++++++--- imgui_widgets.cpp | 65 +++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 89 insertions(+), 14 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index f383a4fd0..6eee724e5 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -7597,8 +7597,16 @@ static void ImGui::RenderWindowOuterBorders(ImGuiWindow* window) } if (g.Style.FrameBorderSize > 0 && !(window->Flags & ImGuiWindowFlags_NoTitleBar) && !window->DockIsActive) { + if (g_LEGACY_STROKES) + { float y = window->Pos.y + window->TitleBarHeight - 1; window->DrawList->AddLineH(window->Pos.x + border_size * 0.5f, window->Pos.x + window->Size.x - border_size * 0.5f, y, border_col, g.Style.FrameBorderSize); + } + else + { + float y = window->Pos.y + window->TitleBarHeight - g.Style.FrameBorderSize; + window->DrawList->AddLineH(window->Pos.x + border_size, window->Pos.x + window->Size.x - border_size, y, border_col, g.Style.FrameBorderSize); + } } } @@ -7708,7 +7716,12 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar 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. 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) + { + if (g_LEGACY_STROKES) window->DrawList->AddLineH(menu_bar_rect.Min.x + window_border_size * 0.5f, menu_bar_rect.Max.x - window_border_size * 0.5f, menu_bar_rect.Max.y, GetColorU32(ImGuiCol_Border), style.FrameBorderSize); + else + window->DrawList->AddLineH(menu_bar_rect.Min.x + window_border_size, menu_bar_rect.Max.x - window_border_size, menu_bar_rect.Max.y - style.FrameBorderSize, GetColorU32(ImGuiCol_Border), style.FrameBorderSize); + } } // Docking: Unhide tab bar (small triangle in the corner), drag from small triangle to quickly undock @@ -24246,7 +24259,10 @@ void ImGui::DebugDrawLineExtents(ImU32 col) float line_y1 = (window->DC.IsSameLine ? window->DC.CursorPosPrevLine.y : window->DC.CursorPos.y); float line_y2 = line_y1 + (window->DC.IsSameLine ? window->DC.PrevLineSize.y : window->DC.CurrLineSize.y); window->DrawList->AddLineH(curr_x - 5.0f, curr_x + 5.0f, line_y1, col, 1.0f); + if (g_LEGACY_STROKES) window->DrawList->AddLineV(curr_x - 0.5f, line_y1, line_y2, col, 1.0f); + else + window->DrawList->AddLineV(curr_x, line_y1, line_y2, col, 1.0f); window->DrawList->AddLineH(curr_x - 5.0f, curr_x + 5.0f, line_y2, col, 1.0f); } diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 19f7933ed..759673a40 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -2950,6 +2950,8 @@ static ImU32 TableGetColumnBorderCol(ImGuiTable* table, int order_n, int column_ return table->BorderColorLight; } +extern bool g_LEGACY_STROKES; + // FIXME-TABLE: This is a mess, need to redesign how we render borders (as some are also done in TableEndRow) void ImGui::TableDrawBorders(ImGuiTable* table) { @@ -2964,7 +2966,11 @@ void ImGui::TableDrawBorders(ImGuiTable* table) // Draw inner border and resizing feedback ImGuiTableInstanceData* table_instance = TableGetInstanceData(table, table->InstanceCurrent); const float border_size = TABLE_BORDER_SIZE; - const float draw_y1 = ImMax(table->InnerRect.Min.y, (table->FreezeRowsCount >= 1 ? table->InnerRect.Min.y : table->WorkRect.Min.y) + table->AngledHeadersHeight) + ((table->Flags & ImGuiTableFlags_BordersOuterH) ? 1.0f : 0.0f); + float draw_y1; + if (g_LEGACY_STROKES) + draw_y1 = ImMax(table->InnerRect.Min.y, (table->FreezeRowsCount >= 1 ? table->InnerRect.Min.y : table->WorkRect.Min.y) + table->AngledHeadersHeight) + ((table->Flags & ImGuiTableFlags_BordersOuterH) ? 1.0f : 0.0f); + else + draw_y1 = ImMax(table->InnerRect.Min.y, (table->FreezeRowsCount >= 1 ? table->InnerRect.Min.y : table->WorkRect.Min.y) + table->AngledHeadersHeight) + ((table->Flags & ImGuiTableFlags_BordersOuterH) ? border_size : 0.0f); const float draw_y2_body = table->InnerRect.Max.y; const float draw_y2_head = table->IsUsingHeaders ? ImMin(table->InnerRect.Max.y, (table->FreezeRowsCount >= 1 ? table->InnerRect.Min.y : table->WorkRect.Min.y) + table_instance->LastTopHeadersRowHeight) : draw_y1; if (table->Flags & ImGuiTableFlags_BordersInnerV) @@ -3021,7 +3027,7 @@ void ImGui::TableDrawBorders(ImGuiTable* table) else if (table->Flags & ImGuiTableFlags_BordersOuterV) { inner_drawlist->AddLineV(outer_border.Min.x, outer_border.Min.y, outer_border.Max.y, outer_col, border_size); - inner_drawlist->AddLineV(outer_border.Max.x, outer_border.Min.y, outer_border.Max.y, outer_col, border_size); + inner_drawlist->AddLineV(outer_border.Max.x - border_size, outer_border.Min.y, outer_border.Max.y, outer_col, border_size); } else if (table->Flags & ImGuiTableFlags_BordersOuterH) { @@ -3032,9 +3038,15 @@ void ImGui::TableDrawBorders(ImGuiTable* table) if ((table->Flags & ImGuiTableFlags_BordersInnerH) && table->RowPosY2 < table->OuterRect.Max.y) { // Draw bottom-most row border between it is above outer border. + const float h_inset = (table->Flags & ImGuiTableFlags_BordersOuterV) ? border_size : 0.0f; const float border_y = table->RowPosY2; if (border_y >= table->BgClipRect.Min.y && border_y < table->BgClipRect.Max.y) + { + if (g_LEGACY_STROKES) inner_drawlist->AddLineH(table->BorderX1, table->BorderX2, border_y, table->BorderColorLight, border_size); + else + inner_drawlist->AddLineH(table->BorderX1 + h_inset, table->BorderX2 - h_inset, border_y, table->BorderColorLight, border_size); + } } inner_drawlist->PopClipRect(); @@ -3627,7 +3639,11 @@ void ImGui::TableAngledHeadersRowEx(ImGuiID row_id, float angle, float max_label if (pass == 1) { // Draw border - draw_list->AddLine(bg_shape[0] - ImVec2(TABLE_BORDER_SIZE * 0.5f, 0.0f), bg_shape[3] - ImVec2(TABLE_BORDER_SIZE * 0.5f, 0.0f), TableGetColumnBorderCol(table, order_n, column_n), TABLE_BORDER_SIZE); + const float border_size = TABLE_BORDER_SIZE; + if (g_LEGACY_STROKES) + draw_list->AddLine(bg_shape[0] - ImVec2(border_size * 0.5f, 0.0f), bg_shape[3] - ImVec2(border_size * 0.5f, 0.0f), TableGetColumnBorderCol(table, order_n, column_n), border_size); + else + draw_list->AddLine(bg_shape[0] + ImVec2(border_size * 0.5f, 0.0f), bg_shape[3] + ImVec2(border_size * 0.5f, 0.0f), TableGetColumnBorderCol(table, order_n, column_n), border_size); } } PopClipRect(); diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 17968a54d..97ec966de 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -901,6 +901,8 @@ bool ImGui::ArrowButton(const char* str_id, ImGuiDir dir) return ArrowButtonEx(str_id, dir, ImVec2(sz, sz), ImGuiButtonFlags_None); } +extern bool g_LEGACY_STROKES; + // Button to close a window bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos) { @@ -930,7 +932,7 @@ bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos) window->DrawList->AddRectFilled(bb.Min, bb.Max, bg_col); RenderNavCursor(bb, id, ImGuiNavRenderCursorFlags_Compact); const ImU32 cross_col = GetColorU32(ImGuiCol_Text); - const ImVec2 cross_center = bb.GetCenter() - ImVec2(0.5f, 0.5f); + const ImVec2 cross_center = g_LEGACY_STROKES ? (bb.GetCenter() - ImVec2(0.5f, 0.5f)) : bb.GetCenter(); const float cross_extent = g.FontSize * 0.5f * 0.7071f - 1.0f; const float cross_thickness = 1.0f * (float)(int)g.Style._MainScale; // FIXME-DPI window->DrawList->AddLine(cross_center + ImVec2(+cross_extent, +cross_extent), cross_center + ImVec2(-cross_extent, -cross_extent), cross_col, cross_thickness); @@ -1762,7 +1764,11 @@ void ImGui::SeparatorTextEx(ImGuiID id, const char* label, const char* label_end const float sep1_x1 = pos.x; const float sep2_x2 = bb.Max.x; - const float seps_y = ImTrunc((bb.Min.y + bb.Max.y) * 0.5f + 0.999f); + float seps_y; + if (g_LEGACY_STROKES) + seps_y = ImTrunc((bb.Min.y + bb.Max.y) * 0.5f + 0.999f); + else + seps_y = ImTrunc((bb.Min.y + bb.Max.y) * 0.5f + (-separator_thickness * 0.5f + 1.0f) + 0.999f); // Align hline vertically, and snap to pixels. const float label_avail_w = ImMax(0.0f, sep2_x2 - sep1_x1 - padding.x * 2.0f); const ImVec2 label_pos(pos.x + padding.x + ImMax(0.0f, (label_avail_w - label_size.x - extra_w) * style.SeparatorTextAlign.x), pos.y + text_baseline_y); // FIXME-ALIGN @@ -5691,7 +5697,11 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ state->CursorAnim += io.DeltaTime; bool cursor_is_visible = (!g.IO.ConfigInputTextCursorBlink) || (state->CursorAnim <= 0.0f) || ImFmod(state->CursorAnim, 1.20f) <= 0.80f; ImVec2 cursor_screen_pos = ImTrunc(draw_pos + cursor_offset - draw_scroll); - ImRect cursor_screen_rect(cursor_screen_pos.x, cursor_screen_pos.y - g.FontSize + 0.5f, cursor_screen_pos.x + 1.0f, cursor_screen_pos.y - 1.5f); + ImRect cursor_screen_rect; + if (g_LEGACY_STROKES) + cursor_screen_rect = ImRect(cursor_screen_pos.x, cursor_screen_pos.y - g.FontSize + 0.5f, cursor_screen_pos.x + 1.0f, cursor_screen_pos.y - 1.5f); + else + cursor_screen_rect = ImRect(cursor_screen_pos.x, cursor_screen_pos.y - g.FontSize + 1.0f, cursor_screen_pos.x + 1.0f, cursor_screen_pos.y - 1.0f); if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect)) draw_window->DrawList->AddLineV(cursor_screen_rect.Min.x, cursor_screen_rect.Min.y, cursor_screen_rect.Max.y, GetColorU32(ImGuiCol_InputTextCursor), style.InputTextCursorSize); @@ -7209,16 +7219,27 @@ void ImGui::TreeNodeDrawLineToChildNode(const ImVec2& target_pos) return; ImGuiTreeNodeStackData* parent_data = &g.TreeNodeStack.Data[g.TreeNodeStack.Size - 1]; - float x1 = ImTrunc(parent_data->DrawLinesX1); + float x1; + if (g_LEGACY_STROKES) + x1 = ImTrunc(parent_data->DrawLinesX1); + else + x1 = ImTrunc(parent_data->DrawLinesX1 - g.Style.TreeLinesSize * 0.5f) + g.Style.TreeLinesSize * 0.5f; // Draw line centered at X1, but snap to pixels boundary. float x2 = ImTrunc(target_pos.x - g.Style.ItemInnerSpacing.x); - float y = ImTrunc(target_pos.y); + float y; + if (g_LEGACY_STROKES) + y = ImTrunc(target_pos.y); + else + y = ImTrunc(target_pos.y) + g.Style.TreeLinesSize * 0.5f; float rounding = (g.Style.TreeLinesRounding > 0.0f) ? ImMin(x2 - x1, g.Style.TreeLinesRounding) : 0.0f; parent_data->DrawLinesToNodesY2 = ImMax(parent_data->DrawLinesToNodesY2, y - rounding); if (x1 >= x2) return; if (rounding > 0.0f) { - x1 += 0.5f + rounding; + if (g_LEGACY_STROKES) + x1 += 0.5f + rounding; + else + x1 += rounding; window->DrawList->PathArcToFast(ImVec2(x1, y - rounding), rounding, 6, 3); if (x1 < x2) window->DrawList->PathLineTo(ImVec2(x2, y)); @@ -7226,7 +7247,11 @@ void ImGui::TreeNodeDrawLineToChildNode(const ImVec2& target_pos) } else { - window->DrawList->AddLineH(x1, x2, y, GetColorU32(ImGuiCol_TreeLines), g.Style.TreeLinesSize); + // We use AddLine() instead of AddLineH() as we provide coordinates for line center, + // in order to make the code use same coordinates for both rounded and non-rounded version. + if (!g_LEGACY_STROKES) + x1 += g.Style.TreeLinesSize * 0.5f; // Avoid overdraw + window->DrawList->AddLine(ImVec2(x1, y), ImVec2(x2, y), GetColorU32(ImGuiCol_TreeLines), g.Style.TreeLinesSize); } } @@ -7249,10 +7274,15 @@ void ImGui::TreeNodeDrawLineToTreePop(const ImGuiTreeNodeStackData* data) y2 = ImMin(y2, window->ClipRect.Max.y); if (y2 <= y1) return; - float x = ImTrunc(data->DrawLinesX1); + float x; + if (g_LEGACY_STROKES) + x = ImTrunc(data->DrawLinesX1); + else + x = ImTrunc(data->DrawLinesX1 - g.Style.TreeLinesSize * 0.5f) + g.Style.TreeLinesSize * 0.5f; // Draw line centered at X1, but snap to pixels boundary. if (data->DrawLinesTableColumn != -1) TablePushColumnChannel(data->DrawLinesTableColumn); - window->DrawList->AddLineV(x, y1, y2, GetColorU32(ImGuiCol_TreeLines), g.Style.TreeLinesSize); + // We use AddLine() instead of AddLineV() as we provide coordinates for line center, + window->DrawList->AddLine(ImVec2(x, y1), ImVec2(x, y2), GetColorU32(ImGuiCol_TreeLines), g.Style.TreeLinesSize); if (data->DrawLinesTableColumn != -1) TablePopColumnChannel(); } @@ -10934,8 +10964,18 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, if (tab_contents_visible && (tab_bar->Flags & ImGuiTabBarFlags_DrawSelectedOverline) && style.TabBarOverlineSize > 0.0f) { // Might be moved to TabItemBackground() ? - ImVec2 tl = bb.GetTL() + ImVec2(0, 1.0f * g.CurrentDpiScale); - ImVec2 tr = bb.GetTR() + ImVec2(0, 1.0f * g.CurrentDpiScale); + ImVec2 tl; + ImVec2 tr; + if (g_LEGACY_STROKES) + { + tl = bb.GetTL() + ImVec2(0, 1.0f * g.CurrentDpiScale); + tr = bb.GetTR() + ImVec2(0, 1.0f * g.CurrentDpiScale); + } + else + { + tl = bb.GetTL() + ImVec2(0, style.TabBarOverlineSize * 0.5f); + tr = bb.GetTR() + ImVec2(0, style.TabBarOverlineSize * 0.5f); + } ImU32 overline_col = GetColorU32(tab_bar_focused ? ImGuiCol_TabSelectedOverline : ImGuiCol_TabDimmedSelectedOverline); if (style.TabRounding > 0.0f) { @@ -10946,7 +10986,10 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, } else { + if (g_LEGACY_STROKES) display_draw_list->AddLine(tl - ImVec2(0.5f, 0.5f), tr - ImVec2(0.5f, 0.5f), overline_col, style.TabBarOverlineSize); + else + display_draw_list->AddLine(tl, tr, overline_col, style.TabBarOverlineSize); } } RenderNavCursor(bb, id);