DrawList: Textured Round Corners: first version. (1962)

This commit is contained in:
Mikko Mononen
2026-04-27 23:39:17 +02:00
committed by ocornut
parent 0935ae6bba
commit ba66bdab22
3 changed files with 163 additions and 5 deletions

View File

@@ -3323,6 +3323,10 @@ struct ImGuiSelectionExternalStorage
#define IM_DRAWLIST_TEX_LINES_WIDTH_MAX (32)
#endif
#ifndef IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX
#define IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX (16)
#endif
// ImDrawIdx: vertex index. [Compile-time configurable type]
// - To use 16-bit indices + allow large meshes: backend need to set 'io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset' and handle ImDrawCmd::VtxOffset (recommended).
// - To use 32-bit indices: override with '#define ImDrawIdx unsigned int' in your imconfig.h file.
@@ -3489,6 +3493,7 @@ struct ImDrawList
ImVector<ImU8> _CallbacksDataBuf; // [Internal]
float _FringeScale; // [Internal] anti-alias fringe is scaled by this value, this helps to keep things sharp while zooming at vertex buffer content.
float _InvFringeScale; // [internal] 1.0 / _FringeScale // FIXME: Consider renaming to _PixelDensity.
bool _FringeScaleIsInteger; // [Internal] true if _FringeScale is a whole number, used to select fast path for rendering
const char* _OwnerName; // Pointer to owner window's name for debugging
// If you want to create ImDrawList instances, pass them ImGui::GetDrawListSharedData().
@@ -3633,6 +3638,7 @@ struct ImDrawList
IMGUI_API int _CalcCircleAutoSegmentCount(float radius) const;
IMGUI_API void _PathArcToFastEx(const ImVec2& center, float radius, int a_min_sample, int a_max_sample, int a_step);
IMGUI_API void _PathArcToN(const ImVec2& center, float radius, float a_min, float a_max, int num_segments);
IMGUI_API void _AddMirrored9Slice(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float r, ImVec4 tex_uvs, ImDrawFlags flags);
};
// All draw data to render a Dear ImGui frame
@@ -3993,7 +3999,8 @@ struct ImFontAtlas
ImVec2 TexUvWhitePixel; // Texture coordinates to a white pixel. May change as new texture gets created.
ImVector<ImFont*> Fonts; // Hold all the fonts returned by AddFont*. Fonts[0] is the default font upon calling ImGui::NewFrame(), use ImGui::PushFont()/PopFont() to change the current font.
ImVector<ImFontConfig> Sources; // Source/configuration data
ImVec4 TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1]; // UVs for baked anti-aliased lines
ImVec4 TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1]; // UVs for baked anti-aliased lines
ImVec4 TexUvCornerFills[IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX]; // UVs for baked anti-aliased corners
int TexNextUniqueID; // Next value to be stored in TexData->UniqueID
int FontNextUniqueID; // Next value to be stored in ImFont->FontID
ImVector<ImDrawListSharedData*> DrawListSharedDatas; // List of users for this atlas. Typically one per Dear ImGui context.

View File

@@ -731,6 +731,7 @@ void ImDrawList::_SetPixelDensity(float pixel_density)
IM_ASSERT(pixel_density > 0.0f);
_FringeScale = 1.0f / pixel_density;
_InvFringeScale = pixel_density;
_FringeScaleIsInteger = ImIsTruncated(_FringeScale);
}
// Reserve space for a number of vertices and indices.
@@ -1609,14 +1610,84 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl
PathStroke(col, thickness, ImDrawFlags_Closed);
}
void ImDrawList::_AddMirrored9Slice(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float r, ImVec4 tex_uvs, ImDrawFlags flags)
{
// 9-slice with corner mirroring
const ImVec2 uv_tl(tex_uvs.x, tex_uvs.y);
const ImVec2 uv_tr(tex_uvs.z, tex_uvs.y);
const ImVec2 uv_br(tex_uvs.z, tex_uvs.w);
const ImVec2 uv_bl(tex_uvs.x, tex_uvs.w);
PrimReserve(9 * 2 * 3, 4 * 4);
const float r_tl = (flags & ImDrawFlags_RoundCornersTopLeft) ? r : 0.0f;
const float r_tr = (flags & ImDrawFlags_RoundCornersTopRight) ? r : 0.0f;
const float r_br = (flags & ImDrawFlags_RoundCornersBottomRight) ? r : 0.0f;
const float r_bl = (flags & ImDrawFlags_RoundCornersBottomLeft) ? r : 0.0f;
ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx;
PrimWriteVtx(ImVec2(p_min.x, p_min.y), uv_tl, col);
PrimWriteVtx(ImVec2(p_min.x + r_tl, p_min.y), uv_tr, col);
PrimWriteVtx(ImVec2(p_max.x - r_tr, p_min.y), uv_tr, col);
PrimWriteVtx(ImVec2(p_max.x, p_min.y), uv_tl, col);
PrimWriteVtx(ImVec2(p_min.x, p_min.y + r_tl), uv_bl, col);
PrimWriteVtx(ImVec2(p_min.x + r_tl, p_min.y + r_tl), uv_br, col);
PrimWriteVtx(ImVec2(p_max.x - r_tr, p_min.y + r_tr), uv_br, col);
PrimWriteVtx(ImVec2(p_max.x, p_min.y + r_tr), uv_bl, col);
PrimWriteVtx(ImVec2(p_min.x, p_max.y - r_bl), uv_bl, col);
PrimWriteVtx(ImVec2(p_min.x + r_bl, p_max.y - r_bl), uv_br, col);
PrimWriteVtx(ImVec2(p_max.x - r_br, p_max.y - r_br), uv_br, col);
PrimWriteVtx(ImVec2(p_max.x, p_max.y - r_br), uv_bl, col);
PrimWriteVtx(ImVec2(p_min.x, p_max.y), uv_tl, col);
PrimWriteVtx(ImVec2(p_min.x + r_bl, p_max.y), uv_tr, col);
PrimWriteVtx(ImVec2(p_max.x - r_br, p_max.y ), uv_tr, col);
PrimWriteVtx(ImVec2(p_max.x, p_max.y), uv_tl, col);
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
PrimWriteIdx(idx+4); PrimWriteIdx(idx+0); PrimWriteIdx(idx+1);
PrimWriteIdx(idx+4); PrimWriteIdx(idx+1); PrimWriteIdx(idx+5);
idx++;
}
idx++;
}
}
void ImDrawList::AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, ImDrawFlags flags)
{
if ((col & IM_COL32_A_MASK) == 0)
return;
if (rounding < 0.5f || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone)
if (ImIsTruncated4(p_min.x, p_min.y, p_max.x, p_min.y) && ImIsTruncated(rounding))
{
PrimReserve(6, 4);
PrimRect(p_min, p_max, col);
if ((flags & ImDrawFlags_RoundCornersMask_) == 0)
flags |= ImDrawFlags_RoundCornersAll;
rounding = ImMin(rounding, ImFabs(p_max.x - p_min.x) * (((flags & ImDrawFlags_RoundCornersTop) == ImDrawFlags_RoundCornersTop) || ((flags & ImDrawFlags_RoundCornersBottom) == ImDrawFlags_RoundCornersBottom) ? 0.5f : 1.0f) - 1.0f);
rounding = ImMin(rounding, ImFabs(p_max.y - p_min.y) * (((flags & ImDrawFlags_RoundCornersLeft) == ImDrawFlags_RoundCornersLeft) || ((flags & ImDrawFlags_RoundCornersRight) == ImDrawFlags_RoundCornersRight) ? 0.5f : 1.0f) - 1.0f);
const int s_rounding = (int)rounding;
if (s_rounding <= 0 || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone)
{
PrimReserve(6, 4);
PrimRect(p_min, p_max, col);
}
else if (s_rounding <= IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX)
{
ImVec4 tex_uvs = _Data->TexUvCornerFills[s_rounding - 1];
_AddMirrored9Slice(p_min, p_max, col, ImMax(2.0f, (float)s_rounding), tex_uvs, flags);
}
else
{
PathRect(p_min, p_max, (float)s_rounding, flags);
PathFillConvex(col);
}
}
else
{
@@ -3760,6 +3831,82 @@ static void ImFontAtlasBuildUpdateTexDataBasic(ImFontAtlas* atlas)
atlas->TexUvWhitePixel = ImVec2((r.x + 0.5f) * atlas->TexUvScale.x, (r.y + 0.5f) * atlas->TexUvScale.y);
}
static ImU8 SampleCorner(float x, float y, float cx, float cy, float r)
{
const float dx = ImMin(x, cx) - cx;
const float dy = ImMin(y, cy) - cy;
const float d = ImSqrt(dx*dx + dy*dy);
const float aa_width = 1.0f;
return (ImU8)(ImClamp(1.0f - (d - r + aa_width * 0.5f) / aa_width, 0.0f, 1.0f) * 255.0f);
}
static void ImFontAtlasBuildUpdateTexDataCorners(ImFontAtlas* atlas)
{
// Pack and store identifier so we can refresh UV coordinates on texture resize.
ImTextureData* tex = atlas->TexData;
ImFontAtlasBuilder* builder = atlas->Builder;
ImFontAtlasRect r;
bool add_and_draw = atlas->GetCustomRect(builder->PackIdCornersTexData, &r) == false;
if (add_and_draw)
{
ImVec2i pack_size(0, 0);
for (int i = 0; i < IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX; i++)
{
pack_size.x += ImMax(2, i+1) + 2;
pack_size.y = ImMax(pack_size.y, (i+1) + 2);
}
builder->PackIdCornersTexData = atlas->AddCustomRect(pack_size.x, pack_size.y, &r);
IM_ASSERT(builder->PackIdCornersTexData != ImFontAtlasRectId_Invalid);
}
int x = r.x;
int y = r.y;
for (int n = 0; n < IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX; n++) // +1 because of the zero-width row
{
const int s = ImMax(2, n+1); // We need at least 2px so that stretching has solid color.
const int w = s + 2;
const int h = s + 2;
// Corner circle
const float cx = 1.0f + (float)(n+1);
const float cy = 1.0f + (float)(n+1);
const float rad = (float)(n+1);
IM_ASSERT((x + w) <= (r.x + r.w) && (y + h) <= (r.y + r.h)); // Make sure we're inside the texture bounds before we start writing pixels
// Write each slice
if (add_and_draw && tex->Format == ImTextureFormat_Alpha8)
{
ImU8* write_ptr = (ImU8*)tex->GetPixelsAt(x, y);
const int pitch = tex->Width;
for (int ly = 0; ly < h; ly++)
{
for (int lx = 0; lx < h; lx++)
write_ptr[lx] = SampleCorner((float)lx + 0.5f, (float)ly + 0.5f, cx, cy, rad);
write_ptr += pitch;
}
}
else if (add_and_draw && tex->Format == ImTextureFormat_RGBA32)
{
ImU32* write_ptr = (ImU32*)(void*)tex->GetPixelsAt(x, y);
const int pitch = tex->Width;
for (int ly = 0; ly < h; ly++)
{
for (int lx = 0; lx < h; lx++)
write_ptr[lx] = IM_COL32(255, 255, 255, SampleCorner((float)lx + 0.5f, (float)ly + 0.5f, cx, cy, rad));
write_ptr += pitch;
}
}
// Refresh UV coordinates
ImVec2 uv0 = ImVec2((float)(x + 1), (float)(y + 1)) * atlas->TexUvScale;
ImVec2 uv1 = ImVec2((float)(x + 1 + s), (float)(y + 1 + s)) * atlas->TexUvScale;
atlas->TexUvCornerFills[n] = ImVec4(uv0.x, uv0.y, uv1.x, uv1.y);
x += w;
}
}
static void ImFontAtlasBuildUpdateTexDataLines(ImFontAtlas* atlas)
{
if (atlas->Flags & ImFontAtlasFlags_NoBakedLines)
@@ -4195,6 +4342,7 @@ void ImFontAtlasUpdateDrawListsSharedData(ImFontAtlas* atlas)
{
shared_data->TexUvWhitePixel = atlas->TexUvWhitePixel;
shared_data->TexUvLines = atlas->TexUvLines;
shared_data->TexUvCornerFills = atlas->TexUvCornerFills;
}
}
@@ -4213,6 +4361,7 @@ static void ImFontAtlasBuildUpdateTexData(ImFontAtlas* atlas)
{
ImFontAtlasBuildUpdateTexDataBasic(atlas);
ImFontAtlasBuildUpdateTexDataLines(atlas);
ImFontAtlasBuildUpdateTexDataCorners(atlas);
}
// Create a new texture, discard previous one

View File

@@ -919,6 +919,7 @@ struct IMGUI_API ImDrawListSharedData
{
ImVec2 TexUvWhitePixel; // UV of white pixel in the atlas (== FontAtlas->TexUvWhitePixel)
const ImVec4* TexUvLines; // UV of anti-aliased lines in the atlas (== FontAtlas->TexUvLines)
const ImVec4* TexUvCornerFills; // UV of rounded corner (== FontAtlas->TexUvCornerFills)
ImFontAtlas* FontAtlas; // Current font atlas
ImFont* Font; // Current font (used for simplified AddText overload)
float FontSize; // Current font size (used for for simplified AddText overload)
@@ -4324,8 +4325,9 @@ struct ImFontAtlasBuilder
// Custom rectangle identifiers
ImFontAtlasRectId PackIdMouseCursors; // White pixel + mouse cursors. Also happen to be fallback in case of packing failure.
ImFontAtlasRectId PackIdLinesTexData;
ImFontAtlasRectId PackIdCornersTexData;
ImFontAtlasBuilder() { memset((void*)this, 0, sizeof(*this)); FrameCount = -1; RectsIndexFreeListStart = -1; PackIdMouseCursors = PackIdLinesTexData = -1; }
ImFontAtlasBuilder() { memset((void*)this, 0, sizeof(*this)); FrameCount = -1; RectsIndexFreeListStart = -1; PackIdMouseCursors = PackIdLinesTexData = PackIdCornersTexData = -1; }
};
IMGUI_API void ImFontAtlasBuildInit(ImFontAtlas* atlas);