diff options
| -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 |
