diff options
| author | Danilo M. <danix@danix.xyz> | 2026-09-16 19:26:07 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-09-16 19:26:07 +0200 |
| commit | 9c8157beb67018f5ecc94917d067b5c96aa8c328 (patch) | |
| tree | 3b668151e5387a125dd3788ea97a768fe0d4fcb0 | |
| parent | bea9b84b0f299d6dc913399a58652b4b2f04424d (diff) | |
| download | conky-theme-udt-9c8157beb67018f5ecc94917d067b5c96aa8c328.tar.gz conky-theme-udt-9c8157beb67018f5ecc94917d067b5c96aa8c328.zip | |
feat: add Cairo card primitives
Rounded-rect card with a hairline border, text helpers with measured
alignment, and font selection.
Cairo's font API stops at BOLD, so the Black face is reached by family
name instead of weight: at size 90, '13' measures 100px as Noto Sans
Black against 94px as Bold, confirming the heavier face is selected.
The card radius is clamped to half the shorter side, because a larger
radius makes the corner arcs overlap into a bowtie.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
| -rw-r--r-- | lib/card.lua | 86 |
1 files changed, 86 insertions, 0 deletions
diff --git a/lib/card.lua b/lib/card.lua new file mode 100644 index 0000000..320d3af --- /dev/null +++ b/lib/card.lua @@ -0,0 +1,86 @@ +-- Cairo drawing primitives shared by every widget. +-- +-- A widget is handed a rect and these helpers; it never computes its own +-- position and never touches the palette directly. + +local M = {} + +-- Cairo's font API tops out at CAIRO_FONT_WEIGHT_BOLD, so a Black face cannot +-- be requested by weight. Passing the family name that fontconfig registers for +-- it ("Noto Sans Black") with NORMAL weight does select it: measured at size +-- 90, "13" is 100.0px wide as Black against 94.0px as Noto Sans Bold. +M.FONT_MONO = 'Inconsolata Nerd Font' +M.FONT_UI = 'Noto Sans' +M.FONT_HEAVY = 'Noto Sans Black' + +function M.font(cr, family, size, bold) + cairo_select_font_face(cr, family, CAIRO_FONT_SLANT_NORMAL, + bold and CAIRO_FONT_WEIGHT_BOLD or CAIRO_FONT_WEIGHT_NORMAL) + cairo_set_font_size(cr, size) +end + +function M.rgba(cr, c, alpha) + cairo_set_source_rgba(cr, c[1], c[2], c[3], alpha or c[4] or 1) +end + +-- Text at (x, y), where y is the BASELINE, not the top of the glyphs. +function M.text(cr, x, y, s) + cairo_move_to(cr, x, y) + cairo_show_text(cr, s) +end + +-- Measured width and height of a string under the current font. +function M.measure(cr, s) + local e = cairo_text_extents_t:create() + cairo_text_extents(cr, s, e) + return e.width, e.height +end + +-- Right-aligned text: x is the RIGHT edge. +function M.text_right(cr, x, y, s) + local w = M.measure(cr, s) + M.text(cr, x - w, y, s) +end + +-- A rounded-rectangle path. Does not paint; the caller fills or strokes, so one +-- path can serve both a fill and its border. +function M.rounded_path(cr, x, y, w, h, r) + -- Clamp the radius: a radius over half the shorter side makes the arcs + -- overlap and Cairo draws a pinched, bowtie-looking shape. + local m = math.min(w, h) / 2 + if r > m then r = m end + cairo_new_path(cr) + cairo_arc(cr, x + w - r, y + r, r, -math.pi / 2, 0) + cairo_arc(cr, x + w - r, y + h - r, r, 0, math.pi / 2) + cairo_arc(cr, x + r, y + h - r, r, math.pi / 2, math.pi) + cairo_arc(cr, x + r, y + r, r, math.pi, math.pi * 1.5) + cairo_close_path(cr) +end + +-- A card: filled rounded rect with a hairline border, matching the mockups. +-- Returns the inner rect so a widget can lay out against padded bounds. +function M.card(cr, rect, colors, pad) + pad = pad or 18 + M.rounded_path(cr, rect.x, rect.y, rect.w, rect.h, 14) + M.rgba(cr, colors.surface, 0.55) + cairo_fill_preserve(cr) + -- 1px hairline. Cairo strokes astride the path, so a width of 1 on an integer + -- coordinate straddles two pixel rows and renders as a soft 2px line; the + -- cards read as thin outlines in the mockup, so keep it sub-pixel-crisp by + -- stroking at 1 and accepting the AA rather than offsetting by 0.5, which + -- would misalign the fill. + M.rgba(cr, colors.border, 0.9) + cairo_set_line_width(cr, 1) + cairo_stroke(cr) + return { x = rect.x + pad, y = rect.y + pad, + w = rect.w - pad * 2, h = rect.h - pad * 2 } +end + +-- A small-caps section label, as used across both mockups. +function M.label(cr, x, y, s, colors) + M.font(cr, M.FONT_MONO, 13, false) + M.rgba(cr, colors.label) + M.text(cr, x, y, s:upper()) +end + +return M |
