aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-09-16 19:26:07 +0200
committerDanilo M. <danix@danix.xyz>2026-09-16 19:26:07 +0200
commit9c8157beb67018f5ecc94917d067b5c96aa8c328 (patch)
tree3b668151e5387a125dd3788ea97a768fe0d4fcb0
parentbea9b84b0f299d6dc913399a58652b4b2f04424d (diff)
downloadconky-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.lua86
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