From b7adfa395dfc28ab7c513000800119e2cea8f79a Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 18 May 2026 17:17:15 +0200 Subject: [PATCH] DrawList: misc fixes, amends (replace uses of GetColorU32, macros fixes, default flags). Demo: added rotating square, added MiterOnly checkbox. --- imgui.h | 2 +- imgui_demo.cpp | 29 +++++++++++++++++++---------- imgui_draw.cpp | 30 +++++++++++++++++++----------- 3 files changed, 39 insertions(+), 22 deletions(-) diff --git a/imgui.h b/imgui.h index c497b0db1..22dbb0e5b 100644 --- a/imgui.h +++ b/imgui.h @@ -3570,7 +3570,7 @@ struct ImDrawList inline void PathLineToMergeDuplicate(const ImVec2& pos) { if (_Path.Size == 0 || memcmp(&_Path.Data[_Path.Size - 1], &pos, 8) != 0) _Path.push_back(pos); } inline void PathFillConvex(ImU32 col, ImDrawFlags flags = 0) { AddConvexPolyFilled(_Path.Data, _Path.Size, col, flags); _Path.Size = 0; } inline void PathFillConcave(ImU32 col) { AddConcavePolyFilled(_Path.Data, _Path.Size, col); _Path.Size = 0; } - inline void PathStroke(ImU32 col, float thickness = 1.0f, ImDrawFlags flags = ImDrawFlags_StrokeCenter) { AddPolyline(_Path.Data, _Path.Size, col, thickness, flags); _Path.Size = 0; } + inline void PathStroke(ImU32 col, float thickness = 1.0f, ImDrawFlags flags = 0) { AddPolyline(_Path.Data, _Path.Size, col, thickness, flags); _Path.Size = 0; } IMGUI_API void PathArcTo(const ImVec2& center, float radius, float a_min, float a_max, int num_segments = 0); IMGUI_API void PathArcToFast(const ImVec2& center, float radius, int a_min_of_12, int a_max_of_12); // Use precomputed angles for a 12 steps circle IMGUI_API void PathEllipticalArcTo(const ImVec2& center, const ImVec2& radius, float rot, float a_min, float a_max, int num_segments = 0); // Ellipse diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 137ce1138..18048c769 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -10418,7 +10418,6 @@ static void ShowExampleAppCustomRendering(bool* p_open) static bool curve_segments_override = false; static int curve_segments_override_v = 8; static ImVec4 colf = ImVec4(1.0f, 1.0f, 0.4f, 1.0f); - static bool square_caps = false; ImGui::DragFloat("Size", &sz, 0.2f, 0.2f, 100.0f, "%.0f"); ImGui::DragFloat("Thickness", &thickness, 0.1f, 0.0f, 30.0f, "%.02f"); ImGui::SliderInt("N-gon sides", &ngon_sides, 3, 12); @@ -10429,7 +10428,6 @@ static void ShowExampleAppCustomRendering(bool* p_open) ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); curve_segments_override |= ImGui::SliderInt("Curves segments override", &curve_segments_override_v, 3, 40); ImGui::ColorEdit4("Color", &colf.x); - ImGui::Checkbox("Square caps", &square_caps); static ImDrawFlags flags = ImDrawFlags_None; ImGui::AlignTextToFramePadding(); @@ -10440,12 +10438,13 @@ static void ShowExampleAppCustomRendering(bool* p_open) ImGui::SameLine(); ImGui::RadioButton("CenterPixelAligned", &flags, ImDrawFlags_StrokeCenterPixelAligned); ImGui::SameLine(); ImGui::RadioButton("Outside", &flags, ImDrawFlags_StrokeOutside); ImGui::SameLine(); ImGui::RadioButton("Legacy", &flags, ImDrawFlags_StrokeLegacy); + ImGui::CheckboxFlags("MiterOnly", &flags, ImDrawFlags_MiterOnly); + ImGui::SameLine(); ImGui::CheckboxFlags("SquareCap", &flags, ImDrawFlags_SquareCap); const ImVec2 p = ImGui::GetCursorScreenPos(); const ImU32 col = ImColor(colf); const float spacing = 10.0f; const ImDrawFlags corners_tl_br = ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersBottomRight; - const ImDrawFlags cap_flags = square_caps ? ImDrawFlags_SquareCap : ImDrawFlags_None; const float rounding = sz / 5.0f; const int circle_segments = circle_segments_override ? circle_segments_override_v : 0; const int curve_segments = curve_segments_override ? curve_segments_override_v : 0; @@ -10464,8 +10463,8 @@ static void ShowExampleAppCustomRendering(bool* p_open) draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, 0.0f, th, flags); x += sz + spacing; // Square draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, rounding, th, flags); x += sz + spacing; // Square with all rounded corners draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, rounding, th, flags | corners_tl_br); x += sz + spacing; // Square with two rounded corners - draw_list->AddTriangle(ImVec2(x+sz*0.5f,y), ImVec2(x+sz, y+sz-0.5f), ImVec2(x, y+sz-0.5f), col, th/*, flags*/);x += sz + spacing; // Triangle - //draw_list->AddTriangle(ImVec2(x+sz*0.2f,y), ImVec2(x, y+sz-0.5f), ImVec2(x+sz*0.4f, y+sz-0.5f), col, th);x+= sz*0.4f + spacing; // Thin triangle + draw_list->AddTriangle(ImVec2(x+sz*0.5f,y), ImVec2(x+sz, y+sz-0.5f), ImVec2(x, y+sz-0.5f), col, th, flags);x += sz + spacing; // Triangle + //draw_list->AddTriangle(ImVec2(x+sz*0.2f,y), ImVec2(x, y+sz-0.5f), ImVec2(x+sz*0.4f, y+sz-0.5f), col, th, flags); x+= sz*0.4f + spacing; // Thin triangle PathConcaveShape(draw_list, x, y, sz); draw_list->PathStroke(col, th, flags | ImDrawFlags_Closed); x += sz + spacing; // Concave Shape //draw_list->AddPolyline(concave_shape, IM_COUNTOF(concave_shape), col, ImDrawFlags_Closed, th); draw_list->AddLineH(x, x + sz, y, col, th, flags); x += sz + spacing; // Horizontal line (note: drawing a filled rectangle will be faster!) @@ -10473,19 +10472,29 @@ static void ShowExampleAppCustomRendering(bool* p_open) draw_list->AddLine(ImVec2(x, y), ImVec2(x + sz, y), col, th, flags); x += sz + spacing; draw_list->AddLine(ImVec2(x, y + sz), ImVec2(x, y), col, th, flags); x += spacing; - draw_list->AddLine(ImVec2(x, y), ImVec2(x + sz, y + sz), col, th, flags | cap_flags); x += sz + spacing; // Diagonal line + draw_list->AddLine(ImVec2(x, y), ImVec2(x + sz, y + sz), col, th, flags); x += sz + spacing; // Diagonal line + + // Rotating square made of lines + float side_step = 3.141592f * 0.5f; + for (int side = 0; side < 4; side++) + { + float t = (float)ImGui::GetTime() * 0.3f + side * side_step; + ImVec2 center(x + sz * 0.5f, y + sz * 0.5f); + draw_list->AddLine({ center.x + cosf(t) * sz * 0.5f, center.y + sinf(t) * sz * 0.5f }, { center.x + cosf(t + side_step) * sz * 0.5f, center.y + sinf(t + side_step) * sz * 0.5f }, col, th, flags); + } + x += sz + spacing; // Path - draw_list->PathArcTo(ImVec2(x + sz*0.5f, y + sz*0.5f), sz*0.5f, 3.141592f, 3.141592f * -0.5f); - draw_list->PathStroke(col, th, flags | cap_flags); + draw_list->PathArcTo(ImVec2(x + sz*0.5f, y + sz*0.5f), sz*0.5f, 3.141592f * -0.5f, 3.141592f); + draw_list->PathStroke(col, th, flags); x += sz + spacing; // Quadratic Bezier Curve (3 control points) - draw_list->AddBezierQuadratic(ImVec2(x + cp3[0].x, y + cp3[0].y), ImVec2(x + cp3[1].x, y + cp3[1].y), ImVec2(x + cp3[2].x, y + cp3[2].y), col, th, curve_segments, flags | cap_flags); + draw_list->AddBezierQuadratic(ImVec2(x + cp3[0].x, y + cp3[0].y), ImVec2(x + cp3[1].x, y + cp3[1].y), ImVec2(x + cp3[2].x, y + cp3[2].y), col, th, curve_segments, flags); x += sz + spacing; // Cubic Bezier Curve (4 control points) - draw_list->AddBezierCubic(ImVec2(x + cp4[0].x, y + cp4[0].y), ImVec2(x + cp4[1].x, y + cp4[1].y), ImVec2(x + cp4[2].x, y + cp4[2].y), ImVec2(x + cp4[3].x, y + cp4[3].y), col, th, curve_segments, flags | cap_flags); + draw_list->AddBezierCubic(ImVec2(x + cp4[0].x, y + cp4[0].y), ImVec2(x + cp4[1].x, y + cp4[1].y), ImVec2(x + cp4[2].x, y + cp4[2].y), ImVec2(x + cp4[3].x, y + cp4[3].y), col, th, curve_segments, flags); x = p.x + 4; y += sz + spacing; diff --git a/imgui_draw.cpp b/imgui_draw.cpp index fb593fccd..07bade2b0 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -825,6 +825,7 @@ void ImDrawList::PrimQuadUV(const ImVec2& a, const ImVec2& b, const ImVec2& c, c #define IM_FIXNORMAL2F_MAX_INVLEN2 100.0f // 500.0f (see #4053, #3366) #define IM_FIXNORMAL2F(VX,VY) { float d2 = VX*VX + VY*VY; if (d2 > 0.000001f) { float inv_len2 = 1.0f / d2; if (inv_len2 > IM_FIXNORMAL2F_MAX_INVLEN2) inv_len2 = IM_FIXNORMAL2F_MAX_INVLEN2; VX *= inv_len2; VY *= inv_len2; } } (void)0 +// We avoid using the ImVec2 math operators here to reduce cost to a minimum for debug/non-inlined builds. // We avoid using the 'do { } while (false)` idiom in those macros as they typically have overhead in debug builds. #define IM_APPEND_VTX(PX, PY, UV, COL) \ { \ @@ -851,7 +852,7 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ { const ImU32 col_trans = col & ~IM_COL32_A_MASK; - const ImDrawFlags stroke_pos = (flags & ImDrawFlags_StrokeMask_); + const ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeCenter); const bool closed = (flags & ImDrawFlags_Closed) != 0; const bool miters_only = (flags & ImDrawFlags_MiterOnly) != 0; const float miter_distance_limit_sqr = IM_POLYLINE_MITER_LIMIT * IM_POLYLINE_MITER_LIMIT; @@ -994,7 +995,7 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ // miter offset formula is derived here: https://www.angusj.com/clipper2/Docs/Trigonometry.htm const float cos_theta_clamped = ImMax(IM_POLYLINE_MITER_ANGLE_LIMIT, cos_theta); // Avoid div by 0. - const float miter_scale_factor = ImMin(1000.f, 1.f / (1.0f + cos_theta_clamped)); + const float miter_scale_factor = ImMin(1000.0f, 1.0f / (1.0f + cos_theta_clamped)); float miter_offset_x = (n0.x + n1.x) * miter_scale_factor; float miter_offset_y = (n0.y + n1.y) * miter_scale_factor; @@ -1123,6 +1124,14 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ PrimUnreserve(idx_count - idx_used, vtx_count - vtx_used); } +static ImU32 ImAlphaMultiply(ImU32 col, float alpha_mul) +{ + IM_ASSERT_PARANOID(a >= 0.0f && a < 1.0f); // We don't clamp! + ImU32 a = (col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT; + a = (ImU32)(a * alpha_mul); // We don't need to clamp 0..255 because alpha is in 0..1 range. + return (col & ~IM_COL32_A_MASK) | (a << IM_COL32_A_SHIFT); +} + void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 col, float thickness, ImDrawFlags flags) { if (points_count < 2 || (col & IM_COL32_A_MASK) == 0) @@ -1133,8 +1142,7 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 return; if (screen_thickness < 1.0f) { - const float alpha = screen_thickness; - col = ImGui::GetColorU32(col, alpha); + col = ImAlphaMultiply(col, screen_thickness); screen_thickness = 1.0f; thickness = _FringeScale; } @@ -1512,7 +1520,7 @@ void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_coun const float cos_theta = n0.x * n1.x + n0.y * n1.y; // miter offset formula is derived here: https://www.angusj.com/clipper2/Docs/Trigonometry.htm const float cos_theta_clamped = ImMax(IM_POLYLINE_MITER_ANGLE_LIMIT, cos_theta); // Avoid div by 0. - const float miter_scale_factor = ImMin(1000.f, 1.f / (1.0f + cos_theta_clamped)); + const float miter_scale_factor = ImMin(1000.0f, 1.0f / (1.0f + cos_theta_clamped)); const float miter_offset_x = (n0.x + n1.x) * miter_scale_factor * half_aa; const float miter_offset_y = (n0.y + n1.y) * miter_scale_factor * half_aa; @@ -1548,7 +1556,7 @@ void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_coun const float cos_theta = n0.x * n1.x + n0.y * n1.y; // miter offset formula is derived here: https://www.angusj.com/clipper2/Docs/Trigonometry.htm const float cos_theta_clamped = ImMax(IM_POLYLINE_MITER_ANGLE_LIMIT, cos_theta); // Avoid div by 0. - const float miter_scale_factor = ImMin(1000.f, 1.f / (1.0f + cos_theta_clamped)); + const float miter_scale_factor = ImMin(1000.0f, 1.0f / (1.0f + cos_theta_clamped)); float miter_offset_x = (n0.x + n1.x) * miter_scale_factor; float miter_offset_y = (n0.y + n1.y) * miter_scale_factor; @@ -2091,6 +2099,7 @@ void ImDrawList::AddLineH(float min_x, float max_x, float y, ImU32 col, float th } else { + // FIXME-POLYLINE: Implement square caps AddRectFilled(ImVec2(min_x, y), ImVec2(max_x, y + thickness), col); } } @@ -2122,6 +2131,7 @@ void ImDrawList::AddLineV(float x, float min_y, float max_y, ImU32 col, float th } else { + // FIXME-POLYLINE: Implement square caps AddRectFilled(ImVec2(x, min_y), ImVec2(x + thickness, max_y), col); } } @@ -2452,6 +2462,7 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl PathStroke(col, thickness, ImDrawFlags_Closed | ImDrawFlags_MiterOnly | ImDrawFlags_StrokeInside); #else + // textured_round_corners const bool is_truncated = _FringeScaleIsInteger && ImIsTruncated4(p_min.x, p_min.y, p_max.x, p_max.y) && ImIsTruncated4(rounding, thickness, 0.0f, 0.0f); if ((stroke_pos == ImDrawFlags_StrokeInside || stroke_pos == ImDrawFlags_StrokeOutside || stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) && is_truncated) { @@ -2770,8 +2781,7 @@ void ImDrawList::AddCircleFilled(const ImVec2& center, float radius, ImU32 col, return; if (screen_diameter < 1.0f) { - const float alpha = screen_diameter; - col = ImGui::GetColorU32(col, alpha); + col = ImAlphaMultiply(col, screen_diameter); radius = _FringeScale * 0.5f; } @@ -2817,7 +2827,6 @@ void ImDrawList::AddNgon(const ImVec2& center, float radius, ImU32 col, int num_ outer_radius = radius; else if (stroke_pos == ImDrawFlags_StrokeOutside) outer_radius = radius + miter_thickness; - if (outer_radius < (miter_thickness + _FringeScale)) { // The polygon has collapsed into a filled polygon. @@ -2842,8 +2851,7 @@ void ImDrawList::AddNgonFilled(const ImVec2& center, float radius, ImU32 col, in return; if (screen_diameter < 1.0f) { - const float alpha = screen_diameter; - col = ImGui::GetColorU32(col, alpha); + col = ImAlphaMultiply(col, screen_diameter); radius = _FringeScale * 0.5f; }