UI: Rounded rectangles from precomputed atlas corners

UIAtlas generates an antialiased white corner for each radius in
ROUND_RECT_RADII (8, 16, 24, 32 UI units) and each style (filled, and
thin/medium/thick outlines from ROUND_RECT_THICKNESS) at the atlas DPI.
DrawBuffer::DrawRoundRect mirrors the corner into all four corners and
stretches the corner's last texel row/column along the edges, so a
round rect is 9 quads (8 for outlines).

Other radii use the nearest corner, scaled. The LogoScreen temporarily
draws some for testing.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-10-24 01:52:34 +02:00
1 parent 42bb9b134b
commit 110fb7fd6a
5 files changed
+182 -2

No files matched your search

+78
View File
@@ -429,6 +429,84 @@ void DrawBuffer::DrawImage4Grid(ImageID atlas_image, float x1, float y1, float x
DrawTexRect(xb, yb, x2, y2, um, vm, u2, v2, color);
}
const char *RoundRectCornerImageName(int radiusIndex, RoundRectStyle style) {
// Generated from the indices, so the tables in the header are all there is to edit.
struct Names {
char names[ROUND_RECT_RADIUS_COUNT][(int)RoundRectStyle::COUNT][16];
Names() {
for (int r = 0; r < ROUND_RECT_RADIUS_COUNT; r++) {
for (int s = 0; s < (int)RoundRectStyle::COUNT; s++) {
snprintf(names[r][s], sizeof(names[r][s]), "I_RRECT_%d_%d", r, s);
}
}
}
};
static const Names names;
return names.names[radiusIndex][(int)style];
}
void DrawBuffer::DrawRoundRect(float x1, float y1, float x2, float y2, float radius, Color color, RoundRectStyle style) {
if (!atlas || x2 <= x1 || y2 <= y1) {
return;
}
if (roundRectAtlasImages_ != atlas->images) {
for (int r = 0; r < ROUND_RECT_RADIUS_COUNT; r++) {
for (int s = 0; s < (int)RoundRectStyle::COUNT; s++) {
roundRectCorners_[r][s] = atlas->getImage(ImageID(RoundRectCornerImageName(r, (RoundRectStyle)s)));
}
}
roundRectAtlasImages_ = atlas->images;
}
int radiusIndex = 0;
for (int i = 1; i < ROUND_RECT_RADIUS_COUNT; i++) {
if (fabsf(ROUND_RECT_RADII[i] - radius) < fabsf(ROUND_RECT_RADII[radiusIndex] - radius)) {
radiusIndex = i;
}
}
const AtlasImage *image = roundRectCorners_[radiusIndex][(int)style];
if (!image) {
return;
}
const bool filled = style == RoundRectStyle::Filled;
const float dpiScale = atlas->dpiScale;
const float r = ROUND_RECT_RADII[radiusIndex] * dpiScale;
const int center = RoundRectCornerCenter(r);
// The corner quad runs from the center of texel 0 to the center of the last texel, which holds
// the straight edge profile that gets stretched along the sides. Shrink it to fit small rects.
float scale = std::max(radius, 0.0f) / ROUND_RECT_RADII[radiusIndex];
const float maxScale = std::min(x2 - x1, y2 - y1) * dpiScale / (2.0f * (r + 0.5f));
scale = std::min(scale, maxScale);
if (scale <= 0.0f) {
if (filled) {
DrawImageCenterTexel(ImageID("I_SOLIDWHITE"), x1, y1, x2, y2, color);
}
return;
}
const float texel = scale / dpiScale;
const float outside = (center - r - 0.5f) * texel; // From the rect edge out to texel 0's center.
const float inside = (r + 0.5f) * texel; // From the rect edge in to the last texel's center.
const float xs[4] = { x1 - outside, x1 + inside, x2 - inside, x2 + outside };
const float ys[4] = { y1 - outside, y1 + inside, y2 - inside, y2 + outside };
const float us[4] = { image->u1, image->u2, image->u2, image->u1 };
const float vs[4] = { image->v1, image->v2, image->v2, image->v1 };
for (int y = 0; y < 3; y++) {
if (ys[y + 1] <= ys[y]) {
continue;
}
for (int x = 0; x < 3; x++) {
if (xs[x + 1] <= xs[x] || (x == 1 && y == 1 && !filled)) {
continue;
}
DrawTexRect(xs[x], ys[y], xs[x + 1], ys[y + 1], us[x], vs[y], us[x + 1], vs[y + 1], color);
}
}
}
void DrawBuffer::DrawImage2GridH(ImageID atlas_image, float x1, float y1, float x2, Color color, float corner_scale) {
const AtlasImage *image = atlas->getImage(atlas_image);
float um = (image->u1 + image->u2) * 0.5f;