From 989dcf395be7280240fc501ba1f07f75bacf91a3 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Wed, 30 Sep 2026 22:33:15 -0300 Subject: The cheatsheet draws mouse buttons as a mouse, keys as chips, and pairs as tables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #btn and #chord draw a mouse (left button, wheel, right button) with the pressed part filled; the wheel is a narrow slot on a paper halo, hollow or filled, so it never reads as a button. A chord is the held button, then for each click the held one and that one down, with arrows between; #btn(.., shift: true) adds a ⇧. HTML gets a span with an aria-label. A key is one soft pill with no border: a combination (Ctrl-w) reads Ctrl + w and a sequence (g g, SPC f c) g › g, separators muted inside it; #keys("h", "j", ...) sets alternatives as separate pills. The drift test reads every key a #keys call names. #pairs sets key | meaning content as a two-column table on alternating tints; split: 2 sets the leader map as two such tables side by side. The mouse chords get their own table: cut, paste, copy (B1-B2 then B1-B3, one B1 hold, as mouse.zig says), 2-1 with an argument, 2-3 cancelling. The A4 page draws its own title and drops the content's level-1 heading. docs/typ/README is the function contract for content files. Co-Authored-By: Claude Opus 5.5 --- docs/typ/style.typ | 91 +++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 77 insertions(+), 14 deletions(-) (limited to 'docs/typ/style.typ') diff --git a/docs/typ/style.typ b/docs/typ/style.typ index 7cb05e23..64d96064 100644 --- a/docs/typ/style.typ +++ b/docs/typ/style.typ @@ -18,6 +18,7 @@ str: rgb("#34743d"), num: rgb("#9a5810"), rule: rgb("#95baa2"), + mouse: rgb("#8a3a00"), ) #let mono = "DejaVu Sans Mono" @@ -36,26 +37,72 @@ #let span(class, body) = html.elem("span", attrs: (class: class), body) #let m(s, fill: palette.ink) = text(font: mono, size: 0.92em, fill: fill, s) -/// A key or a sequence of keys, one cap per space-separated chord. +/// A key: one press (`x`, `Ctrl-w`, `Shift-Esc`), or a sequence pressed one +/// after another, written space-separated (`g g`, `SPC f c`). Either is ONE +/// pill: a combination's parts joined by a muted +, a sequence's steps by a +/// muted › (`Ctrl-w h` reads Ctrl + w › h). #let key(k) = context if html-out() { - k.split(" ").map(t => html.elem("kbd", t)).join(" ") + html.elem("kbd", k.split(" ").map(t => html.elem("kbd", t)).join(" ")) } else { - k.split(" ").map(t => box( - fill: white, stroke: 0.4pt + palette.rule, radius: 1.5pt, - inset: (x: 1.6pt, y: 0pt), outset: (y: 1.4pt), m(t), - )).join(h(1pt)) -} - -/// A mouse button. -#let btn(b) = context if html-out() { span("btn", b) } else { + let muted(c) = text(fill: palette.grip.transparentize(25%), size: 0.85em, c) + let press(t) = (if t.len() > 1 and t.contains("-") { t.split("-") } else { (t,) }).join(muted[+]) box( - fill: palette.col-grip, radius: 2pt, inset: (x: 1.6pt, y: 0pt), outset: (y: 1.4pt), - text(font: mono, size: 0.85em, weight: "bold", fill: white, b), + fill: palette.grip.transparentize(84%), radius: 2pt, + inset: (x: 1.4pt, y: 0pt), outset: (y: 1.5pt), + text(font: mono, size: 0.92em, fill: palette.ink, + k.split(" ").map(press).join(muted[#h(1pt)›#h(1pt)])), ) } -/// A chord: the first button held, the others clicked. -#let chord(..bs) = bs.pos().map(btn).join(text(fill: palette.col-grip)[--]) +/// Alternatives: separate keys listed together, each its own command. +#let keys(..ks) = context ks.pos().map(key).join(if html-out() [ ] else { h(0.25em) }) + +#let button-names = ("B1": "left button", "B2": "wheel", "B3": "right button") + +/// A mouse, about cap height: left button, wheel, right button. The parts +/// in `pressed` ("B1", "B2", "B3") are filled. +#let mouse(pressed) = { + let (w, h, top) = (0.84em, 1.2em, 0.56em) + let ink = palette.mouse + let s = 0.7pt + ink + let on(b) = if b in pressed { ink } else { none } + box(width: w, height: h, baseline: 0.24em, { + place(rect(width: w / 2, height: top, radius: (top-left: w / 2), fill: on("B1"), stroke: s)) + place(dx: w / 2, rect(width: w / 2, height: top, radius: (top-right: w / 2), fill: on("B3"), stroke: s)) + place(dy: top, rect(width: w, height: h - top, radius: (bottom: w / 2), stroke: s)) + // the wheel: a narrow tall slot over the buttons' seam, on a paper + // halo so a filled wheel stands apart from the button outlines + let (ww, wh, halo) = (0.26em, 0.44em, 0.07em) + let (wx, wy) = ((w - ww) / 2, 0.06em) + place(dx: wx - halo, dy: wy - halo / 2, rect( + width: ww + 2 * halo, height: wh + halo, radius: ww, fill: palette.paper)) + place(dx: wx, dy: wy, rect( + width: ww, height: wh, radius: ww / 2, stroke: 0.5pt + ink, + fill: if "B2" in pressed { ink } else { palette.paper }, + )) + }) +} + +/// A mouse button; `shift: true` for the button with Shift held. +#let btn(b, shift: false) = context if html-out() { + let label = (if shift { "shift " } else { "" }) + button-names.at(b) + html.elem("span", attrs: (class: "btn", aria-label: label, title: label), (if shift { "⇧" } else { "" }) + b) +} else { + if shift { text(size: 0.8em, fill: palette.col-grip)[⇧] } + mouse((b,)) +} + +/// A chord: the first button held, each next one clicked while it is. +/// Drawn as the held button, then for each click the held one and that one +/// down, with arrows between. +#let chord(..bs) = context if html-out() { + let label = bs.pos().map(b => button-names.at(b)).join(" then ") + html.elem("span", attrs: (class: "chord", aria-label: label, title: label), bs.pos().join("-")) +} else { + let (held, ..clicks) = bs.pos() + let arrow = text(size: 0.75em, fill: palette.col-grip)[#h(0.5pt)›#h(0.5pt)] + ((mouse((held,)),) + clicks.map(c => mouse((held, c)))).join(arrow) +} /// A tag word or builtin, as it sits in a tag. #let word(w) = context if html-out() { span("word", raw(w)) } else { @@ -99,3 +146,19 @@ text(fill: palette.num)[→#m(to, fill: palette.num)] } } + +/// Pairs: a thing and what it means, as a two-column table, key column +/// first, rows on alternating tints. `split: 2` sets the rows as that many +/// tables side by side (on paper; HTML gets one table). +#let pairs(split: 1, ..cells) = { + let rows = cells.pos().chunks(2) + let one(rows) = table( + columns: (auto, 1fr), stroke: none, inset: (x: 2pt, y: 1.6pt), + fill: (_, y) => if calc.even(y) { palette.tag-bg.transparentize(35%) }, + ..rows.flatten(), + ) + context if html-out() or split == 1 { one(rows) } else { + let n = calc.ceil(rows.len() / split) + grid(columns: (1fr,) * split, column-gutter: 2pt, ..rows.chunks(n).map(one)) + } +} -- cgit v1.3