makepad/widgets/sheets/themes/cyberpunk/widgets.splash

2744 lines
143 KiB
Text

use mod.prelude.widgets_internal.*
// Cyberpunk: night-city street tech, after the Amp app's cyberpunk style. The
// window's ground is the hull of a machine: one continuous plate of satin
// polished purple-grey steel where the UI sits, under a cyan and a magenta
// light, framed at the window's edges by a raised band of machinery (THE
// HULL, below). A raised surface is a gunmetal plate cut with hard chamfers
// -- the top-left and bottom-right corners cut deep, the other two clipped --
// and edge-lit by a neon tube that runs cyan at the top into magenta at the
// foot, broken in a few places, with warning tape wrapped round its deep
// bottom-right cut. A button is a key in the same cut: a gunmetal cap with a
// holographic foil sheen and a neon pipe just inside its lip. Fields and
// screens are black glass with a cyan hairline; a screen's bezel carries a
// magenta-to-cyan tube and its glass a static chroma split. Meters are wedge
// ladders of slanted cells, cyan into magenta into warning yellow. Cyan is
// live (rest, hover, focus, a value), magenta is on or held, yellow warns.
// Static: nothing here reads a clock, so an idle window redraws nothing.
// ---------------------------------------------------------------- helpers
// One device pixel in points.
let cp_px = fn(dpi: float) -> float {
return 1.0 / max(dpi, 0.5)
}
// An edge rounded to a whole device pixel in window space, handed back in
// box space: a hairline on it lands on one pixel row.
let cp_snap = fn(v: float, o: float, px: float) -> float {
return Finish.snap(o + v, px) - o
}
// Distance from p to the segment a..b, in points.
let cp_seg = fn(p: vec2, a: vec2, b: vec2) -> float {
let pa = p - a
let ba = b - a
let t = clamp(dot(pa, ba) / max(dot(ba, ba), 0.0001), 0.0, 1.0)
return length(pa - ba * t)
}
// THE CUT. A box centred on `c`, half size `h`, its top-left and bottom-right
// corners cut `big` points along each edge and the other two `small`, both
// held inside the box.
let cp_cut = fn(p: vec2, c: vec2, h: vec2, big: float, small: float) -> float {
let m = max(min(h.x, h.y) - 0.25, 0.0)
let kb = min(big, m)
let ks = min(small, m)
return Finish.sd_notch(p, c, h, vec4(kb, ks, kb, ks))
}
// The shadow a part in THE CUT throws: its own chamfered outline moved by
// `off` and softened over `blur` either side of its edge, 0..1. A shadow
// cast from a plain box would show dark corners past the cuts.
let cp_cut_shade = fn(p: vec2, c: vec2, h: vec2, big: float, small: float, off: vec2, blur: float) -> float {
return 1.0 - smoothstep(-blur, blur, cp_cut(p - off, c, h, big, small))
}
// The neon: electric cyan at 0 into hot magenta at 1.
let cp_neon = fn(t: float) -> vec3 {
return mix(vec3(0.08, 0.94, 1.0), vec3(1.0, 0.12, 0.72), clamp(t, 0.0, 1.0))
}
// A neon line `at` points inside an edge (`d` its distance): a white-hot core
// about `w` wide in its ink, and a halo of the ink falling over `reach`.
// Light, to add to a face.
let cp_tube = fn(d: float, at: float, w: float, reach: float, ink: vec3) -> vec3 {
let x = abs(d + at)
let line = 1.0 - smoothstep(w * 0.3, w, x)
let halo = exp(-x / max(reach, 0.1)) * 0.35
return mix(ink, vec3(1.0, 1.0, 1.0), line * 0.45) * line * 0.95 + ink * halo
}
// Focus: a cyan hairline one device pixel out from the edge and a short
// breath of it on the ground.
let cp_focus = fn(dst: vec4, d: float, px: float, on: float) -> vec4 {
let out = 1.0 - Finish.cover(d - px * 2.0, px)
let ring = Finish.ring_out(d, px, px * 2.0, px) + Finish.glow(d - px * 2.0, 1.2) * 0.4 * out
return Finish.over(dst, vec4(0.08, 0.94, 1.0, 1.0) * clamp(ring, 0.0, 1.0) * on)
}
// A lit mark (a check, a dash): a white-hot core along the stroke's middle,
// the ink at its edges and a halo of the ink round it. `dk` is the distance
// to the stroke's edge, `w` its half width.
let cp_mark = fn(dst: vec4, dk: float, w: float, px: float, on: float, ink: vec3) -> vec4 {
let x = max(dk, 0.0)
let g = (0.35 * exp(-(x * x) / (2.0 * 0.6 * 0.6)) + 0.18 * exp(-(x * x) / (2.0 * 1.6 * 1.6))) * (1.0 - Finish.cover(dk, px))
let res = Finish.over(dst, vec4(ink, 1.0) * g * on)
let core = mix(ink, vec3(1.0, 1.0, 1.0), (1.0 - smoothstep(0.1 * w, 0.85 * w, dk + w)) * 0.8)
return Finish.over(res, vec4(core, 1.0) * Finish.cover(dk, px) * on)
}
// ---------------------------------------------------------------- ground
// THE HULL. The window's ground is one machine's hull: a single continuous
// plate of steel across the middle of the window, where the UI is mounted,
// framed by a raised band of machinery along the top, under the caption,
// and by a thin lip down the sides and along the foot, where an app's panes
// scroll, so no content ever passes over the frame. The plate is satin
// polished steel in a purple-grey tone, sunk 18 points in the frame under
// its shadow, swirled with polishing arcs, and the neon is reflected in it
// as soft blocks of tube lines -- cyan on the left, magenta on the right --
// placed by the window's size, nothing in it repeating (`cp_ground`); just
// under the top band, where nothing scrolls, stand a group of deep slots
// and two small screws (`cp_edge_parts`). The two washes of the sheet's own
// light -- a broad cyan from above the upper left, a magenta from past the
// right edge (`cp_wash`) -- catch in glints on the edges that face them:
// the frame's bevels, the slots' rims, the screw heads and the frame's
// parts. The top band is 34 points deep, widening into chamfered blocks at
// its corners; the lips are 4 points (`cp_band`, `cp_opening`); where the
// plate meets it the frame rises on one clean chamfered lip, its crest lit,
// its facets lit where they face the light, and it lays a soft shadow on
// the plate under its upper and left edges. On the top band: stepped
// sub-plates and seams, vent and grille slots, bolts, octagonal lens ports
// with rings, hazard bars, barcodes and print, traces with 45 degree bends,
// and the sheet's own cyan and magenta in LEDs, the lit edge of the bevel
// round the plate taking the washes in place of a drawn line, with a rare
// red or amber light. It all stays dark, so the controls stay the brightest
// thing. The light comes from the upper left, mostly from above (`cp_lt`).
// Nothing reads the clock.
let cp_lt = fn() -> vec2 {
return vec2(-0.5, -0.866)
}
// The cyan, the magenta and the two warning lights, picked by `t` 0..1:
// cyan and magenta mostly, red one time in eight, amber one in twelve.
let cp_lamp_ink = fn(t: float) -> vec3 {
if t < 0.48 {
return vec3(0.08, 0.94, 1.0)
}
if t < 0.835 {
return vec3(1.0, 0.12, 0.72)
}
if t < 0.92 {
return vec3(1.0, 0.16, 0.10)
}
return vec3(1.0, 0.62, 0.10)
}
// A box centred on `c`, half size `h`, its corners rounded by `r`.
let cp_rbox = fn(p: vec2, c: vec2, h: vec2, r: float) -> float {
let rr = clamp(r, 0.0, min(h.x, h.y))
let q = abs(p - c) - h + vec2(rr, rr)
return min(max(q.x, q.y), 0.0) + length(max(q, vec2(0.0, 0.0))) - rr
}
// A part standing on a face of colour `col`, its own face `top`, from its
// distance `d` and the distances `dl` and `ds` a bevel `bw` toward and away
// from the light: the facet toward the light lit, the one away shaded, a
// short shadow on the face round its foot.
let cp_raise = fn(col: vec3, top: vec3, d: float, dl: float, ds: float, bw: float, px: float) -> vec3 {
let a = Finish.cover(d, px)
let lit = clamp(dl / bw, 0.0, 1.0)
let shade = clamp(ds / bw, 0.0, 1.0)
let cast = clamp(0.5 - dl / bw, 0.0, 1.0)
let up = top + vec3(0.080, 0.085, 0.097) * lit - top * 0.45 * shade
return mix(col * (1.0 - 0.42 * cast), up, a)
}
// A hole sunk into a face of colour `col`, the same three distances: its
// floor dark, the face's shadow in under its upper wall, a lit lip along
// its lower wall.
let cp_sink = fn(col: vec3, d: float, dl: float, ds: float, bw: float, px: float) -> vec3 {
let a = Finish.cover(d, px)
let sh = clamp(0.5 + dl / bw, 0.0, 1.0)
let lit = clamp(ds / bw, 0.0, 1.0)
let bot = col * 0.40 * (1.0 - 0.45 * sh) + vec3(0.045, 0.048, 0.055) * lit
return mix(col, bot, a)
}
// A row of `n` vent slots from `o` (the block's top left), `len` points
// long and 2.4 wide on a 5 point pitch, standing upright in a row across
// (`across` 1) or lying in a stack down. Only the slot nearest the pixel is
// worked out.
let cp_vents = fn(col: vec3, p: vec2, o: vec2, n: float, len: float, across: float, px: float) -> vec3 {
let q = p - o
let u = mix(q.y, q.x, across)
let i = clamp(floor(u / 5.0), 0.0, n - 1.0)
let cu = (i + 0.5) * 5.0
let c = o + mix(vec2(len * 0.5, cu), vec2(cu, len * 0.5), across)
let h = mix(vec2(len * 0.5, 1.2), vec2(1.2, len * 0.5), across)
let d = cp_rbox(p, c, h, 1.2)
let dl = cp_rbox(p + cp_lt() * 1.2, c, h, 1.2)
let ds = cp_rbox(p - cp_lt() * 1.2, c, h, 1.2)
return cp_sink(col, d, dl, ds, 1.2, px)
}
// A bolt at `c`: a domed head in a countersink.
let cp_bolt = fn(col: vec3, p: vec2, c: vec2, px: float) -> vec3 {
let q = p - c
var res = cp_sink(col, length(q) - 2.7, length(q + cp_lt() * 0.9) - 2.7, length(q - cp_lt() * 0.9) - 2.7, 0.9, px)
return cp_raise(res, col * 1.2, length(q) - 1.8, length(q + cp_lt() * 1.0) - 1.8, length(q - cp_lt() * 1.0) - 1.8, 1.0, px)
}
// A port at `c` of bore radius `r`: an octagonal collar standing on the
// face, the bore sunk in it with a groove ringed round it, a lens of dark
// glass in its middle with a crisp spark of the light, and with `glow` a
// small lamp of `ink` at the lens's heart.
let cp_port = fn(col: vec3, p: vec2, c: vec2, r: float, ink: vec3, glow: float, px: float) -> vec3 {
let q = p - c
let rr = length(q)
let hc = vec2(r + 5.0, r + 5.0)
let kc = (r + 5.0) * 0.58
var res = cp_raise(col, col * 1.08, Finish.sd_chamfer(p, c, hc, kc), Finish.sd_chamfer(p + cp_lt() * 1.4, c, hc, kc), Finish.sd_chamfer(p - cp_lt() * 1.4, c, hc, kc), 1.4, px)
res = cp_sink(res, rr - r, length(q + cp_lt() * 1.6) - r, length(q - cp_lt() * 1.6) - r, 1.6, px)
let groove = Finish.cover(abs(rr - r * 0.76) - px * 0.5, px)
res = mix(res, res * 0.45, groove)
let rl = r * 0.48
res = cp_raise(res, vec3(0.030, 0.032, 0.040), rr - rl, length(q + cp_lt() * 1.0) - rl, length(q - cp_lt() * 1.0) - rl, 1.0, px)
let sp = Finish.cover(length(q + vec2(0.36, 0.44) * rl) - max(rl * 0.13, px * 0.7), px)
res = res + vec3(0.30, 0.32, 0.36) * sp * 0.55
return res + ink * (Finish.cover(rr - rl * 0.28, px) * 0.75 + exp(-rr / max(rl * 0.35, 0.5)) * 0.25) * glow
}
// Hazard bars in the box `lo`..`hi`: bars of `ink` 2.5 points wide on a 5
// point pitch, leaning at 45 degrees, on a darker ground.
let cp_hull_bars = fn(col: vec3, p: vec2, lo: vec2, hi: vec2, ink: vec3, px: float) -> vec3 {
let box = Finish.cover(cp_rbox(p, (lo + hi) * 0.5, (hi - lo) * 0.5, 0.0), px)
let t = (p.x - p.y) * 0.70710678 / 5.0
let bar = Finish.cover((abs(fract(t) - 0.5) - 0.25) * 5.0, px)
return mix(col, mix(col * 0.55, ink, bar), box)
}
// A barcode printed in the box `lo`..`hi`: bars on a 2 point pitch, each
// 0.6 to 1.2 points wide or left out, hashed from its column.
let cp_barcode = fn(col: vec3, p: vec2, lo: vec2, hi: vec2, ink: vec3, px: float) -> vec3 {
let box = cp_rbox(p, (lo + hi) * 0.5, (hi - lo) * 0.5, 0.0)
let x = p.x - lo.x
let ci = floor(x / 2.0)
let hb = Finish.hash(vec2(ci * 1.37 + lo.y * 0.11, lo.x * 0.29 + 3.0))
let bw = 0.6 + 0.6 * hb
let xb = x - ci * 2.0
let d = max(max(0.4 - xb, xb - 0.4 - bw), box) + step(hb, 0.22) * 10.0
return mix(col, ink, Finish.cover(d, px))
}
// Print: `rows` lines of tiny glyphs from `o`, on a 3.4 point leading, each
// glyph a dash 1.3 points wide on a 1.9 point pitch, spaces and line ends
// hashed, as small type reads from a little way off.
let cp_print = fn(col: vec3, p: vec2, o: vec2, len: float, rows: float, ink: vec3, px: float) -> vec3 {
let q = p - o
let ri = clamp(floor(q.y / 3.4), 0.0, rows - 1.0)
let gi = floor(q.x / 1.9)
let line = len * (0.45 + 0.55 * Finish.hash(vec2(ri * 7.1 + o.x * 0.13, o.y * 0.17)))
let gap = step(Finish.hash(vec2(gi * 1.31 + ri * 17.7, o.x * 0.07 + o.y * 0.03)), 0.2)
let gx = q.x - gi * 1.9
let gy = q.y - ri * 3.4
let d = max(max(max(0.3 - gx, gx - 1.6), max(0.6 - gy, gy - 2.1)), max(-q.x, q.x - line)) + gap * 10.0
return mix(col, ink, Finish.cover(d, px))
}
// A crosshair at `c`: two arms 7 points across, one device pixel wide on
// the pixel's middle.
let cp_cross = fn(col: vec3, p: vec2, c: vec2, ink: vec3, px: float) -> vec3 {
let cc = (floor(c / px) + vec2(0.5, 0.5)) * px
let q = abs(p - cc)
let d = min(max(q.x - 3.5, q.y - px * 0.5), max(q.y - 3.5, q.x - px * 0.5))
return mix(col, ink, Finish.cover(d, px))
}
// A trace from `a` along to `b`, across a 45 degree bend to `c` and along
// to `e`: cut into the face as a groove with a lit lower lip, or with `lit`
// a thin line of `ink`, a ring at its start and a pad at its end.
let cp_trace = fn(col: vec3, p: vec2, a: vec2, b: vec2, c: vec2, e: vec2, ink: vec3, lit: float, px: float) -> vec3 {
let d = min(min(cp_seg(p, a, b), cp_seg(p, b, c)), cp_seg(p, c, e))
let pl = p + cp_lt() * 0.8
let ps = p - cp_lt() * 0.8
let dl = min(min(cp_seg(pl, a, b), cp_seg(pl, b, c)), cp_seg(pl, c, e))
let ds = min(min(cp_seg(ps, a, b), cp_seg(ps, b, c)), cp_seg(ps, c, e))
let ring = abs(length(p - a) - 2.2) - 0.6
let pad = length(p - e) - 1.6
let groove = cp_sink(col, min(min(d - 0.6, ring), pad), dl - 0.6, ds - 0.6, 0.8, px)
let core = Finish.cover(min(d - px * 0.5, pad), px)
let halo = exp(-max(min(d, pad), 0.0) / 1.6) * (1.0 - core)
return groove + ink * (core * 0.5 + halo * 0.16) * lit
}
// A row of `n` LEDs from `o`, `stride` apart, each a lamp of `ink` in a
// small housing with a halo round it.
let cp_leds = fn(col: vec3, p: vec2, o: vec2, n: float, stride: vec2, ink: vec3, px: float) -> vec3 {
let i = clamp(floor(dot(p - o, stride) / max(dot(stride, stride), 0.01) + 0.5), 0.0, n - 1.0)
let c = o + stride * i
let q = p - c
let r = length(q)
let res = cp_sink(col, r - 1.7, length(q + cp_lt() * 0.7) - 1.7, length(q - cp_lt() * 0.7) - 1.7, 0.7, px)
let lamp = Finish.cover(r - 1.0, px)
return res + ink * (lamp * 0.8 + exp(-r / 1.5) * 0.3 * (1.0 - lamp))
}
// An engraved line from `a` along to `b`, across a 45 degree jog to `c`
// and along to `e`: a groove a point wide with a lit lower lip.
let cp_groove = fn(col: vec3, p: vec2, a: vec2, b: vec2, c: vec2, e: vec2, px: float) -> vec3 {
let pl = p + cp_lt() * 0.7
let ps = p - cp_lt() * 0.7
let d = min(min(cp_seg(p, a, b), cp_seg(p, b, c)), cp_seg(p, c, e)) - 0.5
let dl = min(min(cp_seg(pl, a, b), cp_seg(pl, b, c)), cp_seg(pl, c, e)) - 0.5
let ds = min(min(cp_seg(ps, a, b), cp_seg(ps, b, c)), cp_seg(ps, c, e)) - 0.5
return cp_sink(col, d, dl, ds, 0.7, px)
}
// THE WASHES: the two lights on the hull in a window of `size`, at `w`.
// The cyan comes broad and soft from above the window's upper left third,
// the magenta from just past its right edge a little below the middle,
// each falling away with distance, hotter close in. .xy the cyan's
// strength and the magenta's.
let cp_wash = fn(w: vec2, size: vec2) -> vec2 {
let diag = length(size)
let qc = (w - vec2(size.x * 0.30, -size.y * 0.14)) / (diag * 0.36)
let qm = (w - vec2(size.x * 1.07, size.y * 0.56)) / (diag * 0.24)
return vec2(exp(-dot(qc, qc)) + 0.5 * exp(-dot(qc, qc) * 6.0), exp(-dot(qm, qm)) + 0.9 * exp(-dot(qm, qm) * 4.0))
}
// The unit directions from `w` toward the cyan light (.xy) and the
// magenta (.zw).
let cp_wash_dir = fn(w: vec2, size: vec2) -> vec4 {
let dc = normalize(vec2(size.x * 0.30, -size.y * 0.14) - w)
let dm = normalize(vec2(size.x * 1.07, size.y * 0.56) - w)
return vec4(dc.x, dc.y, dm.x, dm.y)
}
// The light a facet of metal `base` takes when it leans the way `n` (a unit
// vector in the window, or zero for flat): the white light from the upper
// left lights it or shades it, with a sharp glint where it faces it full,
// and each wash lights it in its own colour as it leans toward that light,
// a broad glow and a sharp glint (`lw` their strengths, `ld` their
// directions).
let cp_facet = fn(base: vec3, n: vec2, lw: vec2, ld: vec4) -> vec3 {
let k = dot(n, cp_lt())
var col = base * (1.0 + 0.55 * max(k, 0.0) - 0.55 * max(-k, 0.0)) + vec3(0.06, 0.064, 0.072) * max(k, 0.0) + vec3(0.16, 0.17, 0.19) * pow(max(k, 0.0), 6.0)
let kc = max(dot(n, ld.xy), 0.0)
let km = max(dot(n, ld.zw), 0.0)
col = col + vec3(0.20, 0.85, 1.0) * min(lw.x, 1.2) * (kc * 0.22 + pow(kc, 6.0) * 0.40)
return col + vec3(1.0, 0.22, 0.75) * min(lw.y, 1.4) * (km * 0.22 + pow(km, 6.0) * 0.40)
}
// One polishing swirl from the cell `id` (of `cell` points), if it has one:
// a sector of fine concentric scratches round a centre near the cell's
// middle, fading at its ends and edges. Its strength at `p`, 0..1.
let cp_swirl1 = fn(p: vec2, id: vec2, cell: float, salt: float, px: float) -> float {
let h1 = Finish.hash(id + vec2(salt * 7.7, salt * 3.1))
if h1 > 0.6 {
return 0.0
}
let c = (id + vec2(0.5, 0.5) + (vec2(Finish.hash(id + vec2(1.3, salt)), Finish.hash(id + vec2(salt, 2.9))) - vec2(0.5, 0.5)) * 0.5) * cell
let q = p - c
let r = length(q)
let r0 = cell * (0.12 + 0.28 * Finish.hash(id + vec2(4.4, salt)))
let wid = cell * (0.12 + 0.23 * Finish.hash(id + vec2(salt, 6.6)))
if r < r0 || r > r0 + wid {
return 0.0
}
let a = atan2(q.y, q.x) + 3.14159
let a0 = Finish.hash(id + vec2(8.8, salt)) * 6.2832
let span = 0.9 + 1.8 * Finish.hash(id + vec2(salt, 9.9))
let da = fract((a - a0) / 6.2832) * 6.2832
if da > span {
return 0.0
}
let fade = sin(da / span * 3.14159) * sin((r - r0) / wid * 3.14159)
let ri = floor(r / 1.4)
let li = Finish.hash(vec2(ri * 1.37, h1 * 91.0 + salt))
let line = Finish.cover(abs(r - (ri + 0.5) * 1.4) - px * 0.4, px) * step(0.45, li)
return line * fade * (0.4 + 0.6 * li)
}
// The swirls at `p`: from the cell it is in and its eight neighbours, so
// arcs cross cell edges and no cell shows.
let cp_swirls = fn(p: vec2, cell: float, salt: float, px: float) -> float {
let id = floor(p / cell)
var m = cp_swirl1(p, id, cell, salt, px)
m = max(m, cp_swirl1(p, id + vec2(-1.0, -1.0), cell, salt, px))
m = max(m, cp_swirl1(p, id + vec2(0.0, -1.0), cell, salt, px))
m = max(m, cp_swirl1(p, id + vec2(1.0, -1.0), cell, salt, px))
m = max(m, cp_swirl1(p, id + vec2(-1.0, 0.0), cell, salt, px))
m = max(m, cp_swirl1(p, id + vec2(1.0, 0.0), cell, salt, px))
m = max(m, cp_swirl1(p, id + vec2(-1.0, 1.0), cell, salt, px))
m = max(m, cp_swirl1(p, id + vec2(0.0, 1.0), cell, salt, px))
return max(m, cp_swirl1(p, id + vec2(1.0, 1.0), cell, salt, px))
}
// A neon shape reflected in the metal: a soft-edged block round (`cx`,
// `cy`) of half size (`hx`, `hy`) in the window's 0..1 coordinates `uv`,
// made of horizontal tube lines `pitch` points apart, a little broken. 0..1.
let cp_reflection = fn(uv: vec2, p: vec2, cx: float, cy: float, hx: float, hy: float, pitch: float) -> float {
let qx = (uv.x - cx) / hx
let qy = (uv.y - cy) / hy
let soft = exp(-qx * qx * qx * qx - qy * qy * 1.6)
let line = pow(0.5 + 0.5 * cos(p.y / pitch * 6.2832), 3.0)
return soft * (0.55 + 0.45 * line) * (0.85 + 0.3 * Finish.noise(p / 23.0))
}
// The plate's metal at window point `w` in a window of `size`, after his
// purple-grey panel: satin polished steel in a purple-grey tone, a step
// darker than his. Cyan light spreads softly over it from the left and
// magenta from the right, and neon shapes are reflected in it as soft
// blocks of horizontal tube lines, placed by the window's size: cyan left
// of the middle, a cyan strip under the top right and a short one low on
// the left; magenta at the right and toward the lower right. Over it all
// swirled polishing marks -- arcs of fine concentric scratches round many
// centres, curving every way -- catch whatever light falls on them; a fine
// speckle and a few grime flecks. Nothing in it runs one way or repeats
// across the window. Also the frame's metal, a shade darker.
let cp_ground = fn(w: vec2, size: vec2, px: float) -> vec3 {
let uv = w / max(size, vec2(1.0, 1.0))
var col = vec3(0.300, 0.262, 0.322) * (0.94 + 0.12 * Finish.fbm(w / 160.0))
// The soft light from the sides.
let cl = exp(-uv.x / 0.30) * (0.7 + 0.3 * Finish.noise(w / 210.0 + vec2(3.0, 1.0)))
let cm = exp(-(1.0 - uv.x) / 0.27) * (0.7 + 0.3 * Finish.noise(w / 190.0 + vec2(7.0, 5.0)))
let lcol = vec3(0.02, 0.30, 0.36) * cl + vec3(0.54, -0.15, 0.35) * cm + vec3(-0.02, -0.04, 0.0) * exp(-(1.0 - uv.x) / 0.45)
col = col + lcol
// The reflected neon.
let rc = cp_reflection(uv, w, 0.235, 0.36, 0.08, 0.075, 9.0) * 0.85 + cp_reflection(uv, w, 0.80, 0.112, 0.12, 0.016, 7.0) * 0.8 + cp_reflection(uv, w, 0.30, 0.905, 0.06, 0.022, 8.0) * 0.6
let rm = cp_reflection(uv, w, 0.87, 0.60, 0.11, 0.07, 10.0) + cp_reflection(uv, w, 0.73, 0.875, 0.12, 0.028, 8.0) * 0.9
col = col + vec3(-0.02, 0.62, 0.75) * rc + vec3(0.66, -0.08, 0.47) * rm
// The swirls catch the light that is on them.
let sw = max(cp_swirls(w, 150.0, 1.0, px), cp_swirls(w + vec2(41.0, 77.0), 97.0, 2.0, px) * 0.8)
col = col + (vec3(0.10, 0.095, 0.11) + lcol * 1.2 + vec3(0.0, 0.5, 0.5) * rc + vec3(0.5, 0.22, 0.36) * rm) * sw * 0.55
// Speckle and a few grime flecks.
let q = w / px
let fl = smoothstep(0.72, 0.80, Finish.noise(q * 0.62 + vec2(1.7, 4.1)))
let fd = smoothstep(0.70, 0.76, Finish.noise(q * 0.55 + vec2(8.3, 0.9))) * smoothstep(0.45, 0.7, Finish.noise(w / 37.0))
col = col * (1.0 + 0.07 * fl - 0.12 * fd) * 0.58
let g = Finish.grain(q, 0.006)
return col + vec3(g, g, g)
}
// The frame's widths round a window of `size`: x left, y top, z right, w
// bottom. Only the top is a band of machinery, under the caption, where
// nothing scrolls; the sides and the foot, where an app's panes run to the
// window's edge and scroll, are a lip of 4 points, so no content ever
// passes over the frame.
let cp_band = fn(size: vec2) -> vec4 {
let s = clamp(min(size.x / 900.0, size.y / 600.0), 0.4, 1.0)
return vec4(4.0, 34.0 * s, 4.0, 4.0)
}
// The corner block nearest `p`, from its window corner inward: .xy its
// width and depth, .z the 45 degree cut on its inner corner. Only the top
// corners have one; at the foot it is empty.
let cp_block = fn(p: vec2, size: vec2, s: float) -> vec3 {
let top = 1.0 - step(size.y * 0.5, p.y)
return vec3(vec2(150.0, 50.0) * s * top, 16.0 * s * top)
}
// THE OPENING: the plate's signed distance (negative on the plate) at `p`
// in a window of `size`, its frame `band` wide, widened at each corner by
// its block.
let cp_opening = fn(p: vec2, size: vec2, band: vec4, s: float) -> float {
let lo = band.xy
let hi = size - band.zw
let d = Finish.sd_notch(p, (lo + hi) * 0.5, (hi - lo) * 0.5, vec4(0.0, 0.0, 0.0, 0.0))
let right = step(size.x * 0.5, p.x)
let down = step(size.y * 0.5, p.y)
let blk = cp_block(p, size, s)
let corner = vec2(right * size.x, down * size.y)
let dir = vec2(1.0 - 2.0 * right, 1.0 - 2.0 * down)
let far = corner + dir * blk.xy
let near = corner - dir * 40.0
let blo = min(near, far)
let bhi = max(near, far)
let kv = vec4(right * down, (1.0 - right) * down, (1.0 - right) * (1.0 - down), right * (1.0 - down)) * blk.z
return max(d, -Finish.sd_notch(p, (blo + bhi) * 0.5, (bhi - blo) * 0.5, kv))
}
// What stands in a top corner block: a stepped sub-plate, and on it vents,
// a barcode with print, a small port and bolts -- or, under the window's
// buttons on the right, only a row of LEDs and print low in the block --
// and a crosshair at its foot. Laid out from the window corner inward and
// mirrored into each top corner.
let cp_corner_parts = fn(col: vec3, p: vec2, size: vec2, s: float, deco: vec3, px: float) -> vec3 {
let right = step(size.x * 0.5, p.x)
let corner = vec2(right * size.x, 0.0)
let dir = vec2(1.0 - 2.0 * right, 1.0)
let blk = cp_block(p, size, s)
// The sub-plate, its inner corner cut as the block's is.
let sq0 = vec2(26.0, 7.0) * s
let sq1 = blk.xy - vec2(10.0, 9.0) * s
let sa = corner + dir * sq0
let sb = corner + dir * sq1
let sc = (sa + sb) * 0.5
let sh = abs(sb - sa) * 0.5
let kv = vec4(0.0, 0.0, 1.0 - right, right) * blk.z * 0.8
var res = cp_raise(col, col * 1.22, Finish.sd_notch(p, sc, sh, kv), Finish.sd_notch(p + cp_lt() * 1.4, sc, sh, kv), Finish.sd_notch(p - cp_lt() * 1.4, sc, sh, kv), 1.4, px)
if right > 0.5 {
res = cp_leds(res, p, corner + dir * vec2(34.0, 36.0) * s, 3.0, vec2(dir.x * 4.5, 0.0), cp_lamp_ink(Finish.hash(vec2(right, 7.7))), px)
res = cp_print(res, p, corner + vec2(-132.0, 34.0) * s, 76.0 * s, 2.0, deco, px)
} else {
let n = 6.0
let va = corner + dir * vec2(42.0 * s, 12.0 * s)
let vb = corner + dir * vec2(42.0 * s + n * 5.0, 36.0 * s)
res = cp_vents(res, p, min(va, vb), n, 24.0 * s, 1.0, px)
let ba = corner + dir * vec2(80.0, 13.0) * s
let bb = corner + dir * vec2(112.0, 22.0) * s
res = cp_barcode(res, p, min(ba, bb), max(ba, bb), deco, px)
res = cp_print(res, p, min(ba, bb) + vec2(0.0, 12.0 * s), 32.0 * s, 2.0, deco, px)
res = cp_port(res, p, corner + dir * vec2(125.0, 21.0) * s, 5.5 * s, cp_lamp_ink(Finish.hash(vec2(right, 7.7))), step(0.5, Finish.hash(vec2(right, 8.8))), px)
res = cp_bolt(res, p, corner + dir * vec2(32.0, 13.0) * s, px)
res = cp_bolt(res, p, corner + dir * vec2(32.0, 35.0) * s, px)
}
return cp_cross(res, p, corner + dir * vec2(10.0 * s, blk.y - 12.0 * s), deco, px)
}
// What stands along the top band between the corner blocks, in cells of
// 120 points, each by its hash: a seam across the band, and a row of vents,
// a stepped sub-plate with a bolt at each end, hazard bars, a trace with a
// 45 degree bend, a barcode with print, a row of LEDs, a pair of bolts,
// grooves jogging together, or nothing; the middle is left plain under the
// window's title.
let cp_strip_parts = fn(col: vec3, p: vec2, size: vec2, band: vec4, s: float, deco: vec3, px: float) -> vec3 {
// The run between the top corner blocks; the sides and the foot are
// a bare lip.
if p.y > band.y + 1.0 {
return col
}
let u = p.x
let t = band.y
let ua = 150.0 * s
let ub = size.x - 150.0 * s
let pitch = 120.0 * s
let ci = floor((u - ua) / pitch)
let u0 = ua + ci * pitch
if u < ua || u0 + pitch > ub {
return col
}
var h = Finish.hash(vec2(ci * 1.37, 5.0))
let h2 = Finish.hash(vec2(ci * 0.91, 9.0))
// The middle of the top is left plain under the window's title.
if abs(u0 + pitch * 0.5 - size.x * 0.5) < 130.0 * s {
h = 1.0
}
let us = t - 10.0 * s
let v0 = 4.0 * s
var res = col
if h2 < 0.5 {
res = cp_groove(res, p, vec2(u0, -2.0), vec2(u0, -2.0), vec2(u0, -2.0), vec2(u0, t - 6.0 * s), px)
}
if h < 0.18 {
let n = 8.0
let len = us * 0.7
let a = vec2(u0 + 30.0 * s, v0 + us * 0.15)
let b = vec2(u0 + 30.0 * s + n * 5.0, v0 + us * 0.85)
return cp_vents(res, p, min(a, b), n, len, 1.0, px)
}
if h < 0.32 {
let a = vec2(u0 + 10.0 * s, v0)
let b = vec2(u0 + pitch - 10.0 * s, v0 + us)
let c = (a + b) * 0.5
let hh = abs(b - a) * 0.5
let k = min(min(hh.x, hh.y), 6.0 * s)
let kk = vec4(k, k, k, k)
res = cp_raise(res, col * 1.22, Finish.sd_notch(p, c, hh, kk), Finish.sd_notch(p + cp_lt() * 1.2, c, hh, kk), Finish.sd_notch(p - cp_lt() * 1.2, c, hh, kk), 1.2, px)
res = cp_bolt(res, p, vec2(u0 + 22.0 * s, v0 + us * 0.5), px)
return cp_bolt(res, p, vec2(u0 + pitch - 22.0 * s, v0 + us * 0.5), px)
}
if h < 0.44 {
let a = vec2(u0 + 26.0 * s, v0 + us * 0.5 - min(4.5 * s, us * 0.35))
let b = vec2(u0 + 70.0 * s, v0 + us * 0.5 + min(4.5 * s, us * 0.35))
return cp_hull_bars(res, p, min(a, b), max(a, b), mix(deco, vec3(0.36, 0.10, 0.27), step(0.6, h2)), px)
}
if h < 0.56 {
let j = us * 0.5
let va = v0 + us * 0.25
let ink = mix(vec3(0.08, 0.94, 1.0), vec3(1.0, 0.12, 0.72), step(0.5, h2))
let a = vec2(u0 + 12.0 * s, va)
let b = vec2(u0 + 50.0 * s, va)
let c = vec2(u0 + 50.0 * s + j, va + j)
let e = vec2(u0 + pitch - 14.0 * s, va + j)
return cp_trace(res, p, a, b, c, e, ink, step(0.55, Finish.hash(vec2(ci * 2.3, 1.0))), px)
}
let along = vec2(4.5, 0.0)
if h < 0.68 {
if us > 16.0 {
let a = vec2(u0 + 30.0 * s, v0 + us * 0.5 - 6.0 * s)
let b = vec2(u0 + 66.0 * s, v0 + us * 0.5 + 2.0 * s)
let lo = min(a, b)
res = cp_barcode(res, p, lo, max(a, b), deco, px)
return cp_print(res, p, lo + vec2(0.0, 10.0 * s), 36.0 * s, 1.0, deco, px)
}
return cp_leds(res, p, vec2(u0 + 40.0 * s, v0 + us * 0.5), 2.0, along, cp_lamp_ink(h2), px)
}
if h < 0.8 {
return cp_leds(res, p, vec2(u0 + 36.0 * s, v0 + us * 0.5), 3.0, along, cp_lamp_ink(h2), px)
}
if h < 0.9 {
res = cp_bolt(res, p, vec2(u0 + 14.0 * s, v0 + us * 0.5), px)
return cp_bolt(res, p, vec2(u0 + pitch - 14.0 * s, v0 + us * 0.5), px)
}
if h < 0.999 {
// Two or three grooves side by side along the strip, jogging
// together at 45 degrees.
let j = us * 0.35
let ga = u0 + 10.0 * s
let gb = u0 + pitch * 0.45
let ge = u0 + pitch - 10.0 * s
let gv = v0 + us * 0.15
let sp = max(2.5 * s, px * 3.0)
res = cp_groove(res, p, vec2(ga, gv), vec2(gb, gv), vec2(gb + j, gv + j), vec2(ge, gv + j), px)
res = cp_groove(res, p, vec2(ga, gv + sp), vec2(gb - sp * 0.4142, gv + sp), vec2(gb + j - sp * 0.4142, gv + j + sp), vec2(ge, gv + j + sp), px)
if h2 > 0.5 && us > 10.0 {
res = cp_groove(res, p, vec2(ga, gv + sp * 2.0), vec2(gb - sp * 0.8284, gv + sp * 2.0), vec2(gb + j - sp * 0.8284, gv + j + sp * 2.0), vec2(ge, gv + j + sp * 2.0), px)
}
}
return res
}
// DEPTH. A raised part throws a soft shadow down and
// to the right (away from the light at the upper left), its penumbra
// growing with its height, and darkens the floor at its foot; a recess takes
// the rim's shadow on its floor and darkens toward its walls; bevels and
// walls are lit by `cp_facet`, crests catch a glint.
// The glint on a crest facing `n`: a little light always, more where it
// faces the white light or a wash.
let cp_glint = fn(n: vec2, lw: vec2, ld: vec4) -> vec3 {
let k = max(dot(n, cp_lt()), 0.0)
let kc = max(dot(n, ld.xy), 0.0)
let km = max(dot(n, ld.zw), 0.0)
return vec3(0.040, 0.042, 0.048) + vec3(0.11, 0.115, 0.125) * k + vec3(0.20, 0.85, 1.0) * min(lw.x, 1.2) * (0.04 + 0.30 * kc) + vec3(1.0, 0.22, 0.75) * min(lw.y, 1.4) * (0.04 + 0.30 * km)
}
// A RAISED PART over `under`: its face `top`, `d` its distance (negative
// inside), `n` its outward unit normal, `h` its height, `bw` its bevel, and
// `dsh` its distance at p + cp_lt() * h, where its shadow falls.
let cp_lift = fn(under: vec3, top: vec3, d: float, n: vec2, h: float, bw: float, dsh: float, lw: vec2, ld: vec4, px: float) -> vec3 {
let pen = 0.45 * h + 0.6
let shadow = 1.0 - smoothstep(-pen, pen, dsh)
let ao = exp(-max(d, 0.0) / (0.6 * h + 1.0))
var col = under * (1.0 - 0.58 * shadow) * (1.0 - 0.30 * ao)
col = mix(col, cp_facet(top * 0.9, n * 0.85, lw, ld), Finish.cover(d, px))
col = mix(col, top, Finish.cover(d + bw, px))
return col + cp_glint(n, lw, ld) * Finish.cover(abs(d + bw) - px * 0.5, px)
}
// A RECESS in a face `face`: its floor `fl`, `d` its distance (negative
// inside), `n` its outward unit normal, `depth`, its wall `bw`, and `dsh`
// its distance at p + cp_lt() * depth: where that lies outside, the rim's
// shadow falls on the floor.
let cp_sunk = fn(face: vec3, fl: vec3, d: float, n: vec2, depth: float, bw: float, dsh: float, lw: vec2, ld: vec4, px: float) -> vec3 {
let pen = 0.4 * depth + 0.6
let shadow = smoothstep(-pen, pen, dsh)
let ao = exp(min(d + bw, 0.0) / (0.45 * depth + 0.8))
let floor_c = fl * (1.0 - 0.65 * shadow) * (1.0 - 0.45 * ao)
var col = mix(face, cp_facet(face * 0.62, -n * 0.85, lw, ld), Finish.cover(d, px))
col = mix(col, floor_c, Finish.cover(d + bw, px))
return col + cp_glint(-n, lw, ld) * Finish.cover(abs(d) - px * 0.5, px) * 0.6
}
// A group of `n` deep slots from `o` (its top left), each `len` long and
// `wid` wide with round ends, side by side on a `pitch`: upright in a row
// across (`across` 1) or lying in a stack down. Dark inside.
let cp_slots = fn(col: vec3, p: vec2, o: vec2, n: float, len: float, wid: float, pitch: float, across: float, lw: vec2, ld: vec4, px: float) -> vec3 {
let q = p - o
let u = mix(q.y, q.x, across)
let i = clamp(floor(u / pitch + 0.5 - wid * 0.5 / pitch), 0.0, n - 1.0)
let cu = i * pitch + wid * 0.5
let c = o + mix(vec2(len * 0.5, cu), vec2(cu, len * 0.5), across)
let h = mix(vec2(len * 0.5, wid * 0.5), vec2(wid * 0.5, len * 0.5), across)
let d = cp_rbox(p, c, h, wid * 0.5)
if d > 3.0 {
return col
}
let gx = cp_rbox(p + vec2(0.25, 0.0), c, h, wid * 0.5) - cp_rbox(p - vec2(0.25, 0.0), c, h, wid * 0.5)
let gy = cp_rbox(p + vec2(0.0, 0.25), c, h, wid * 0.5) - cp_rbox(p - vec2(0.0, 0.25), c, h, wid * 0.5)
let nn = normalize(vec2(gx, gy) + vec2(0.00001, 0.0))
return cp_sunk(col, col * 0.12, d, nn, 5.0, 0.9, cp_rbox(p + cp_lt() * 5.0, c, h, wid * 0.5), lw, ld, px)
}
// A screw head at `c`: a domed head standing in a countersink, throwing a
// small shadow into it, a slot across it at a turn of its own.
let cp_screw = fn(col: vec3, p: vec2, c: vec2, turn: float, lw: vec2, ld: vec4, px: float) -> vec3 {
let q = p - c
let r = length(q)
if r > 7.0 {
return col
}
let nq = q / max(r, 0.001)
var res = cp_sunk(col, col * 0.4, r - 3.4, nq, 1.6, 0.8, length(q + cp_lt() * 1.6) - 3.4, lw, ld, px)
let dome = cp_facet(col * 1.15, nq * clamp(r / 2.4, 0.0, 1.0) * 0.9, lw, ld)
res = cp_lift(res, dome, r - 2.4, nq, 1.6, 0.6, length(q + cp_lt() * 1.6) - 2.4, lw, ld, px)
let sd = vec2(cos(turn), sin(turn))
let slot = Finish.cover(abs(dot(q, vec2(-sd.y, sd.x))) - 0.35, px) * Finish.cover(abs(dot(q, sd)) - 1.7, px)
return mix(res, col * 0.25, slot)
}
// The plate's hardware, placed by the window's size, only in the strip just
// under the top band, where an app's panes do not scroll: a group of three
// short slots and two screws.
let cp_edge_parts = fn(col: vec3, p: vec2, size: vec2, band: vec4, lw: vec2, ld: vec4, px: float) -> vec3 {
let y = band.y + 11.0
if abs(p.y - y) > 9.0 {
return col
}
var res = cp_slots(col, p, vec2(floor(size.x * 0.47), y - 1.5), 3.0, 14.0, 3.0, 8.0, 1.0, lw, ld, px)
res = cp_screw(res, p, vec2(size.x * 0.36, y), 0.4, lw, ld, px)
return cp_screw(res, p, vec2(size.x * 0.63, y), 1.1, lw, ld, px)
}
// The hull at `p` in a window of `size`, `px` a device pixel in points:
// the plate sunk 18 points in the frame -- the frame's shadow falling in on
// it under the frame's upper and left edges and the plate darkening at the
// frame's foot -- the frame's inner wall rising from a dark crease, lit
// where it faces a light, its crest, then the frame and its machinery.
let cp_hull = fn(p: vec2, size: vec2, px: float) -> vec3 {
let band = cp_band(size)
let s = band.y / 34.0
let d = cp_opening(p, size, band, s)
let lw = cp_wash(p, size)
let ld = cp_wash_dir(p, size)
let depth = 18.0
let fw = 7.0 * s
let steel = cp_ground(p, size, px)
let metal = steel * 0.8
var col = metal
if d < fw {
col = cp_edge_parts(steel, p, size, band, lw, ld, px)
let pen = 0.4 * depth + 1.0
let fs = smoothstep(-pen, pen, cp_opening(p + cp_lt() * depth, size, band, s))
col = col * (1.0 - 0.62 * fs) * (1.0 - 0.38 * exp(min(d, 0.0) / (0.35 * depth)))
}
if d < -2.0 {
return col
}
let gx = cp_opening(p + vec2(0.5, 0.0), size, band, s) - cp_opening(p - vec2(0.5, 0.0), size, band, s)
let gy = cp_opening(p + vec2(0.0, 0.5), size, band, s) - cp_opening(p - vec2(0.0, 0.5), size, band, s)
let n = normalize(vec2(gx, gy) + vec2(0.00001, 0.0))
var frame = metal
let deco = vec3(0.140, 0.149, 0.168)
let right = step(size.x * 0.5, p.x)
let down = step(size.y * 0.5, p.y)
let blk = cp_block(p, size, s)
let qb = (p - vec2(right * size.x, down * size.y)) * vec2(1.0 - 2.0 * right, 1.0 - 2.0 * down)
if d > fw - px {
if qb.x < blk.x && qb.y < blk.y {
frame = cp_corner_parts(frame, p, size, s, deco, px)
} else {
frame = cp_strip_parts(frame, p, size, band, s, deco, px)
}
}
col = mix(col, col * 0.3, Finish.cover(abs(d) - px, px))
col = mix(col, cp_facet(metal * 0.75, -n * 0.9, lw, ld), Finish.cover(-d, px) * (1.0 - Finish.cover(fw - d, px)))
col = mix(col, frame, Finish.cover(fw - d, px))
col = col + cp_glint(-n, lw, ld) * Finish.cover(abs(d - fw) - px * 0.5, px)
// The frame catches the washes where it catches the light: its lit
// edges, facets, bolt heads and rims take the colour of the light there.
let on_frame = Finish.cover(fw - d, px)
let hl = max(dot(col - metal, vec3(0.3, 0.59, 0.11)), 0.0)
return col + (vec3(0.08, 0.80, 1.0) * lw.x + vec3(1.0, 0.10, 0.70) * lw.y) * (hl * 2.0 + 0.012) * on_frame
}
// ---------------------------------------------------------------- plates
// A RAISED PLATE: dark gunmetal brushed across, a shade lighter at the top,
// cut on the chamfer and edge-lit -- a hairline of neon on the lip, the tube
// 2.6 points inside it, cyan at the top running into magenta at the foot and
// broken in a few places, and a fainter line of the other ink inside that.
// Warning tape wraps the deep bottom-right cut (`tape` 1) and a row of vent
// slots stands near the top right (`vents` 1). It throws a soft shadow and a
// breath of its neon onto the ground. `o` is the box's window position, so
// neighbouring plates share one brushing. Premultiplied.
let cp_plate = fn(p: vec2, lo: vec2, hi: vec2, big: float, small: float, px: float, o: vec2, hover: float, tape: float, vents: float) -> vec4 {
let c = (lo + hi) * 0.5
let h = (hi - lo) * 0.5
let m = max(min(h.x, h.y) - 0.25, 0.0)
let kb = min(big, m)
let ks = min(small, m)
let d = Finish.sd_notch(p, c, h, vec4(kb, ks, kb, ks))
let a = Finish.cover(d, px)
let w = p + o
let uv = clamp((p - lo) / max(hi - lo, vec2(1.0, 1.0)), vec2(0.0, 0.0), vec2(1.0, 1.0))
let t = smoothstep(0.15, 0.85, uv.y * 0.8 + uv.x * 0.2)
let ink = cp_neon(t)
let ink2 = cp_neon(1.0 - t)
let gate = step(0.10, fract((p.x - lo.x + (p.y - lo.y) * 1.3) / 170.0))
// What it throws: a soft shadow under it and the neon's breath round it.
let sh = (1.0 - smoothstep(-4.0, 4.0, Finish.sd_notch(p - vec2(0.0, 3.0), c, h - vec2(1.0, 1.0), vec4(kb, ks, kb, ks)))) * 0.6
var res = vec4(0.0, 0.0, 0.0, sh * (1.0 - a))
res = Finish.over(res, vec4(ink, 1.0) * Finish.glow(d, 2.5) * (0.16 + 0.08 * hover) * gate * (1.0 - a))
// The face: gunmetal brushed in long streaks, lighter at the top.
let row = floor(w.y * 1.4)
let streak = Finish.noise(vec2(w.x * 0.01 + row * 7.13, row * 0.37))
var col = vec3(0.133, 0.155, 0.212) * (1.0 + (streak - 0.5) * 0.07) * (1.06 - uv.y * 0.12)
col = col + vec3(0.010, 0.012, 0.016) * hover
// Warning tape wrapped across the deep bottom-right cut: yellow and
// black, the black stripes upright in the window, chipped here and there.
let q = abs(p - c) - h
let cd = (q.x + q.y + kb) * 0.7071
let br = step(c.x, p.x) * step(c.y, p.y)
let t1 = max(kb * 0.33, 5.0)
let t0 = t1 + 6.5
let band = smoothstep(-t0 - 0.5, -t0 + 0.5, cd) * (1.0 - smoothstep(-t1 - 0.5, -t1 + 0.5, cd)) * br * tape
let sx = fract(w.x / 7.0)
let st = smoothstep(0.46, 0.54, sx) * (1.0 - smoothstep(0.94, 1.0, sx))
let chip = smoothstep(0.72, 0.8, Finish.noise(w * 0.3))
let tcol = mix(vec3(0.98, 0.84, 0.05) * (0.92 + 0.1 * Finish.hash(floor(w * 2.0))), vec3(0.04, 0.04, 0.045), st)
col = mix(col, tcol, band * (1.0 - chip * 0.75))
// Vent slots near the top right: short dark bars.
let vx = p.x - (hi.x - 58.0)
let vy = p.y - (lo.y + 7.0)
let fv = fract(vx / 5.0)
let slot = step(0.0, vx) * step(vx, 44.0) * step(0.2, fv) * step(fv, 0.62) * step(0.0, vy) * step(vy, 5.0) * vents
col = mix(col, vec3(0.01, 0.01, 0.015), slot * 0.9)
// The neon: the lip, the tube, and the inner line of the other ink.
col = col + ink * Finish.band(d, 0.0, px * 1.5, px) * 0.40 * gate
col = col + cp_tube(d, 2.6, 0.85, 2.6, ink) * gate * (0.9 + 0.25 * hover)
col = col + ink2 * (1.0 - smoothstep(0.2, 0.7, abs(d + 5.2))) * 0.30 * gate
return Finish.over(res, vec4(min(col, vec3(1.0, 1.0, 1.0)) * a, a))
}
// A RECESSED PANEL: a black well in the same cut with a faint HUD grid on a
// 16 point pitch, the surround's shade under its top edge, a hairline of cyan
// round its floor and a device pixel of light on the ground under its foot.
let cp_tray = fn(p: vec2, lo: vec2, hi: vec2, big: float, small: float, px: float) -> vec4 {
let c = (lo + hi) * 0.5
let h = (hi - lo) * 0.5
let d = cp_cut(p, c, h, big, small)
let a = Finish.cover(d, px)
let f = fract((p - lo) / vec2(16.0, 16.0))
let g = max(1.0 - smoothstep(0.0, 0.07, f.x), 1.0 - smoothstep(0.0, 0.07, f.y))
var col = vec3(0.051, 0.059, 0.078) + vec3(0.02, 0.07, 0.09) * g * 0.35
col = col * (1.0 - 0.45 * (1.0 - smoothstep(0.0, 8.0, p.y - lo.y)))
col = col + vec3(0.08, 0.94, 1.0) * (1.0 - smoothstep(0.2, 0.8, abs(d + 1.6))) * 0.28
let lip = Finish.ring_out(d, 0.0, px, px) * step(c.y, p.y) * 0.22
let res = vec4(0.40, 0.48, 0.56, 1.0) * lip
return Finish.over(res, vec4(col * a, a))
}
// BLACK GLASS: a field's or a slot's well in the cut, `fill` lit a little
// under its top edge, a hairline round it in `ink` paled toward white
// (`rim_a` its strength) and, with `glow`, the hairline's light on the glass
// inside.
let cp_glass = fn(p: vec2, lo: vec2, hi: vec2, big: float, small: float, px: float, fill: vec3, ink: vec3, rim_a: float, glow: float) -> vec4 {
let c = (lo + hi) * 0.5
let h = (hi - lo) * 0.5
let d = cp_cut(p, c, h, big, small)
let a = Finish.cover(d, px)
var col = fill * (1.0 - smoothstep(0.0, 7.0, p.y - lo.y) * 0.25)
let rim = 1.0 - smoothstep(0.3, 1.0, abs(d + 0.6))
col = mix(col, mix(ink, vec3(1.0, 1.0, 1.0), 0.28), rim * clamp(rim_a, 0.0, 1.0))
col = col + ink * exp(min(d, 0.0) / 4.0) * 0.22 * glow
var res = vec4(ink, 1.0) * Finish.glow(d, 1.6) * 0.22 * glow * (1.0 - a)
return Finish.over(res, vec4(min(col, vec3(1.0, 1.0, 1.0)) * a, a))
}
// ---------------------------------------------------------------- keys
// The holographic foil on a key: thin-film colours that shift with the
// window position `w`, strongest in a diagonal band across the face (`uv`),
// over faint brushing and a fine grating. `amt` scales the band.
let cp_foil = fn(base: vec3, w: vec2, uv: vec2, amt: float) -> vec3 {
let row = floor(w.y * 1.6)
let br = Finish.noise(vec2(w.x * 0.02 + row * 7.13, row * 0.37))
let col = base * (1.0 + (br - 0.5) * 0.10)
let t = (w.x * 0.8 + w.y) * 0.006 + Finish.noise(w * 0.04) * 0.5 + uv.x * 0.35
let ha = 0.5 + 0.5 * sin(t * 6.2832)
let hb = pow(0.5 + 0.5 * sin(t * 10.7 + 1.3), 6.0)
var film = mix(vec3(0.05, 0.85, 1.0), vec3(0.95, 0.15, 0.85), ha)
film = mix(film, vec3(1.0, 0.85, 0.35), hb * 0.7)
let b = uv.x + uv.y * 0.6 - 0.75
let band = (exp(-(b * b) / 0.03) * 0.55 + 0.07) * amt
let grating = 0.5 + 0.5 * sin((w.x - w.y) * 2.4)
return col * (1.0 - band * 0.45) + film * band * (0.18 + 0.36 * grating)
}
// A KEY: a gunmetal cap in the cut wearing the foil, and a neon pipe just
// inside its lip, in the `rest` ink at rest and brighter under the hand. On
// (`lit`) or held (`down`) the pipe takes the `on` ink at full strength, the
// face darkens under a glow from the rim and the ink lights the ground round
// it; held, the key also loses its shadow, pressed home. `charge` lights the
// rest ink the same way without the key being on (a primary's call). `face`
// 0 draws the pipe alone over a breath of black glass (an outline key);
// `foil` scales the sheen; `raise` 0 seats the key on the ground with its
// contact shadow only. Focus rings it in cyan. Premultiplied.
let cp_key = fn(p: vec2, lo: vec2, hi: vec2, big: float, small: float, px: float, o: vec2, hover: float, down: float, lit: float, focus: float, disabled: float, rest: vec3, on: vec3, charge: float, face: float, foil: float, raise: float) -> vec4 {
let c = (lo + hi) * 0.5
let h = (hi - lo) * 0.5
let d = cp_cut(p, c, h, big, small)
let a = Finish.cover(d, px)
let live = 1.0 - disabled
let up = (1.0 - down) * live * raise
let held = clamp(max(down, lit), 0.0, 1.0) * live
let w = p + o
let uv = clamp((p - lo) / max(hi - lo, vec2(1.0, 1.0)), vec2(0.0, 0.0), vec2(1.0, 1.0))
let ink = mix(rest, on, held)
let lift = max(held, charge * live)
// What the key throws: a contact shadow, and a short drop while it is up.
let drop = cp_cut_shade(p, c, h - vec2(0.5, 0.5), big, small, vec2(0.0, 2.0), 2.2) * 0.5 * up * face
let contact = cp_cut_shade(p, c, h, big, small, vec2(0.0, px), px) * 0.6 * face
var res = vec4(0.0, 0.0, 0.0, (1.0 - (1.0 - drop) * (1.0 - contact)) * (1.0 - a))
let halo = (0.30 * lift + 0.10 * hover * live) * (1.0 - a)
res = Finish.over(res, vec4(ink, 1.0) * Finish.glow(d, 2.0) * halo)
// The face.
var col = cp_foil(vec3(0.128, 0.140, 0.178), w, uv, foil * live)
col = col * (1.0 - 0.12 * down * live) + vec3(0.012, 0.014, 0.018) * hover * live
col = col * (1.0 - 0.25 * lit * live) + on * 0.07 * lit * live + rest * 0.05 * charge * live
col = mix(col, vec3(0.082, 0.088, 0.104), disabled)
let fa = a * face
var top = vec4(col * fa, fa)
// Without a face, a breath of black glass behind the pipe.
top = Finish.over(vec4(0.02, 0.025, 0.035, 1.0) * a * 0.55 * (1.0 - face), top)
// The pipe and the glow it lays on the face.
let line = (1.0 - smoothstep(0.2, 0.8, abs(d + 1.2))) * step(d, 0.0)
let rim_glow = exp(min(d, 0.0) / 5.0) * lift * 0.45
let amt = mix(0.55 + 0.30 * hover, 1.05, lift) * live + 0.20 * disabled
let pink = mix(ink, vec3(0.30, 0.38, 0.44), disabled)
let la = clamp(line * amt, 0.0, 1.0) * a
top = vec4(top.rgb + pink * (line * amt + rim_glow) * a, max(top.a, la))
res = Finish.over(res, top)
return cp_focus(res, d, px, focus * live)
}
// Warning marks: four short bars of warning yellow leaning forward, standing
// inside the bottom-left cut, as the Amp app's critical keys carry them.
let cp_hazard = fn(dst: vec4, p: vec2, lo: vec2, hi: vec2, small: float, px: float, amt: float) -> vec4 {
let x0 = lo.x + small + 2.0
let y1 = hi.y - 2.5
let y0 = y1 - 3.5
let s = (p.x - x0) - (y1 - p.y) * 0.55
let g = fract(s / 2.2)
let bar = Finish.cover((abs(g - 0.5) - 0.27) * 2.2, px)
let inside = Finish.cover(max(-s, s - 8.6), px) * Finish.cover(max(y0 - p.y, p.y - y1), px)
return Finish.over(dst, vec4(0.86, 0.76, 0.10, 1.0) * bar * inside * amt * 0.9)
}
// The three-quarter arc a waiting button turns in place of its label.
let cp_spinner = fn(dst: vec4, p: vec2, c: vec2, rad: float, t: float, amt: float) -> vec4 {
let sdf = Sdf2d.viewport(p)
let a0 = t * 2.0 * PI
sdf.arc_round_caps(c.x, c.y, rad, a0, a0 + 1.5 * PI, 1.5)
sdf.fill(vec4(0.6, 0.98, 1.0, amt))
return Finish.over(dst, sdf.result)
}
// ---------------------------------------------------------------- buttons
// Every button is a key, 26 points on a 13 point line. Its shadow and glow
// fall outside the box, so the quad grows and nothing is paid in padding.
// The cut is read off the face's own border_radius (the deep cut is 2.8x
// it), so a use site that rounds a button tighter also cuts it tighter.
mod.widgets.ButtonFlat.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 5.0 bottom: 5.0}
mod.widgets.ButtonFlat.min_height = 26.0
mod.widgets.ButtonFlat.icon_walk.width = 14.0
mod.widgets.ButtonIcon.padding = mod.turtle.Inset{left: 7.0 right: 7.0 top: 5.0 bottom: 5.0}
mod.widgets.ButtonFlatIcon.padding = mod.turtle.Inset{left: 7.0 right: 7.0 top: 5.0 bottom: 5.0}
mod.widgets.ButtonFlatterIcon.padding = mod.turtle.Inset{left: 7.0 right: 7.0 top: 5.0 bottom: 5.0}
mod.widgets.ButtonXs.min_height = 0.0
mod.widgets.ButtonSm.min_height = 0.0
mod.widgets.ButtonFlat.draw_bg.border_radius = uniform(2.5)
mod.widgets.ButtonFlat.draw_bg.border_size = uniform(1.0)
mod.widgets.ButtonFlat.clip_x = false
mod.widgets.ButtonFlat.clip_y = false
mod.widgets.ButtonFlat.draw_bg.vertex = fn() {
let m = 8.0
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
// The key and its states, shared by every button template; `kind` picks the
// variant: 0 flat (a seated key, its foil quieter), 1 the key, 2 primary (charged cyan, the
// warning marks), 3 secondary (a quiet foil), 4 tertiary (a magenta pipe),
// 5 danger (charged red, the warning marks), 6 outline (the pipe alone),
// 7 flatter (nothing at rest, a seated key under the hand).
let cp_button = fn(pos: vec2, size: vec2, o: vec2, dpi: float, br: float, hover: float, down: float, disabled: float, focus: float, loading: float, anim: float, kind: float) -> vec4 {
let m = 8.0
let px = cp_px(dpi)
let p = pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let lo = vec2(cp_snap(0.0, o.x, px), cp_snap(0.0, o.y, px))
let hi = vec2(cp_snap(size.x, o.x, px), cp_snap(size.y, o.y, px))
let big = max(br, 0.0) * 2.8
let small = max(br, 0.0)
let cyan = vec3(0.08, 0.94, 1.0)
let mag = vec3(1.0, 0.12, 0.72)
var rest = cyan
var on = mag
var charge = 0.0
var face = 1.0
var foil = 1.0
var show = 1.0
var marks = 0.0
var raise = 1.0
if kind < 0.5 {
raise = 0.0
foil = 0.6
}
if kind > 6.5 {
show = clamp(max(hover, down) + focus, 0.0, 1.0) * 0.8
raise = 0.0
}
if kind > 1.5 && kind < 2.5 {
charge = 1.0
marks = 1.0
}
if kind > 2.5 && kind < 3.5 {
foil = 0.35
}
if kind > 3.5 && kind < 4.5 {
rest = mag
on = cyan
}
if kind > 4.5 && kind < 5.5 {
rest = vec3(1.0, 0.23, 0.36)
on = vec3(1.0, 0.55, 0.62)
charge = 0.6
marks = 1.0
}
if kind > 5.5 && kind < 6.5 {
face = 0.0
}
var res = cp_key(p, lo, hi, big, small, px, o, hover, down, 0.0, focus, disabled, rest, on, charge, face, foil, raise) * show
res = cp_hazard(res, p, lo, hi, small, px, marks * (1.0 - disabled * 0.7))
if loading > 0.001 {
let c = (lo + hi) * 0.5
let rad = max(min(hi.x - lo.x, hi.y - lo.y) * 0.5 - 6.5, 2.0)
res = cp_spinner(res, p, c, rad, anim, loading)
}
return res
}
mod.widgets.ButtonFlat.draw_bg.pixel = fn() {
return self.host_over(cp_button(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.border_radius, self.hover, self.down, self.disabled, self.focus, self.loading, self.anim_time, 0.0), 8.0)
}
mod.widgets.Button.draw_bg.pixel = fn() {
return self.host_over(cp_button(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.border_radius, self.hover, self.down, self.disabled, self.focus, self.loading, self.anim_time, 1.0), 8.0)
}
mod.widgets.ButtonPrimary.draw_bg.pixel = fn() {
return self.host_over(cp_button(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.border_radius, self.hover, self.down, self.disabled, self.focus, self.loading, self.anim_time, 2.0), 8.0)
}
mod.widgets.ButtonSecondary.draw_bg.pixel = fn() {
return self.host_over(cp_button(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.border_radius, self.hover, self.down, self.disabled, self.focus, self.loading, self.anim_time, 3.0), 8.0)
}
mod.widgets.ButtonTertiary.draw_bg.pixel = fn() {
return self.host_over(cp_button(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.border_radius, self.hover, self.down, self.disabled, self.focus, self.loading, self.anim_time, 4.0), 8.0)
}
mod.widgets.ButtonDanger.draw_bg.pixel = fn() {
return self.host_over(cp_button(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.border_radius, self.hover, self.down, self.disabled, self.focus, self.loading, self.anim_time, 5.0), 8.0)
}
mod.widgets.ButtonOutline.draw_bg.pixel = fn() {
return self.host_over(cp_button(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.border_radius, self.hover, self.down, self.disabled, self.focus, self.loading, self.anim_time, 6.0), 8.0)
}
mod.widgets.ButtonFlatter.draw_bg.pixel = fn() {
return self.host_over(cp_button(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.border_radius, self.hover, self.down, self.disabled, self.focus, self.loading, self.anim_time, 7.0), 8.0)
}
// Legends: printed ice-cyan, white under the hand, magenta while held.
mod.widgets.ButtonFlat.draw_text.color = #c8faff
mod.widgets.ButtonFlat.draw_text.color_hover = #ffffff
mod.widgets.ButtonFlat.draw_text.color_down = #ff4fd0
mod.widgets.ButtonFlat.draw_text.color_focus = #e6fbff
mod.widgets.ButtonFlat.draw_text.color_disabled = #4f6272
mod.widgets.ButtonFlat.draw_icon.color = #c8faff
mod.widgets.ButtonFlat.draw_icon_end.color = #c8faff
mod.widgets.Button.draw_text.color = #c8faff
mod.widgets.Button.draw_text.color_hover = #ffffff
mod.widgets.Button.draw_text.color_down = #ff4fd0
mod.widgets.Button.draw_text.color_focus = #e6fbff
mod.widgets.Button.draw_text.color_disabled = #4f6272
mod.widgets.ButtonPrimary.draw_text.color = #ffffff
mod.widgets.ButtonPrimary.draw_text.color_hover = #ffffff
mod.widgets.ButtonPrimary.draw_text.color_down = #ff4fd0
mod.widgets.ButtonPrimary.draw_text.color_focus = #ffffff
mod.widgets.ButtonPrimary.draw_text.color_disabled = #4f6272
mod.widgets.ButtonPrimary.draw_icon.color = #ffffff
mod.widgets.ButtonSecondary.draw_text.color = #c8faff
mod.widgets.ButtonSecondary.draw_text.color_hover = #ffffff
mod.widgets.ButtonSecondary.draw_text.color_down = #ff4fd0
mod.widgets.ButtonSecondary.draw_text.color_focus = #e6fbff
mod.widgets.ButtonSecondary.draw_icon.color = #c8faff
mod.widgets.ButtonTertiary.draw_text.color = #ffd6f4
mod.widgets.ButtonTertiary.draw_text.color_hover = #ffffff
mod.widgets.ButtonTertiary.draw_text.color_down = #c8faff
mod.widgets.ButtonTertiary.draw_text.color_focus = #ffd6f4
mod.widgets.ButtonTertiary.draw_icon.color = #ffd6f4
mod.widgets.ButtonDanger.draw_text.color = #ffffff
mod.widgets.ButtonDanger.draw_text.color_hover = #ffffff
mod.widgets.ButtonDanger.draw_text.color_down = #ffd0d8
mod.widgets.ButtonDanger.draw_text.color_focus = #ffffff
mod.widgets.ButtonDanger.draw_icon.color = #ffffff
mod.widgets.ButtonOutline.draw_text.color = #c8faff
mod.widgets.ButtonOutline.draw_text.color_hover = #ffffff
mod.widgets.ButtonOutline.draw_text.color_down = #ff4fd0
mod.widgets.ButtonOutline.draw_text.color_focus = #e6fbff
mod.widgets.ButtonOutline.draw_text.color_disabled = #4f6272
mod.widgets.ButtonOutline.draw_icon.color = #c8faff
// ---------------------------------------------------------------- selection
// The small controls' quads grow so their glow and focus ring fall outside
// the box.
mod.widgets.CheckBoxFlat.draw_bg.vertex = fn() {
let m = 5.0
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
mod.widgets.CheckBoxFlat.clip_x = false
mod.widgets.CheckBoxFlat.clip_y = false
// The check box: a well of black glass in a small cut with a cyan hairline.
// Checked, the hairline and a check mark light magenta, the mark a white-hot
// core in a magenta tube with its halo on the glass; mixed is a dash.
mod.widgets.CheckBoxFlat.draw_bg.pixel = fn() {
let m = 5.0
let px = cp_px(self.draw_pass.dpi_factor)
let o = self.rect_pos
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let sz = self.size
let x0 = (self.rect_size.x - sz) * self.mark_at_end
let y0 = self.rect_size.y * 0.5 - sz * 0.5
let lo = vec2(cp_snap(x0, o.x, px), cp_snap(y0, o.y, px))
let hi = vec2(cp_snap(x0 + sz, o.x, px), cp_snap(y0 + sz, o.y, px))
let c = (lo + hi) * 0.5
let live = 1.0 - self.disabled
let on = max(self.active, self.mixed) * live
let held = max(self.down, self.pressed) * live
var ink = mix(vec3(0.08, 0.94, 1.0), vec3(1.0, 0.12, 0.72), max(on, held))
ink = mix(ink, vec3(1.0, 0.23, 0.36), self.error)
ink = mix(ink, vec3(0.30, 0.38, 0.44), self.disabled)
let fill = mix(vec3(0.016, 0.026, 0.068), vec3(0.05, 0.055, 0.065), self.disabled)
let rim = mix(0.65 + 0.25 * self.hover, 1.0, max(on, held)) * live + 0.25 * self.disabled
var res = cp_glass(p, lo, hi, sz * 0.32, sz * 0.1, px, fill, ink, rim, on)
let d = cp_cut(p, c, (hi - lo) * 0.5, sz * 0.32, sz * 0.1)
let k = sz / 14.0
let dm = min(cp_seg(p, c + vec2(-3.6, 0.1) * k, c + vec2(-1.1, 2.6) * k), cp_seg(p, c + vec2(-1.1, 2.6) * k, c + vec2(3.7, -2.8) * k))
let dd = cp_seg(p, c - vec2(3.5 * k, 0.0), c + vec2(3.5 * k, 0.0))
let dk = mix(dm - 0.9, dd - 0.9, self.mixed)
res = cp_mark(res, dk, 0.9, px, max(self.active, self.mixed), ink)
return cp_focus(res, d, px, self.focus * live)
}
// The radio: the same glass cut evenly at all four corners, an octagon.
// Chosen, an octagonal magenta lamp lights in its middle.
mod.widgets.RadioButtonFlat.draw_bg.vertex = fn() {
let m = 5.0
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
mod.widgets.RadioButtonFlat.clip_x = false
mod.widgets.RadioButtonFlat.clip_y = false
mod.widgets.RadioButtonFlat.draw_bg.pixel = fn() {
let m = 5.0
let px = cp_px(self.draw_pass.dpi_factor)
let o = self.rect_pos
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let sz = self.size
let y0 = self.rect_size.y * 0.5 - sz * 0.5
let lo = vec2(cp_snap(0.0, o.x, px), cp_snap(y0, o.y, px))
let hi = vec2(cp_snap(sz, o.x, px), cp_snap(y0 + sz, o.y, px))
let c = (lo + hi) * 0.5
let live = 1.0 - self.disabled
let on = self.active * live
let held = self.down * live
var ink = mix(vec3(0.08, 0.94, 1.0), vec3(1.0, 0.12, 0.72), max(on, held))
ink = mix(ink, vec3(0.30, 0.38, 0.44), self.disabled)
let fill = mix(vec3(0.016, 0.026, 0.068), vec3(0.05, 0.055, 0.065), self.disabled)
let rim = mix(0.65 + 0.25 * self.hover, 1.0, max(on, held)) * live + 0.25 * self.disabled
var res = cp_glass(p, lo, hi, sz * 0.29, sz * 0.29, px, fill, ink, rim, on)
let d = cp_cut(p, c, (hi - lo) * 0.5, sz * 0.29, sz * 0.29)
let ls = sz * 0.19
let dl = cp_cut(p, c, vec2(ls, ls), ls * 0.6, ls * 0.6)
res = cp_mark(res, dl, ls, px, self.active, ink)
return cp_focus(res, d, px, self.focus * live)
}
// The toggle: a slot of black glass, and a key half its length riding in it
// with a lamp in its middle. On, the key stands at the far end, its pipe and
// lamp lit magenta and the slot's hairline with them.
mod.widgets.ToggleFlat.draw_bg.vertex = fn() {
let m = 6.0
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
mod.widgets.ToggleFlat.clip_x = false
mod.widgets.ToggleFlat.clip_y = false
// One switch: a slot `sz` at the start of the row or at its end, a key in
// it `t` along its travel, lit by `on`. `legend` 1 prints an O on the key
// while off and an I while on (the rocker); 0 leaves a lamp on it.
let cp_switch = fn(p: vec2, size: vec2, o: vec2, px: float, sz: vec2, at_end: float, t: float, hover: float, down: float, focus: float, on: float, disabled: float, legend: float) -> vec4 {
let o0 = vec2((size.x - sz.x) * at_end, size.y * 0.5 - sz.y * 0.5)
let lo = vec2(cp_snap(o0.x, o.x, px), cp_snap(o0.y, o.y, px))
let hi = vec2(cp_snap(o0.x + sz.x, o.x, px), cp_snap(o0.y + sz.y, o.y, px))
let c = (lo + hi) * 0.5
let h = (hi - lo) * 0.5
let live = 1.0 - disabled
let lit = on * live
var ink = mix(vec3(0.08, 0.94, 1.0), vec3(1.0, 0.12, 0.72), lit)
ink = mix(ink, vec3(0.30, 0.38, 0.44), disabled)
let fill = mix(vec3(0.016, 0.026, 0.068), vec3(0.05, 0.055, 0.065), disabled)
var res = cp_glass(p, lo, hi, sz.y * 0.4, sz.y * 0.12, px, fill, ink, mix(0.60 + 0.25 * hover, 0.95, lit) * live + 0.25 * disabled, lit * 0.6)
let d = cp_cut(p, c, h, sz.y * 0.4, sz.y * 0.12)
// The key, two points in from the slot, half its length.
let kh = sz.y - 4.0
let kw = max(sz.x * 0.5 - 2.0, kh)
let kx = lo.x + 2.0 + (sz.x - 4.0 - kw) * t
let klo = vec2(cp_snap(kx, o.x, px), lo.y + 2.0)
let khi = vec2(cp_snap(kx + kw, o.x, px), hi.y - 2.0)
let key = cp_key(p, klo, khi, kh * 0.38, kh * 0.12, px, o, hover, down, on, 0.0, disabled, vec3(0.08, 0.94, 1.0), vec3(1.0, 0.12, 0.72), 0.0, 1.0, 0.8, 1.0)
res = Finish.over(res, key)
let kc = (klo + khi) * 0.5
if legend > 0.5 {
let ring = Finish.cover(abs(length(p - kc) - kh * 0.22) - 0.55, px) * (1.0 - on)
let bar = Finish.cover(max(abs(p.x - kc.x) - 0.6, abs(p.y - kc.y) - kh * 0.26), px) * on
let lc = mix(vec3(0.50, 0.60, 0.68), vec3(1.0, 0.45, 0.85), lit)
res = Finish.over(res, vec4(lc, 1.0) * (ring + bar) * mix(1.0, 0.4, disabled))
} else {
let ld = length(p - kc) - 1.6
let lamp = Finish.cover(ld, px)
let lcol = mix(vec3(0.10, 0.06, 0.10), mix(vec3(1.0, 0.12, 0.72), vec3(1.0, 1.0, 1.0), 0.35), lit)
res = Finish.over(res, vec4(1.0, 0.12, 0.72, 1.0) * exp(-max(ld, 0.0) / 1.6) * 0.5 * lit * (1.0 - lamp))
res = Finish.over(res, vec4(lcol, 1.0) * lamp)
}
return cp_focus(res, d, px, focus * live)
}
mod.widgets.ToggleFlat.draw_bg.pixel = fn() {
let m = 6.0
let px = cp_px(self.draw_pass.dpi_factor)
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let sz = vec2(self.size * self.pill_aspect, self.size)
let t = mix(self.active, self.drag_pos, self.drag)
return cp_switch(p, self.rect_size, self.rect_pos, px, sz, self.mark_at_end, t, self.hover, max(self.pressed, self.down), self.focus, self.active, self.disabled, 0.0)
}
// The rocker and the slide switch are the same slot and key: the rocker's
// key prints the side in force, the slide switch's carries the lamp.
mod.widgets.ToggleRocker.draw_bg.vertex = fn() {
let m = 6.0
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
mod.widgets.ToggleRocker.clip_x = false
mod.widgets.ToggleRocker.clip_y = false
mod.widgets.ToggleRocker.draw_bg.pixel = fn() {
let m = 6.0
let px = cp_px(self.draw_pass.dpi_factor)
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let sz = vec2(self.size * self.pill_aspect, self.size)
return cp_switch(p, self.rect_size, self.rect_pos, px, sz, self.mark_at_end, self.active, self.hover, self.down, self.focus, self.active, self.disabled, 1.0)
}
mod.widgets.ToggleSlide.draw_bg.vertex = fn() {
let m = 6.0
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
mod.widgets.ToggleSlide.clip_x = false
mod.widgets.ToggleSlide.clip_y = false
mod.widgets.ToggleSlide.draw_bg.pixel = fn() {
let m = 6.0
let px = cp_px(self.draw_pass.dpi_factor)
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let sz = vec2(self.size * self.pill_aspect, self.size)
let t = mix(self.active, self.drag_pos, self.drag)
return cp_switch(p, self.rect_size, self.rect_pos, px, sz, self.mark_at_end, t, self.hover, self.down, self.focus, self.active, self.disabled, 0.0)
}
// The radio tab: a key of its own; chosen, it is on -- its pipe magenta and
// its face lit from the rim, as the Amp app's tab keys light.
mod.widgets.RadioButtonTabFlat.draw_bg.vertex = fn() {
let m = 8.0
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
mod.widgets.RadioButtonTabFlat.clip_x = false
mod.widgets.RadioButtonTabFlat.clip_y = false
mod.widgets.RadioButtonTabFlat.padding = mod.turtle.Inset{left: 4.0 right: 10.0 top: 5.0 bottom: 5.0}
mod.widgets.RadioButtonTabFlat.min_height = 26.0
mod.widgets.RadioButtonTabFlat.draw_bg.pixel = fn() {
let m = 8.0
let px = cp_px(self.draw_pass.dpi_factor)
let o = self.rect_pos
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let lo = vec2(cp_snap(0.0, o.x, px), cp_snap(0.0, o.y, px))
let hi = vec2(cp_snap(self.rect_size.x, o.x, px), cp_snap(self.rect_size.y, o.y, px))
return cp_key(p, lo, hi, 7.0, 2.5, px, o, self.hover, self.down, self.active, self.focus, self.disabled, vec3(0.08, 0.94, 1.0), vec3(1.0, 0.12, 0.72), 0.0, 1.0, 1.0, 1.0)
}
mod.widgets.RadioButtonTabFlat.draw_text.color = #c8faff
mod.widgets.RadioButtonTabFlat.draw_text.color_hover = #ffffff
mod.widgets.RadioButtonTabFlat.draw_text.color_active = #ffffff
mod.widgets.RadioButtonTabFlat.draw_text.color_disabled = #4f6272
// ---------------------------------------------------------------- sliders
// A slider's slot: black glass 6 points tall in a small cut, its hairline
// cyan.
let cp_slot = fn(p: vec2, lo: vec2, hi: vec2, px: float, hover: float, disabled: float) -> vec4 {
let fill = mix(vec3(0.016, 0.026, 0.068), vec3(0.05, 0.055, 0.065), disabled)
let ink = mix(vec3(0.08, 0.94, 1.0), vec3(0.30, 0.38, 0.44), disabled)
return cp_glass(p, lo, hi, 3.0, 1.0, px, fill, ink, (0.55 + 0.25 * hover) * (1.0 - disabled) + 0.25 * disabled, 0.0)
}
// The value lit in a slot: a neon bar from x0 to x1 about the line `cy`,
// `hh` half tall, a white-hot core along its middle in the ink and a halo of
// the ink on the glass round it.
let cp_beam = fn(dst: vec4, p: vec2, x0: float, x1: float, cy: float, hh: float, px: float, ink: vec3, amt: float) -> vec4 {
let has = step(0.001, x1 - x0)
let ex = max(max(x0 - p.x, p.x - x1), 0.0)
let ey = max(abs(p.y - cy) - hh, 0.0)
let inside = Finish.cover(max(x0 - p.x, p.x - x1), px) * Finish.cover(abs(p.y - cy) - hh, px) * has
let halo = exp(-length(vec2(ex, ey)) / 1.8) * 0.40 * (1.0 - inside) * has
let res = Finish.over(dst, vec4(ink, 1.0) * halo * amt)
let core = 1.0 - smoothstep(0.0, hh, abs(p.y - cy))
let col = mix(ink, vec3(1.0, 1.0, 1.0), core * 0.7)
return Finish.over(res, vec4(col, 1.0) * inside * amt)
}
// A slider's handle: a small upright key with a magenta-white index across
// its middle.
let cp_handle = fn(dst: vec4, p: vec2, c: vec2, hw: float, hh: float, px: float, o: vec2, hover: float, held: float, focus: float, disabled: float) -> vec4 {
let lo = vec2(cp_snap(c.x - hw, o.x, px), c.y - hh)
let hi = vec2(cp_snap(c.x + hw, o.x, px), c.y + hh)
var res = Finish.over(dst, cp_key(p, lo, hi, 3.5, 1.2, px, o, hover, held, 0.0, focus, disabled, vec3(0.08, 0.94, 1.0), vec3(1.0, 0.12, 0.72), 0.0, 1.0, 0.7, 1.0))
let idx = Finish.cover(max(abs(p.x - c.x) - hw + 2.5, abs(p.y - c.y) - px * 0.75), px)
let ic = mix(mix(vec3(1.0, 0.45, 0.85), vec3(1.0, 1.0, 1.0), held * 0.5), vec3(0.35, 0.40, 0.46), disabled)
return Finish.over(res, vec4(ic, 1.0) * idx)
}
// The bar slider: the slot under its label row, the neon value, and the key
// riding it.
mod.widgets.SliderFlat.clip_x = false
mod.widgets.SliderFlat.clip_y = false
mod.widgets.SliderFlat.draw_bg.vertex = fn() {
let m = 8.0
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
mod.widgets.SliderFlat.draw_bg.pixel = fn() {
let m = 8.0
let px = cp_px(self.draw_pass.dpi_factor)
let o = self.rect_pos
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let cy = cp_snap(20.0 + (self.rect_size.y - 20.0) * 0.5, o.y, px)
let lo = vec2(cp_snap(0.0, o.x, px), cy - 3.0)
let hi = vec2(cp_snap(self.rect_size.x, o.x, px), cy + 3.0)
var res = cp_slot(p, lo, hi, px, self.hover, self.disabled)
let hw = 5.0
let span = self.rect_size.x - hw * 2.0
let hx = hw + self.slide_pos * span
let ox = hw + self.origin_pos * span
let f0 = mix(lo.x + 2.0, min(ox, hx), self.arc_origin)
let f1 = mix(hx, max(ox, hx), self.arc_origin)
let ink = mix(vec3(0.08, 0.94, 1.0), vec3(0.30, 0.38, 0.44), self.disabled)
res = cp_beam(res, p, f0, f1, cy, 1.0, px, ink, 1.0 - 0.6 * self.disabled)
return cp_handle(res, p, vec2(hx, cy), hw, 9.0, px, o, self.hover, self.drag, self.focus, self.disabled)
}
// A slider's value is a text input, and the field row's minimum pushed it
// half a row below its label: here it is only as tall as its line.
mod.widgets.SliderMinimal.text_input.min_height = 0.0
mod.widgets.SliderMinimal.text_input.padding = mod.turtle.Inset{left: 0.0 right: 0.0 top: 0.0 bottom: 0.0}
mod.widgets.SliderFlat.text_input.min_height = 0.0
mod.widgets.SliderFlat.text_input.padding = mod.turtle.Inset{left: 0.0 right: 0.0 top: 0.0 bottom: 0.0}
mod.widgets.Slider.text_input.min_height = 0.0
mod.widgets.Slider.text_input.padding = mod.turtle.Inset{left: 0.0 right: 0.0 top: 0.0 bottom: 0.0}
mod.widgets.SliderRoundFlat.text_input.min_height = 0.0
mod.widgets.SliderRoundFlat.text_input.padding = mod.turtle.Inset{left: 0.0 right: 0.0 top: 0.0 bottom: 0.0}
mod.widgets.SliderRound.text_input.min_height = 0.0
mod.widgets.SliderRound.text_input.padding = mod.turtle.Inset{left: 0.0 right: 0.0 top: 0.0 bottom: 0.0}
// A dial's value box stands in the legend row above the ring: at the field
// row's height it hung over the ring, so here it is its line and two points
// either side.
mod.widgets.RotaryFlat.text_input.min_height = 0.0
mod.widgets.RotaryFlat.text_input.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 2.0 bottom: 2.0}
mod.widgets.Rotary.text_input.min_height = 0.0
mod.widgets.Rotary.text_input.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 2.0 bottom: 2.0}
mod.widgets.RotaryGradientY.text_input.min_height = 0.0
mod.widgets.RotaryGradientY.text_input.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 2.0 bottom: 2.0}
// The minimal slider: only the slot and the lit value; the key shows under
// the hand.
mod.widgets.SliderMinimal.draw_bg.pixel = fn() {
let px = cp_px(self.draw_pass.dpi_factor)
let o = self.rect_pos
let p = self.pos * self.rect_size
let cy = cp_snap(self.offset_y + (self.rect_size.y - self.offset_y) * 0.5, o.y, px)
let lo = vec2(cp_snap(0.0, o.x, px), cy - 2.5)
let hi = vec2(cp_snap(self.rect_size.x, o.x, px), cy + 2.5)
var res = cp_slot(p, lo, hi, px, self.hover, self.disabled)
let lo2 = min(self.origin_pos, self.slide_pos)
let hi2 = max(self.origin_pos, self.slide_pos)
let f0 = max(mix(0.0, lo2, self.arc_origin) * self.rect_size.x, lo.x + 1.5)
let f1 = min(mix(self.slide_pos, hi2, self.arc_origin) * self.rect_size.x, hi.x - 1.5)
let ink = mix(vec3(0.08, 0.94, 1.0), vec3(0.30, 0.38, 0.44), self.disabled)
res = cp_beam(res, p, f0, f1, cy, 0.8, px, ink, 1.0 - 0.6 * self.disabled)
let show = clamp(max(self.hover, self.drag), 0.0, 1.0)
let hx = clamp(self.slide_pos * self.rect_size.x, 3.0, self.rect_size.x - 3.0)
let cap = cp_handle(vec4(0.0, 0.0, 0.0, 0.0), p, vec2(hx, cy), 3.0, 6.0, px, o, 0.0, self.drag, 0.0, self.disabled)
return Finish.over(res, cap * show)
}
// The round slider: a label column, then the bar slider's slot, value and
// key; the slot ends short of the right edge so the value the widget writes
// there stands on the ground.
mod.widgets.SliderRoundFlat.clip_x = false
mod.widgets.SliderRoundFlat.clip_y = false
mod.widgets.SliderRoundFlat.draw_bg.vertex = fn() {
let m = 8.0
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
mod.widgets.SliderRoundFlat.draw_bg.pixel = fn() {
let m = 8.0
let px = cp_px(self.draw_pass.dpi_factor)
let o = self.rect_pos
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let cy = cp_snap(self.rect_size.y * 0.5, o.y, px)
let lo = vec2(cp_snap(self.label_size, o.x, px), cy - 3.0)
let hi = vec2(cp_snap(self.rect_size.x - 37.0, o.x, px), cy + 3.0)
var res = cp_slot(p, lo, hi, px, self.hover, self.disabled)
let hw = 5.0
let span = hi.x - lo.x - hw * 2.0
let hx = lo.x + hw + self.slide_pos * span
let ink = mix(vec3(0.08, 0.94, 1.0), vec3(0.30, 0.38, 0.44), self.disabled)
res = cp_beam(res, p, lo.x + 2.0, hx, cy, 1.0, px, ink, 1.0 - 0.6 * self.disabled)
return cp_handle(res, p, vec2(hx, cy), hw, 9.0, px, o, self.hover, self.drag, self.focus, self.disabled)
}
// The range slider: the bar slider's slot, the value lit between the two
// ends, and a key on each end; the end under the pointer lights, the held
// one (or both while the band is dragged) turns magenta.
mod.widgets.RangeSliderFlat.clip_x = false
mod.widgets.RangeSliderFlat.clip_y = false
mod.widgets.RangeSliderFlat.draw_bg.vertex = fn() {
let m = 8.0
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
mod.widgets.RangeSliderFlat.draw_bg.pixel = fn() {
let m = 8.0
let px = cp_px(self.draw_pass.dpi_factor)
let o = self.rect_pos
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let rad = self.handle_px * 0.5
let cy = cp_snap(self.label_px + (self.rect_size.y - self.label_px) * 0.5, o.y, px)
let lo = vec2(cp_snap(0.0, o.x, px), cy - 3.0)
let hi = vec2(cp_snap(self.rect_size.x, o.x, px), cy + 3.0)
var res = cp_slot(p, lo, hi, px, self.hover, self.disabled)
let span = self.rect_size.x - self.inset_px * 2.0 - self.handle_px
let sx = self.inset_px + rad + self.start_pos * span
let ex = self.inset_px + rad + self.end_pos * span
let ink = mix(vec3(0.08, 0.94, 1.0), vec3(0.30, 0.38, 0.44), self.disabled)
res = cp_beam(res, p, sx, ex, cy, 1.0, px, ink, 1.0 - 0.6 * self.disabled)
let held_s = clamp(self.drag * (self.hot_start + self.hot_band), 0.0, 1.0)
let held_e = clamp(self.drag * (self.hot_end + self.hot_band), 0.0, 1.0)
let hw = min(rad, 5.0)
res = cp_handle(res, p, vec2(sx, cy), hw, 9.0, px, o, self.hover * self.hot_start, held_s, 0.0, self.disabled)
return cp_handle(res, p, vec2(ex, cy), hw, 9.0, px, o, self.hover * self.hot_end, held_e, 0.0, self.disabled)
}
// ---------------------------------------------------------------- meters
// A WEDGE LADDER: slanted cells along `a` (0..span from the scale's zero) on
// a lane `b` across (0 at the lane's floor, `lane` its height), each cell
// taller than the last toward the top of the scale, lit cyan into magenta
// into warning yellow up to `level`; unlit cells are the ghost of their own
// outline. Returns the cells' light (rgb) and cover (a), not premultiplied.
let cp_ladder = fn(a: float, b: float, span: float, lane: float, pitch: float, level: float, px: float) -> vec4 {
let xs = a - b * 0.45
let seg = floor(xs / pitch)
let fx = fract(xs / pitch)
let n = max(floor(span / pitch), 1.0)
let tt = (seg + 0.5) / n
let hf = mix(0.35, 1.0, clamp(tt, 0.0, 1.0))
let e = px / pitch
let cx = smoothstep(0.10 - e, 0.10 + e, fx) * (1.0 - smoothstep(0.78 - e, 0.78 + e, fx))
let cy = smoothstep(-px, px, b) * (1.0 - smoothstep(lane * hf - px, lane * hf + px, b))
let valid = step(0.0, seg) * step(seg, n - 1.0)
let mcell = cx * cy * valid
let on = step(tt, level) * step(0.001, level)
var lit = mix(vec3(0.08, 0.94, 1.0), vec3(1.0, 0.12, 0.72), smoothstep(0.45, 0.80, tt))
lit = mix(lit, vec3(1.0, 0.86, 0.10), smoothstep(0.84, 0.95, tt))
let inner = smoothstep(0.16, 0.2, fx) * (1.0 - smoothstep(0.68, 0.72, fx)) * smoothstep(1.0, 1.6, b) * (1.0 - smoothstep(lane * hf - 1.6, lane * hf - 1.0, b))
let ol = (1.0 - cx * cy * inner) * mcell
let col = mix(lit * 0.18, lit * (1.0 + 0.25 * smoothstep(0.1, 0.0, fx)), on)
return vec4(col, mcell * max(on, ol))
}
// The level meter: black glass in a small cut with a cyan hairline, the
// wedge ladder in it under scanlines, the held peak one cell at full
// strength, and a magenta lamp past its end for the over.
mod.widgets.LevelMeter.draw_bg.pixel = fn() {
let px = cp_px(self.draw_pass.dpi_factor)
let o = self.rect_pos
let sz = self.rect_size
let p = self.pos * sz
let v = self.vertical
let along = mix(sz.x, sz.y, v)
let across = mix(sz.y, sz.x, v)
let a = mix(p.x, sz.y - p.y, v)
let b = mix(sz.y - p.y, p.x, v)
let lamp_room = self.lamp * (across + 3.0)
let bar = max(4.0, along - lamp_room)
let wlo = mix(vec2(0.0, 0.0), vec2(0.0, sz.y - bar), v)
let whi = mix(vec2(bar, sz.y), vec2(sz.x, sz.y), v)
let lo = vec2(cp_snap(wlo.x, o.x, px), cp_snap(wlo.y, o.y, px))
let hi = vec2(cp_snap(whi.x, o.x, px), cp_snap(whi.y, o.y, px))
var res = cp_glass(p, lo, hi, min(across * 0.45, 3.0), 1.0, px, vec3(0.024, 0.027, 0.039), vec3(0.08, 0.94, 1.0), 0.30, 0.0)
let pad = 2.0
let span = max(1.0, bar - pad * 2.0)
let pitch = max(self.segment, 3.0)
let lane = across - pad * 2.0
let cell = cp_ladder(a - pad, b - pad, span, lane, pitch, self.level, px)
let sl = 0.82 + 0.18 * (0.5 + 0.5 * sin(p.y * 3.14159 * self.draw_pass.dpi_factor))
let inb = step(pad, a) * step(a, bar - pad)
res = Finish.over(res, vec4(cell.rgb * sl, 1.0) * cell.a * inb * self.opacity)
// The held peak: its cell at full strength.
let hold = cp_ladder(a - pad, b - pad, span, lane, pitch, 1.0, px)
let hold_at = span * self.hold
let hc = step(abs(a - pad - (b - pad) * 0.45 - hold_at), pitch * 0.5) * step(0.002, self.hold)
res = Finish.over(res, vec4(hold.rgb, 1.0) * hold.a * hc * inb)
if self.lamp > 0.5 {
let lc = mix(vec2(bar + 3.0 + across * 0.5, sz.y * 0.5), vec2(sz.x * 0.5, across * 0.5), v)
let rl = clamp(across * 0.5 - 1.5, 1.0, 2.5)
let ld = cp_cut(p, lc, vec2(rl, rl), rl * 0.6, rl * 0.2)
let over = step(0.5, self.over)
let lamp = Finish.cover(ld, px)
res = Finish.over(res, vec4(1.0, 0.12, 0.72, 1.0) * Finish.glow(ld, 1.5) * 0.45 * over * (1.0 - lamp))
res = Finish.over(res, vec4(mix(vec3(0.10, 0.06, 0.10), vec3(1.0, 0.55, 0.88), over), 1.0) * lamp)
}
return res * self.opacity
}
// The progress bar: a slot of black glass, the value a neon bar in its role
// colour along the middle of the glass, its halo round it. Unknown, a
// stretch of it stands in the middle with its ends fading (a sheet may not
// read the clock).
mod.widgets.ProgressBarFlat.height = 8.0
mod.widgets.ProgressBarFlat.draw_bg.pixel = fn() {
let px = cp_px(self.draw_pass.dpi_factor)
let o = self.rect_pos
let p = self.pos * self.rect_size
let th = min(self.thickness, self.rect_size.y)
let y0 = cp_snap((self.rect_size.y - th) * 0.5, o.y, px)
let x0 = cp_snap(self.rect_size.x * self.start, o.x, px)
let x1 = cp_snap(self.rect_size.x * self.end, o.x, px)
let lo = vec2(x0, y0)
let hi = vec2(x1, y0 + th)
var res = vec4(0.0, 0.0, 0.0, 0.0)
if self.track > 0.5 {
res = cp_glass(p, lo, hi, min(th * 0.5, 3.0), 1.0, px, vec3(0.024, 0.027, 0.039), vec3(0.08, 0.94, 1.0), 0.30, 0.0)
}
let fill = self.fill_color()
let ins = 2.5
let span = hi.x - lo.x - ins * 2.0
var f0 = lo.x + ins
var f1 = f0 + span * clamp((self.value - self.start) / max(self.end - self.start, 0.001), 0.0, 1.0)
var fade = fill.a
if self.value < 0.0 {
f0 = lo.x + ins + span * 0.3
f1 = lo.x + ins + span * 0.7
fade = fade * smoothstep(f0, f0 + span * 0.12, p.x) * smoothstep(f1, f1 - span * 0.12, p.x)
}
let lit = cp_beam(vec4(0.0, 0.0, 0.0, 0.0), p, f0, f1, (lo.y + hi.y) * 0.5, max(th * 0.5 - ins, 0.6), px, fill.rgb, 1.0)
res = Finish.over(res, lit * fade)
return res * self.opacity
}
mod.widgets.ProgressBarFlat.draw_bg.color_primary = #14f0ff
// The spinner: a ring of slanted cells three quarters lit, cyan into
// magenta, standing still (a sheet may not read the pass clock).
mod.widgets.SpinnerFlat.draw_bg.pixel = fn() {
let px = cp_px(self.draw_pass.dpi_factor)
let p = self.pos * self.rect_size
let c = self.rect_size * 0.5
let q = p - c
let size = min(self.rect_size.x, self.rect_size.y)
let ro = size * 0.5 - 1.0
let ri = ro - max(size * 0.16, 2.0)
let rl = length(q)
var ang = atan2(-q.x, q.y)
if ang < 0.0 {
ang = ang + 2.0 * PI
}
let n = 12.0
let u = ang / (2.0 * PI) * n + (rl - ri) * 0.08
let f = fract(u)
let k = floor(u)
let cell = Finish.cover(max(ri - rl, rl - ro), px) * smoothstep(0.12, 0.2, f) * (1.0 - smoothstep(0.72, 0.8, f))
let t = k / n
let on = step(t, 0.75)
let col = mix(mix(vec3(0.08, 0.94, 1.0), vec3(1.0, 0.12, 0.72), t / 0.75) * mix(0.35, 1.0, t / 0.75), vec3(0.06, 0.10, 0.12), 1.0 - on)
return vec4(col, 1.0) * cell * self.alpha
}
// ---------------------------------------------------------------- knobs
// A KNOB is a HUD dial drawn flat in thin lines with a soft glow, straight
// on the ground. Cyan draws its structure and lights its value, deep teal
// fills its heavy arcs, orange is kept for a few fixed accents, and white
// for the hottest bits. From the outside in: a halo past the outer ring (an
// orange partial arc with a ball on its first end, a satellite circle on a
// radial line, a short arc of orange ticks toward the end of the scale, and
// a faint dotted arc across the knob's gap); the outer ring over the scale,
// broken at its thirds, a cross bar at either end; a bold band over its
// middle third; a ring of ticks, every fifth longer, and on a big dial a
// thin ring inside them with a dot at each long tick; a segmented arc of
// blocks, unlit ones dim, lit from the start of the scale (or from the
// origin, marked orange) to the value, the block at the value white and
// standing proud; a dotted ring; the inner ring with two orange marker
// dots; two heavy partial arcs, a thin arc either side, a broken crosshair,
// a small ring round the centre and a crisp white dot in it. The pointer is
// a white line from that ring out to the blocks, and a white wedge stands
// on the outer ring at the value. The smallest knob keeps the ring, the
// blocks (unlit ones a dim fill), the inner ring, the dot, the pointer and
// the wedge; the halo's tick and dotted arcs and the centre ring come in
// from 15 points of radius, the ticks and the dotted ring from 19, the
// orange arc, the satellite, the crosshair and unlit blocks in outline from
// 27, the band, the heavy and thin arcs and the marker dots from 33, and
// the thin ring of dots from 48. Hover brightens the lines and widens their
// glow, a drag lights the value whiter, focus sets four brackets on the
// diagonals past the halo, and disabled is dim grey with the value hidden.
// Angles run from straight down, clockwise, the knob's gap at the bottom.
// A line of the dial: `d` the distance from its edge (negative inside), a
// crisp core in `ink` at `amt` and a soft glow in `glow` falling over
// `reach` points, laid as light on what is under it. Premultiplied.
let cp_glow = fn(dst: vec4, d: float, px: float, ink: vec3, glow: vec3, amt: float, reach: float, halo: float) -> vec4 {
let core = Finish.cover(d, px) * amt
let g = exp(-max(d, 0.0) / max(reach, 0.1)) * halo * amt * (1.0 - core)
let res = vec4(dst.rgb + glow * g, min(dst.a + g * 0.3, 1.0))
return Finish.over(res, vec4(ink, 1.0) * core)
}
// The same with the glow in the line's own ink.
let cp_hud = fn(dst: vec4, d: float, px: float, ink: vec3, amt: float, reach: float, halo: float) -> vec4 {
return cp_glow(dst, d, px, ink, ink, amt, reach, halo)
}
// Distance from `q` (centre-relative, `rq` its length, `ang` its angle) to
// the arc of radius `r` from angle `a0` to `a1`, its ends round.
let cp_arc = fn(q: vec2, rq: float, ang: float, r: float, a0: float, a1: float) -> float {
let e0 = vec2(-sin(a0), cos(a0)) * r
let e1 = vec2(-sin(a1), cos(a1)) * r
let ends = min(length(q - e0), length(q - e1))
return mix(ends, abs(rq - r), step(a0, ang) * step(ang, a1))
}
let cp_knob = fn(p: vec2, c: vec2, rr: float, px: float, slide: float, origin: float, arc_origin: float, gap: float, hover: float, held: float, focus: float, disabled: float) -> vec4 {
let live = 1.0 - disabled
let rad = rr
let q = p - c
let rq = length(q)
let one = PI / 180.0
let sweep = max(2.0 * PI - gap * one, 0.01)
let start = gap * one * 0.5
let stop = start + sweep
var ang = atan2(-q.x, q.y)
if ang < 0.0 {
ang = ang + 2.0 * PI
}
let u = (ang - start) / sweep
let t1 = step(15.0, rad)
let tk = step(19.0, rad)
let tm = step(27.0, rad)
let t2 = step(33.0, rad)
let t3 = step(48.0, rad)
let hv = hover * live
let hot = held * live
let grey = vec3(0.30, 0.34, 0.38)
let cyan = mix(vec3(0.08, 0.94, 1.0), grey, disabled)
let teal = mix(vec3(0.03, 0.50, 0.60), grey * 0.75, disabled)
let amber = mix(vec3(1.0, 0.55, 0.10), grey, disabled)
let white = mix(vec3(0.94, 1.0, 1.0), vec3(0.42, 0.46, 0.50), disabled)
let lift = (1.0 + 0.30 * hv) * mix(1.0, 0.55, disabled)
let reach = mix(0.9, 1.8, hv)
let halo = 0.30 * live
let lo_v = mix(0.0, min(origin, slide), arc_origin)
let hi_v = mix(slide, max(origin, slide), arc_origin)
// The radii: the halo, the outer ring, the ticks, the blocks, the
// dotted ring, the inner ring and the ring round the centre.
let rh = rad + 0.6
let ro = rad - max(rad * 0.055, 1.6)
let tr1 = ro - mix(1.2, mix(4.3, 5.2, t3), t2)
let tl = mix(1.4, mix(1.8, 2.4, t3), t2)
let b1 = rad * mix(mix(mix(0.80, 0.735, tk), 0.71, t2), 0.62, t3)
let b0 = rad * mix(mix(0.58, 0.585, tk), 0.465, t3)
let rd = rad * mix(mix(0.49, 0.44, t2), 0.355, t3)
let ri = rad * mix(mix(mix(0.45, 0.40, tk), 0.53, t2), 0.445, t3)
let rc = rad * mix(mix(0.21, 0.20, tk), mix(0.11, 0.095, t3), tm)
// THE WELL. Under everything the knob draws, a near-black disc out to
// its halo, set into the plate: a thin dark rim, the plate's shadow
// falling in under its upper edge, a faint lit lip along its lower edge
// where the wall faces the light, and the knob's own cyan glowing on it
// round the rings, black toward its rim and in the gap at its foot.
// Dimmer when disabled.
let rw = rh + 1.0
let dw = rq - rw
let wa = Finish.cover(dw, px)
let wsh = smoothstep(-2.5, 2.5, length(q + cp_lt() * 3.0) - rw)
var well = vec3(0.030, 0.030, 0.064) * (1.0 - 0.55 * wsh)
let nq = q / max(rq, 0.001)
let rings = smoothstep(rw * 0.15, rw * 0.5, rq) * (1.0 - smoothstep(rw * 0.75, rw, rq)) * (1.0 - 0.75 * smoothstep(0.2, 0.9, nq.y))
well = well + mix(vec3(0.0, 0.14, 0.24), grey * 0.07, disabled) * rings * (0.5 + 0.5 * live)
let wlip = Finish.cover(abs(dw + 1.2) - 0.5 * px, px) * max(dot(nq, -cp_lt()), 0.0)
well = well + vec3(0.10, 0.11, 0.13) * wlip * 0.6
well = mix(well, vec3(0.0, 0.0, 0.0), Finish.cover(abs(dw + 0.5 * px) - 0.5 * px, px) * 0.8)
let wo = wa * mix(1.0, 0.6, disabled)
var res = vec4(well * wo, wo)
// THE HALO. An arc of short orange ticks toward the end of the scale,
// standing out from the halo's line.
let hs = mix(2.1, 2.5, t2) / max(rh, 1.0)
let hk = floor(ang / hs + 0.5) * hs
let hu = (hk - start) / sweep
let htl = mix(0.9, 1.4, t2)
let hd = vec2(-sin(hk), cos(hk))
let d_ht = cp_seg(q, hd * (rh - 0.3), hd * (rh + htl)) - 0.5 * px + (1.0 - step(0.80, hu) * step(hu, 0.97) * t1) * 100.0
res = cp_hud(res, d_ht, px, amber, 0.85 * lift, reach * 0.7, halo * 0.6)
// A faint dotted arc across the knob's gap, so the dial keeps a round
// edge; it stops short of the cross bars.
let gsp = 3.0 / max(rh, 1.0)
var ga = ang
if ga > PI {
ga = ga - 2.0 * PI
}
let gk = floor(ga / gsp + 0.5) * gsp
let gin = step(abs(gk), start - 2.6 / max(rh, 1.0)) * t1
let d_gd = length(q - vec2(-sin(gk), cos(gk)) * rh) - max(0.6 * px, rad * 0.009) + (1.0 - gin) * 100.0
res = cp_hud(res, d_gd, px, cyan, 0.50 * lift, reach * 0.5, halo * 0.3)
// On a bigger dial an orange partial arc over the start of the scale,
// a ball on its first end.
let oa0 = start + sweep * 0.07
let oa1 = start + sweep * 0.30
let d_oa = cp_arc(q, rq, ang, rh, oa0, oa1) - mix(0.7, 0.95, t3)
let d_ball = length(q - vec2(-sin(oa0), cos(oa0)) * rh) - mix(1.8, 2.2, t3)
res = cp_hud(res, min(d_oa, d_ball) + (1.0 - tm) * 100.0, px, amber, 0.9 * lift, reach, halo)
// And a satellite: a small open circle with an orange heart on the
// halo, on a radial line that runs in through the ring to the blocks.
let sa = start + sweep * 0.75
let sdir = vec2(-sin(sa), cos(sa))
let sr = mix(1.5, 2.0, t3)
let sc = sdir * (rh - 0.2)
let d_sat = abs(length(q - sc) - sr) - 0.5 * px + (1.0 - tm) * 100.0
res = cp_hud(res, d_sat, px, cyan, 0.9 * lift, reach, halo)
res = cp_hud(res, length(q - sc) - sr * 0.4 + (1.0 - tm) * 100.0, px, amber, 0.95 * lift, reach, halo)
let d_rl = cp_seg(q, sdir * (b1 + 1.0), sdir * (rh - 0.2 - sr)) - 0.5 * px + (1.0 - tm) * 100.0
res = cp_hud(res, d_rl, px, cyan, 0.45 * lift, reach * 0.6, halo * 0.4)
// THE OUTER RING over the scale, broken at its thirds, a cross bar
// through it at either end.
let bw = mix(1.4, max(2.2, ro * 0.06), t1) / max(ro, 1.0)
let s3 = sweep / 3.0
let d_r0 = cp_arc(q, rq, ang, ro, start, start + s3 - bw)
let d_r1 = cp_arc(q, rq, ang, ro, start + s3 + bw, start + 2.0 * s3 - bw)
let d_r2 = cp_arc(q, rq, ang, ro, start + 2.0 * s3 + bw, stop)
let cl = mix(0.9, mix(1.8, 2.4, t3), t2)
let co = rh + mix(0.4, 1.0, t2)
let e0 = vec2(-sin(start), cos(start))
let e1 = vec2(-sin(stop), cos(stop))
let d_cap = min(cp_seg(q, e0 * (ro - cl), e0 * co), cp_seg(q, e1 * (ro - cl), e1 * co))
res = cp_hud(res, min(min(d_r0, min(d_r1, d_r2)), d_cap) - 0.5 * px, px, cyan, 0.85 * lift, reach, halo)
// A bold teal band just inside it over the middle third, square at its
// ends.
let rb = ro - mix(2.3, 2.9, t3)
let d_bb = max(abs(rq - rb) - mix(0.85, 1.1, t3), (abs(ang - PI) - s3 * 0.5 + bw * ro / rb) * rq) + (1.0 - t2) * 100.0
res = cp_glow(res, d_bb, px, teal, cyan, 0.95 * lift, reach, halo * 0.5)
// The ring of ticks, every fifth longer and brighter.
let nt = mix(mix(20.0, 40.0, t2), 50.0, t3)
let tq = u * nt
let ti = floor(tq + 0.5)
let tx = abs(tq - ti) * sweep / nt * rq
let major = step(fract(ti / 5.0 + 0.01), 0.05)
let tr0 = tr1 - tl - major * mix(0.6, mix(1.5, 1.2, t3), t2)
let tok = step(-0.5, ti) * step(ti, nt + 0.5) * tk
let d_tick = max(tx - 0.5 * px, max(tr0 - rq, rq - tr1)) + (1.0 - tok) * 100.0
res = cp_hud(res, d_tick, px, cyan, mix(mix(0.6, 0.42, t2), 0.85, major) * lift, reach * 0.7, halo * 0.6)
// On a big dial a thin ring inside the ticks, a dot at each long tick.
let rn = rad * 0.70
let nk = clamp(floor(u * 10.0 + 0.5), 0.0, 10.0)
let na = start + sweep * nk / 10.0
let d_nr = cp_arc(q, rq, ang, rn, start, stop) - 0.5 * px + (1.0 - t3) * 100.0
res = cp_hud(res, d_nr, px, cyan, 0.35 * lift, reach * 0.5, halo * 0.3)
let d_nd = length(q - vec2(-sin(na), cos(na)) * rn) - max(rad * 0.02, 1.0) + (1.0 - t3) * 100.0
res = cp_hud(res, d_nd, px, cyan, 0.85 * lift, reach * 0.7, halo * 0.6)
// The segmented arc: unlit blocks dim (an outline from 27 points), lit
// ones cyan, the one at the value white and standing proud of the rest
// toward the centre.
let bm = (b0 + b1) * 0.5
let bh = (b1 - b0) * 0.5
let nb = clamp(floor(bm * sweep / mix(mix(3.4, 4.0, tk), 4.4, t2)), 8.0, 40.0)
let bu = u * nb
let bi = floor(bu)
let bf = bu - bi
let pitch = sweep / nb * rq
let gp = max(pitch * 0.30, 1.5 * px)
let dbx = (abs(bf - 0.5) - 0.5) * pitch + gp * 0.5
let bok = step(0.0, bi) * step(bi, nb - 1.0)
// Lit blocks and the head go by index, so a value on the middle of a
// block lights that block and makes it the head.
let nbm = nb - 1.0
let lo_i = clamp(floor(lo_v * nb + 0.0001), 0.0, nbm)
let hi_i = clamp(floor(hi_v * nb - 0.0001), 0.0, nbm)
let hd_i = mix(hi_i, lo_i, arc_origin * step(slide, origin - 0.0001))
let on = step(lo_i - 0.5, bi) * step(bi, hi_i + 0.5) * step(0.0005, hi_v - lo_v) * live
let head = on * (1.0 - step(0.5, abs(bi - hd_i)))
let proud = max(rad * 0.035, 0.75) * head
let dbr = abs(rq - bm + proud * 0.5) - bh - proud * 0.5
let d_blk = max(dbx, dbr) + (1.0 - bok) * 100.0
let d_line = mix(d_blk, abs(d_blk + 0.5 * px) - 0.5 * px, tm)
let lit = mix(mix(cyan, vec3(0.50, 1.0, 1.0), hot * 0.35), white, head)
let blk_glow = mix(cyan, vec3(0.60, 1.0, 1.0), head)
let blk_amt = mix(mix(0.26, 0.50, tm) * lift, 1.0, on)
res = cp_glow(res, mix(d_line, d_blk, on), px, mix(cyan, lit, on), blk_glow, blk_amt, reach * mix(0.7, 1.0 + 0.3 * hot, on), halo * mix(0.4, 1.0 + 0.4 * head + 0.3 * hot, on))
// The origin, on a knob lit from it: an orange bar two device pixels
// wide across the blocks, cut into them with a dark pixel either side.
let dirg = vec2(-sin(start + sweep * origin), cos(start + sweep * origin))
let d_org = cp_seg(q, dirg * (b0 - 1.5), dirg * (b1 + 1.5))
res = Finish.over(res, vec4(0.0, 0.0, 0.0, 1.0) * Finish.cover(d_org - 2.0 * px, px) * 0.85 * arc_origin)
res = cp_hud(res, d_org - px, px, amber, arc_origin * lift, reach, halo)
// The dotted ring.
let nd = max(floor(2.0 * PI * rd / 2.8), 8.0)
let ak = floor(ang / (2.0 * PI) * nd + 0.5) * 2.0 * PI / nd
let d_dot = length(q - vec2(-sin(ak), cos(ak)) * rd) - max(px * 0.75, rad * 0.008) + (1.0 - tk) * 100.0
res = cp_hud(res, d_dot, px, cyan, 0.65 * lift, reach * 0.6, halo * 0.5)
// The inner ring, and its two orange marker dots on a big dial.
res = cp_hud(res, abs(rq - ri) - 0.5 * px, px, cyan, 0.60 * lift, reach, halo * 0.7)
let mr = max(rad * 0.032, 1.1)
let d_mk = min(length(q - vec2(-ri, 0.0)), length(q - vec2(ri, 0.0))) - mr + (1.0 - t2) * 100.0
res = cp_hud(res, d_mk, px, amber, 0.95 * lift, reach, halo)
// Two heavy teal partial arcs just inside it, top left and bottom right.
let rh2 = rad * mix(0.49, 0.405, t3)
let d_hv = min(cp_arc(q, rq, ang, rh2, 1.95, 2.95), cp_arc(q, rq, ang, rh2, 5.09, 6.09)) - rad * mix(0.028, 0.026, t3) + (1.0 - t2) * 100.0
res = cp_glow(res, d_hv, px, teal, cyan, 0.95 * lift, reach, halo * 0.5)
// A thin arc either side of the centre.
let rp = rad * mix(0.36, 0.30, t3)
let d_pa = min(cp_arc(q, rq, ang, rp, 0.96, 2.18), cp_arc(q, rq, ang, rp, 4.10, 5.32)) - max(rad * 0.012, 0.75) + (1.0 - t2) * 100.0
res = cp_hud(res, d_pa, px, cyan, 0.85 * lift, reach, halo)
// The crosshair: four short strokes, open at the middle.
let qa = abs(q)
let x0 = rad * mix(0.16, 0.13, t3)
let x1 = rad * mix(0.28, 0.24, t3)
let d_x = min(max(qa.y - 0.5 * px, max(x0 - qa.x, qa.x - x1)), max(qa.x - 0.5 * px, max(x0 - qa.y, qa.y - x1))) + (1.0 - tm) * 100.0
res = cp_hud(res, d_x, px, cyan, 0.70 * lift, reach, halo * 0.7)
// The small ring round the centre.
res = cp_hud(res, abs(rq - rc) - 0.5 * px + (1.0 - t1) * 100.0, px, cyan, 0.65 * lift, reach * 0.6, halo * 0.5)
// The pointer: a white line from that ring out to the blocks, its glow
// tight to it.
let ve = start + sweep * slide
let dir = vec2(-sin(ve), cos(ve))
let pr0 = mix(max(rad * 0.22, 2.2), rc + max(rad * 0.04, 1.0), t1)
let pr1 = b0 - max(rad * 0.035, 0.75) - 0.8
let d_ptr = cp_seg(q, dir * pr0, dir * pr1) - mix(0.6, 0.85, hot) * px
res = cp_glow(res, d_ptr, px, white, cyan, 1.0, reach * (0.7 + 0.2 * hot), halo * (0.8 + 0.3 * hot))
// The marker on the outer ring at the value: a small white wedge
// pointing in.
let mk = clamp(rad * 0.05, 1.3, 3.0)
let tip = ro - mk * 1.5
let bas = ro + mk * 0.5
let ta = dot(q, dir)
let sa2 = abs(dot(q, vec2(-dir.y, dir.x)))
let kk = mk / (bas - tip)
let d_wedge = max(max(tip - ta, ta - bas), (sa2 - (ta - tip) * kk) / sqrt(1.0 + kk * kk))
res = cp_glow(res, d_wedge, px, white, cyan, 1.0, reach * (0.8 + 0.3 * hot), halo * (1.0 + 0.4 * hot))
// The centre: a crisp white dot, its glow tight.
let d_ctr = rq - max(rad * 0.05, 1.2)
res = cp_glow(res, d_ctr, px, white, cyan, mix(1.0, 0.8, disabled), reach * 0.5, halo * 0.8)
// Focus: four short brackets on the diagonals past the halo, their ends
// turned in.
let rf = rad + 4.2
let off = (fract(ang / (0.5 * PI)) - 0.5) * 0.5 * PI
let span = mix(0.20, 0.13, t2)
let d_br = max(abs(rq - rf) - 0.5 * px, (abs(off) - span) * rq)
let d_brc = max(abs((abs(off) - span) * rq) - 0.5 * px, max(rf - 1.2 - rq, rq - rf))
return cp_hud(res, min(d_br, d_brc), px, cyan, focus * live, 1.4, 0.5)
}
mod.widgets.RotaryKnob.clip_x = false
mod.widgets.RotaryKnob.clip_y = false
mod.widgets.RotaryKnob.draw_bg.vertex = fn() {
let m = 6.0
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
mod.widgets.RotaryKnob.draw_bg.pixel = fn() {
let m = 6.0
let px = cp_px(self.draw_pass.dpi_factor)
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let dia = min(self.rect_size.x, self.rect_size.y)
let c = vec2(Finish.snap(self.rect_pos.x + self.rect_size.x * 0.5, px) - self.rect_pos.x, Finish.snap(self.rect_pos.y + self.rect_size.y - dia * 0.5, px) - self.rect_pos.y)
return cp_knob(p, c, dia * 0.5 - 1.0, px, self.slide_pos, self.origin_pos, self.arc_origin, self.gap, self.hover, self.drag, self.focus, self.disabled)
}
mod.widgets.RotaryFlat.clip_x = false
mod.widgets.RotaryFlat.clip_y = false
mod.widgets.RotaryFlat.draw_bg.vertex = fn() {
let m = 6.0
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
mod.widgets.RotaryFlat.draw_bg.pixel = fn() {
let m = 6.0
let px = cp_px(self.draw_pass.dpi_factor)
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let dia = min(self.rect_size.x, self.rect_size.y - 22.0)
let c = vec2(Finish.snap(self.rect_pos.x + self.rect_size.x * 0.5, px) - self.rect_pos.x, Finish.snap(self.rect_pos.y + 20.0 + dia * 0.5, px) - self.rect_pos.y)
return cp_knob(p, c, dia * 0.5 - 1.0, px, self.slide_pos, self.origin_pos, self.arc_origin, self.gap, self.hover, self.drag, self.focus, self.disabled)
}
// THE FADER: a slot of black glass along the travel with the value lit in
// it, a wedge ladder of cells beside it lit to the value, a scale of cyan
// ticks on the other side, and a key for a cap with a magenta-white index
// across its middle. The cap's shadow and glow fall outside the box.
mod.widgets.SliderFader.cap_size = 22.0
mod.widgets.SliderFader.draw_bg.track_size = uniform(5.0)
mod.widgets.SliderFader.draw_bg.cap_cross = uniform(0.62)
mod.widgets.SliderFader.clip_x = false
mod.widgets.SliderFader.clip_y = false
mod.widgets.SliderFader.draw_bg.vertex = fn() {
let m = 8.0
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
mod.widgets.SliderFader.draw_bg.pixel = fn() {
let m = 8.0
let px = cp_px(self.draw_pass.dpi_factor)
let sz = self.rect_size
let p = self.pos * (sz + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let v = self.is_vertical
let along = mix(sz.x, sz.y, v)
let across = mix(sz.y, sz.x, v)
let travel = max(along - self.inset_px * 2.0 - self.cap_px, 1.0)
let foot = self.inset_px + self.cap_px * 0.5
// Track space: `a` from the value's zero, `b` across.
let a = mix(p.x, sz.y - p.y, v)
let b = mix(p.y, p.x, v)
let ab = vec2(a, b)
let mid = Finish.snap(across * 0.5, px)
let live = 1.0 - self.disabled
// The slot.
let tw = max(min(self.track_size, across) * 0.5, 1.0)
let slo = vec2(self.inset_px, mid - tw)
let shi = vec2(along - self.inset_px, mid + tw)
let fill = mix(vec3(0.016, 0.026, 0.068), vec3(0.05, 0.055, 0.065), self.disabled)
let ink = mix(vec3(0.08, 0.94, 1.0), vec3(0.30, 0.38, 0.44), self.disabled)
var res = cp_glass(ab, slo, shi, 2.0, 0.8, px, fill, ink, 0.40 * live + 0.25 * self.disabled, 0.0)
let at = foot + self.slide_pos * travel
res = cp_beam(res, ab, slo.x + 2.0, at, mid, 0.7, px, ink, 1.0 - 0.6 * self.disabled)
// The ladder beside the slot, its floor toward the slot.
let lane = max(min(mid - tw - 3.5, 7.0), 2.0)
let lb = (mid - tw - 2.5) - b
let cell = cp_ladder(a - foot + 1.5, lb, travel + 3.0, lane, 4.0, self.slide_pos * live, px)
res = Finish.over(res, vec4(cell.rgb, 1.0) * cell.a * step(0.0, lb))
// The scale on the other side: a tick every 8 points, the ends and the
// middle longer.
let pitch = 8.0
let k = floor((a - foot) / pitch + 0.5)
let ka = foot + k * pitch
let inrange = step(0.0, k) * step(ka, foot + travel + 0.1)
let major = clamp(step(k, 0.1) + step(abs(ka - (foot + travel)), pitch * 0.5) + step(abs(k - floor(travel / pitch * 0.5 + 0.5)), 0.1), 0.0, 1.0)
let tk = mid + tw + 2.5
let tlen = mix(2.5, 4.5, major)
let tick = Finish.cover(abs(a - ka) - px * 0.5, px) * step(tk, b) * step(b, tk + tlen) * inrange
res = Finish.over(res, vec4(0.08, 0.94, 1.0, 1.0) * tick * mix(0.35, 0.7, major) * mix(1.0, 0.4, self.disabled))
// The cap: a key across the slot.
let cross = min(max(across * self.cap_cross, 10.0), across)
let lead = Finish.snap(at - self.cap_px * 0.5, px)
let klo = vec2(lead, mid - cross * 0.5)
let khi = vec2(lead + self.cap_px, mid + cross * 0.5)
let key = cp_key(ab, klo, khi, mix(4.0, 1.5, v), mix(1.5, 4.0, v), px, vec2(0.0, 0.0), self.hover, self.drag, 0.0, self.focus, self.disabled, vec3(0.08, 0.94, 1.0), vec3(1.0, 0.12, 0.72), 0.0, 1.0, 0.8, 1.0)
res = Finish.over(res, key)
let ka2 = klo.x + self.cap_px * 0.5
let idx = Finish.cover(max(abs(a - ka2) - px * 0.75, abs(b - mid) - cross * 0.5 + 3.0), px)
let ic = mix(mix(vec3(1.0, 0.45, 0.85), vec3(1.0, 1.0, 1.0), self.drag * 0.5), vec3(0.35, 0.40, 0.46), self.disabled)
return Finish.over(res, vec4(ic, 1.0) * idx)
}
// ---------------------------------------------------------------- fields
// Text fields: black glass in the cut with a cyan hairline, which lights
// under the hand, glows at focus and turns magenta while held. An input with
// no fill of its own (the one a slider, a combo box or a well holds) draws
// nothing.
let cp_field = fn(p: vec2, size: vec2, o: vec2, px: float, br: float, hover: float, down: float, focus: float, disabled: float) -> vec4 {
let lo = vec2(cp_snap(0.0, o.x, px), cp_snap(0.0, o.y, px))
let hi = vec2(cp_snap(size.x, o.x, px), cp_snap(size.y, o.y, px))
let live = 1.0 - disabled
let fill = mix(vec3(0.016, 0.026, 0.068), vec3(0.05, 0.055, 0.065), disabled)
var ink = mix(vec3(0.08, 0.94, 1.0), vec3(1.0, 0.12, 0.72), down * hover * live)
ink = mix(ink, vec3(0.30, 0.38, 0.44), disabled)
let rim = clamp(0.78 + 0.12 * hover + 0.3 * focus, 0.0, 1.0) * live + 0.22 * disabled
return cp_glass(p, lo, hi, br * 2.8, br, px, fill, ink, rim, focus * live)
}
mod.widgets.TextInputFlat.draw_bg.pixel = fn() {
if self.color.a < 0.01 {
return vec4(0.0, 0.0, 0.0, 0.0)
}
let px = cp_px(self.draw_pass.dpi_factor)
return cp_field(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, self.border_radius, self.hover, self.down, self.focus, self.disabled)
}
mod.widgets.TextInputFlat.draw_cursor.color = #14f0ff
mod.widgets.TextInputFlat.draw_text.color = #e6fbff
mod.widgets.TextInputFlat.draw_text.color_empty = #6b8499
mod.widgets.TextInputFlat.draw_text.color_disabled = #4f6272
// The number field: the same glass; its stepper two cyan chevrons behind a
// hairline, magenta while one is pressed.
mod.widgets.NumberField.draw_bg.pixel = fn() {
let px = cp_px(self.draw_pass.dpi_factor)
return cp_field(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, self.border_radius, self.hover, 0.0, self.focus, self.disabled)
}
mod.widgets.NumberField.spin_width = 16.0
mod.widgets.NumberSpin.width = 16.0
mod.widgets.NumberSpin.draw_text.color = #00000000
mod.widgets.NumberSpin.draw_text.color_disabled = #00000000
mod.widgets.NumberSpin.draw_bg.pixel = fn() {
let px = cp_px(self.draw_pass.dpi_factor)
let o = self.rect_pos
let p = self.pos * self.rect_size
let live = 1.0 - self.disabled
let hu = max(0.0, self.hot)
let hd = max(0.0, -self.hot)
let du = max(0.0, self.down)
let dd = max(0.0, -self.down)
let x0 = cp_snap(0.0, o.x, px)
let mid = cp_snap(self.rect_size.y * 0.5, o.y, px)
// The hairline between the value and the stepper.
let div = step(x0, p.x) * step(p.x, x0 + px) * step(5.0, p.y) * step(p.y, self.rect_size.y - 5.0)
var res = vec4(0.08, 0.94, 1.0, 1.0) * div * 0.30 * live
let cx = self.rect_size.x * 0.5
let cu = mid - 4.0
let cd = mid + 4.0
let du2 = min(cp_seg(p, vec2(cx - 3.0, cu + 1.5), vec2(cx, cu - 1.5)), cp_seg(p, vec2(cx, cu - 1.5), vec2(cx + 3.0, cu + 1.5))) - 0.6
let dd2 = min(cp_seg(p, vec2(cx - 3.0, cd - 1.5), vec2(cx, cd + 1.5)), cp_seg(p, vec2(cx, cd + 1.5), vec2(cx + 3.0, cd - 1.5))) - 0.6
let ink_u = mix(mix(vec3(0.08, 0.94, 1.0) * (0.7 + 0.3 * hu), vec3(1.0, 0.12, 0.72), du), vec3(0.30, 0.38, 0.44), self.disabled)
let ink_d = mix(mix(vec3(0.08, 0.94, 1.0) * (0.7 + 0.3 * hd), vec3(1.0, 0.12, 0.72), dd), vec3(0.30, 0.38, 0.44), self.disabled)
res = Finish.over(res, vec4(ink_u, 1.0) * Finish.cover(du2, px))
return Finish.over(res, vec4(ink_d, 1.0) * Finish.cover(dd2, px))
}
// The chooser: the field's glass with a magenta chevron near its right end;
// the hairline lights while its list is open.
let cp_chevron = fn(dst: vec4, p: vec2, x: float, y: float, px: float, ink: vec3) -> vec4 {
let d1 = cp_seg(p, vec2(x - 3.5, y - 1.8), vec2(x, y + 1.8))
let d2 = cp_seg(p, vec2(x, y + 1.8), vec2(x + 3.5, y - 1.8))
let ar = Finish.cover(min(d1, d2) - 0.6, px)
return Finish.over(dst, vec4(ink, 1.0) * ar)
}
mod.widgets.DropDownFlat.padding = mod.turtle.Inset{left: 8.0 right: 24.0 top: 4.0 bottom: 4.0}
mod.widgets.DropDownFlat.clip_x = false
mod.widgets.DropDownFlat.clip_y = false
mod.widgets.DropDownFlat.draw_bg.vertex = fn() {
let m = 6.0
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
mod.widgets.DropDownFlat.draw_bg.pixel = fn() {
let m = 6.0
let px = cp_px(self.draw_pass.dpi_factor)
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let lit = max(self.focus, self.active)
var res = cp_field(p, self.rect_size, self.rect_pos, px, self.border_radius, self.hover, self.down, lit, self.disabled)
let ink = mix(mix(vec3(1.0, 0.16, 0.74), vec3(1.0, 0.55, 0.88), self.hover), vec3(0.30, 0.38, 0.44), self.disabled)
return cp_chevron(res, p, self.rect_size.x - 12.0, self.rect_size.y * 0.5, px, ink)
}
mod.widgets.ComboBoxFlat.padding = mod.turtle.Inset{left: 8.0 right: 24.0 top: 4.0 bottom: 4.0}
mod.widgets.ComboBoxFlat.input.min_height = 0.0
mod.widgets.ComboBoxFlat.clip_x = false
mod.widgets.ComboBoxFlat.clip_y = false
mod.widgets.ComboBoxFlat.draw_bg.vertex = fn() {
let m = 6.0
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
mod.widgets.ComboBoxFlat.draw_bg.pixel = fn() {
let m = 6.0
let px = cp_px(self.draw_pass.dpi_factor)
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
var res = cp_field(p, self.rect_size, self.rect_pos, px, self.border_radius, self.hover, 0.0, self.focus, self.disabled)
let ink = mix(mix(vec3(1.0, 0.16, 0.74), vec3(1.0, 0.55, 0.88), self.hover), vec3(0.30, 0.38, 0.44), self.disabled)
return cp_chevron(res, p, self.rect_size.x - 12.0, self.rect_size.y * 0.5, px, ink)
}
// Open lists: the menu floor (#0b0d12) in the cut with a cyan hairline; a
// row under the hand takes a cyan wash and a tick at its left, the chosen
// row a magenta one.
let cp_menu = fn(p: vec2, size: vec2, px: float) -> vec4 {
let c = size * 0.5
let d = cp_cut(p, c, c, 7.0, 2.5)
var col = vec3(0.043, 0.051, 0.071)
col = mix(col, vec3(0.08, 0.94, 1.0), (1.0 - smoothstep(0.3, 1.0, abs(d + 0.6))) * 0.5)
let a = Finish.cover(d, px)
return vec4(col * a, a)
}
let cp_row = fn(p: vec2, size: vec2, px: float, hover: float, active: float, disabled: float) -> vec4 {
let live = 1.0 - disabled
let ink = mix(vec3(0.08, 0.94, 1.0), vec3(1.0, 0.12, 0.72), active)
let wash = (0.10 * hover + 0.20 * active) * live
let tick = step(2.0, p.x) * step(p.x, 4.0) * step(4.0, p.y) * step(p.y, size.y - 4.0) * max(hover, active) * live
let res = vec4(ink * wash, wash)
return Finish.over(res, vec4(ink, 1.0) * tick)
}
mod.widgets.PopupMenuFlat.draw_bg.pixel = fn() {
let px = cp_px(self.draw_pass.dpi_factor)
return cp_menu(self.pos * self.rect_size, self.rect_size, px)
}
mod.widgets.PopupMenuItem.draw_bg.pixel = fn() {
let px = cp_px(self.draw_pass.dpi_factor)
return cp_row(self.pos * self.rect_size, self.rect_size, px, self.hover, self.active, self.disabled)
}
mod.widgets.PopupMenuItem.draw_text.color_active = #ffffff
mod.widgets.ComboBoxFlat.draw_popup_bg.pixel = fn() {
let px = cp_px(self.draw_pass.dpi_factor)
return cp_menu(self.pos * self.rect_size, self.rect_size, px)
}
mod.widgets.ComboBoxFlat.draw_item.pixel = fn() {
let px = cp_px(self.draw_pass.dpi_factor)
return cp_row(self.pos * self.rect_size, self.rect_size, px, self.hover, self.active, 0.0)
}
mod.widgets.ComboBoxFlat.draw_item_text.color_active = #ffffff
// ---------------------------------------------------------------- tabs
// The segmented control: the strip is black glass with a cyan hairline; the
// chosen answer is a block of dark teal glass with a cyan underline, its
// word white, the others the muted blue-grey.
mod.widgets.SegmentedControlFlat.draw_bg.vertex = fn() {
let m = 6.0
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
mod.widgets.SegmentedControlFlat.draw_bg.pixel = fn() {
let m = 6.0
let px = cp_px(self.draw_pass.dpi_factor)
let o = self.rect_pos
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let lo = vec2(cp_snap(0.0, o.x, px), cp_snap(0.0, o.y, px))
let hi = vec2(cp_snap(self.rect_size.x, o.x, px), cp_snap(self.rect_size.y, o.y, px))
return cp_glass(p, lo, hi, 7.0, 2.5, px, vec3(0.016, 0.026, 0.068), vec3(0.08, 0.94, 1.0), 0.78, 0.0)
}
mod.widgets.SegmentedControlFlat.draw_pill.pixel = fn() {
let px = cp_px(self.draw_pass.dpi_factor)
let o = self.rect_pos
let p = self.pos * self.rect_size
let lo = vec2(cp_snap(0.0, o.x, px), cp_snap(0.0, o.y, px))
let hi = vec2(cp_snap(self.rect_size.x, o.x, px), cp_snap(self.rect_size.y, o.y, px))
let c = (lo + hi) * 0.5
let d = cp_cut(p, c, (hi - lo) * 0.5, 2.5, 1.0)
let a = Finish.cover(d, px)
var col = vec3(0.035, 0.137, 0.192)
let dash = Finish.cover(abs(p.y - (hi.y - 3.0)) - 0.75, px) * step(lo.x + 5.0, p.x) * step(p.x, hi.x - 5.0)
col = mix(col, vec3(0.18, 0.855, 1.0), dash)
var res = vec4(vec3(0.08, 0.94, 1.0) * 0.35, 1.0) * Finish.cover(abs(p.y - (hi.y - 3.0)) - 2.0, px) * step(lo.x + 4.0, p.x) * step(p.x, hi.x - 4.0) * 0.3
return Finish.over(res, vec4(col * a, a))
}
mod.widgets.SegmentedControlFlat.draw_text.color = #7f97ab
mod.widgets.SegmentedControlFlat.draw_text_selected.color = #ffffff
// Tabs: a row of keys standing side by side, a point apart; the tab in use
// is on -- its pipe magenta and its face lit from the rim, as the Amp app's
// tab keys light.
let cp_tab = fn(pos: vec2, size: vec2, o: vec2, dpi: float, hover: float, active: float) -> vec4 {
let m = 8.0
let px = cp_px(dpi)
let p = pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let lo = vec2(cp_snap(1.0, o.x, px), cp_snap(1.0, o.y, px))
let hi = vec2(cp_snap(size.x - 1.0, o.x, px), cp_snap(size.y - 1.0, o.y, px))
return cp_key(p, lo, hi, 7.0, 2.5, px, o, hover, 0.0, active, 0.0, 0.0, vec3(0.08, 0.94, 1.0), vec3(1.0, 0.12, 0.72), 0.0, 1.0, 1.0, 1.0)
}
mod.widgets.Tabs.clip_x = false
mod.widgets.Tabs.clip_y = false
mod.widgets.Tabs.draw_tab.vertex = fn() {
let m = 8.0
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
mod.widgets.Tabs.draw_tab_hover.vertex = fn() {
let m = 8.0
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
mod.widgets.Tabs.draw_tab_active.vertex = fn() {
let m = 8.0
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
mod.widgets.Tabs.draw_bg.pixel = fn() {
return vec4(0.0, 0.0, 0.0, 0.0)
}
mod.widgets.Tabs.draw_tab.pixel = fn() {
return cp_tab(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, 0.0, 0.0)
}
mod.widgets.Tabs.draw_tab_hover.pixel = fn() {
return cp_tab(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, 1.0, 0.0)
}
mod.widgets.Tabs.draw_tab_active.pixel = fn() {
return cp_tab(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, 0.0, 1.0)
}
mod.widgets.Tabs.draw_sep.pixel = fn() {
return vec4(0.0, 0.0, 0.0, 0.0)
}
mod.widgets.Tabs.draw_mark.color = #ff4fd0
mod.widgets.Tabs.draw_text.color = #c8faff
mod.widgets.Tabs.draw_text_active.color = #ffffff
// ---------------------------------------------------------------- screens
// A SCREEN: a black bezel in the cut with a neon tube in it, magenta at the
// left running into cyan at the right, round a face of black glass with a
// faint dot matrix; the glass over it keeps a static chroma split round its
// rim -- a magenta outline shifted left, a cyan one right -- and a soft
// reflection sheet. Nothing is drawn over the picture itself.
mod.widgets.ScreenView.draw_bg.border_radius = uniform(7.0)
mod.widgets.ScreenView.clip_x = false
mod.widgets.ScreenView.clip_y = false
mod.widgets.ScreenView.draw_bg.vertex = fn() {
let m = 6.0
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
mod.widgets.ScreenView.draw_bg.pixel = fn() {
let m = 6.0
let px = cp_px(self.draw_pass.dpi_factor)
let o = self.rect_pos
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let lo = vec2(cp_snap(0.0, o.x, px), cp_snap(0.0, o.y, px))
let hi = vec2(cp_snap(self.rect_size.x, o.x, px), cp_snap(self.rect_size.y, o.y, px))
let c = (lo + hi) * 0.5
let h = (hi - lo) * 0.5
let big = self.border_radius
let small = big * 0.36
let d = cp_cut(p, c, h, big, small)
let a = Finish.cover(d, px)
let uv = clamp((p - lo) / max(hi - lo, vec2(1.0, 1.0)), vec2(0.0, 0.0), vec2(1.0, 1.0))
let ink = cp_neon(1.0 - smoothstep(0.1, 0.9, uv.x))
// What the screen throws: its neon's breath and a shadow under it.
let sh = cp_cut_shade(p, c, h, big, small, vec2(0.0, 2.0), 3.0) * 0.5
var res = vec4(0.0, 0.0, 0.0, sh * (1.0 - a))
res = Finish.over(res, vec4(ink, 1.0) * Finish.glow(d, 2.0) * 0.20 * (1.0 - a))
// The bezel, black, and the face inside it.
let b = max(self.bezel, 1.5)
let fd = cp_cut(p, c, h - vec2(b, b), max(big - b * 0.6, 1.0), max(small - b * 0.4, 0.5))
var col = vec3(0.039, 0.043, 0.055)
let f = fract((p - lo) / vec2(3.0, 3.0)) - vec2(0.5, 0.5)
let dotm = 1.0 - smoothstep(0.18, 0.32, length(f))
var face = self.color.rgb + vec3(0.0, 0.035, 0.045) * dotm
if self.recess > 0.0 {
face = face * (1.0 - 0.45 * (1.0 - smoothstep(0.0, self.recess * 1.5, p.y - lo.y - b)))
}
if self.scan > 0.0 {
face = face * Finish.scan(p.y - lo.y, px, 3.0 * px, self.scan)
}
col = mix(col, face, Finish.cover(fd, px))
// The tube in the bezel.
col = col + cp_tube(d, b * 0.5, 0.7, 2.2, ink)
return Finish.over(res, vec4(min(col, vec3(1.0, 1.0, 1.0)) * a, a))
}
mod.widgets.ScreenView.draw_glass.pixel = fn() {
let p = self.pos * self.rect_size
let px = cp_px(self.draw_pass.dpi_factor)
let c = self.rect_size * 0.5
let b = max(self.bezel, 1.5)
let h = c - vec2(b, b)
let big = max(self.face_radius - b * 0.6, 1.0)
let small = max(self.face_radius * 0.36 - b * 0.4, 0.5)
let cov = Finish.cover(cp_cut(p, c, h, big, small), px)
let dm = cp_cut(p + vec2(1.2, 0.0), c, h, big, small)
let dc = cp_cut(p - vec2(1.2, 0.0), c, h, big, small)
let rm = 1.0 - smoothstep(0.2, 0.8, abs(dm + 2.5))
let rc = 1.0 - smoothstep(0.2, 0.8, abs(dc + 2.5))
let r = smoothstep(0.6, 0.0, self.pos.y + (self.pos.x - 0.5) * 0.3) * self.sheen
var add = vec3(1.0, 0.12, 0.72) * rm * 0.40 + vec3(0.08, 0.94, 1.0) * rc * 0.40
add = add + vec3(0.60, 0.90, 1.0) * r
// Light only: added to what the screen shows, never covering it.
return vec4(add * cov, 0.0)
}
mod.widgets.ScreenView.sheen = 0.05
// THE NEEDLE METER: a screen of black glass in the cut with a cyan hairline
// and a faint HUD grid; the scale printed in cyan, the zone a band of
// magenta, and a white-hot needle with a magenta ghost to its left and a
// cyan one to its right, rooted under a small key.
mod.widgets.NeedleMeter.draw_bg.border_radius = uniform(9.0)
mod.widgets.NeedleMeter.draw_bg.pixel = fn() {
let p = self.pos * self.rect_size
let px = cp_px(self.draw_pass.dpi_factor)
let o = self.rect_pos
let lo = vec2(cp_snap(0.0, o.x, px), cp_snap(0.0, o.y, px))
let hi = vec2(cp_snap(self.rect_size.x, o.x, px), cp_snap(self.rect_size.y, o.y, px))
let c = (lo + hi) * 0.5
let hc = (hi - lo) * 0.5
let fd = cp_cut(p, c, hc, self.border_radius, self.border_radius * 0.3)
let fa = Finish.cover(fd, px)
let fg = fract((p - lo) / vec2(16.0, 16.0))
let grid = max(1.0 - smoothstep(0.0, 0.05, fg.x), 1.0 - smoothstep(0.0, 0.05, fg.y))
var col = vec3(0.027, 0.031, 0.047) + vec3(0.08, 0.94, 1.0) * grid * 0.035
let pv = self.pivot()
let rr = self.scale_radius()
let q = p - pv
let r = length(q)
let ang = atan2(q.x, 0.0 - q.y)
let sweep = self.sweep * 0.017453293
let t = ang / sweep + 0.5
let on_scale = step(-0.0001, t) * step(t, 1.0001)
let arc = Finish.cover(abs(r - rr) - px * 0.5, px) * on_scale
let n = max(floor(self.majors + 0.5), 1.0) * max(floor(self.minors + 0.5), 1.0)
let k = floor(clamp(t, 0.0, 1.0) * n + 0.5)
let ak = (k / n - 0.5) * sweep
let perp = abs(q.x * cos(ak) + q.y * sin(ak))
let mn = max(floor(self.minors + 0.5), 1.0)
let major = 1.0 - step(0.5, k - mn * floor(k / mn + 0.0001))
let len = mix(self.tick_minor, self.tick_major, major)
let radial = max(rr - len - r, r - rr)
let tick = Finish.cover(perp - px * mix(0.5, 1.0, major), px) * Finish.cover(radial, px) * step(0.0, -q.y * cos(ak) + q.x * sin(ak))
col = mix(col, vec3(0.08, 0.94, 1.0), max(arc * 0.45, tick * mix(0.55, 1.0, major)))
if self.zone < 0.999 {
let zone_t = Finish.cover((self.zone - t) * sweep * r, px) * Finish.cover((t - 1.0) * sweep * r, px)
let zone_r = Finish.cover(max(rr + px - r, r - rr - 2.5), px)
col = mix(col, vec3(1.0, 0.12, 0.72), zone_t * zone_r * 0.9)
}
// The needle and its two ghosts.
let na = self.needle_angle(self.value)
let nd = vec2(sin(na), 0.0 - cos(na))
let nn = vec2(-nd.y, nd.x)
let tail = max(self.pivot_size - px, 0.0)
let along = dot(q, nd)
let across = dot(q, nn)
let span = Finish.cover(along - (rr + 3.0), px) * Finish.cover(0.0 - along - tail, px)
let core = Finish.cover(abs(across) - 0.5, px) * span
let gm = (1.0 - smoothstep(0.3, 1.1, abs(across + 1.4))) * 0.7 * span
let gc = (1.0 - smoothstep(0.3, 1.1, abs(across - 1.4))) * 0.7 * span
col = col + vec3(1.0, 0.14, 0.72) * gm * 0.8 + vec3(0.08, 0.94, 1.0) * gc * 0.8
col = mix(col, vec3(0.92, 1.0, 1.0), core)
// The pivot: a small dark key with a magenta lamp.
let pr = max(self.pivot_size, 1.5)
let pd = cp_cut(p, pv, vec2(pr, pr), pr * 0.6, pr * 0.2)
col = mix(col, vec3(0.13, 0.14, 0.18), Finish.cover(pd, px))
col = mix(col, vec3(1.0, 0.45, 0.85), Finish.cover(length(q) - pr * 0.35, px))
// The hairline round the face.
col = mix(col, vec3(0.08, 0.94, 1.0), (1.0 - smoothstep(0.3, 1.0, abs(fd + 0.6))) * 0.45)
return vec4(min(col, vec3(1.0, 1.0, 1.0)) * fa, fa) * self.opacity
}
// ---------------------------------------------------------------- surfaces
// The scroll bar: a thin slot, its thumb a slanted bar of cyan, brighter
// under the hand.
mod.widgets.ScrollBar.bar_size = 7.0
mod.widgets.ScrollBar.draw_bg.pixel = fn() {
let px = cp_px(self.draw_pass.dpi_factor)
let sz = self.rect_size
let p = self.pos * sz
let v = self.is_vertical
let t0 = mix(sz.x * self.norm_scroll, sz.y * self.norm_scroll, v)
let t1 = t0 + mix(sz.x * self.norm_handle, sz.y * self.norm_handle, v)
let a = mix(p.x, p.y, v)
let b = mix(p.y, p.x, v)
let across = mix(sz.y, sz.x, v)
let mid = across * 0.5
let slot = Finish.cover(abs(b - mid) - 0.5, px) * 0.35
var res = vec4(0.08, 0.94, 1.0, 1.0) * slot * 0.25
let hw = max(across * 0.5 - 2.0, 1.0)
let sb = b - mid
let lead = a - sb * 0.6
let thumb = Finish.cover(max(max(t0 + 1.0 - lead, lead - t1 + 1.0), abs(sb) - hw), px)
let lit = max(self.hover, self.drag)
let col = mix(vec3(0.08, 0.94, 1.0) * 0.55, mix(vec3(0.08, 0.94, 1.0), vec3(1.0, 0.12, 0.72), self.drag), lit)
res = Finish.over(res, vec4(col, 1.0) * thumb * (0.75 + 0.25 * lit))
return res * self.opacity
}
mod.widgets.PanelView.clip_x = false
mod.widgets.PanelView.clip_y = false
mod.widgets.PanelView.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 12.0 bottom: 12.0}
mod.widgets.PanelView.draw_bg.vertex = fn() {
let m = 10.0
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
mod.widgets.PanelView.draw_bg.pixel = fn() {
let m = 10.0
let px = cp_px(self.draw_pass.dpi_factor)
let o = self.rect_pos
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let lo = vec2(cp_snap(0.0, o.x, px), cp_snap(0.0, o.y, px))
let hi = vec2(cp_snap(self.rect_size.x, o.x, px), cp_snap(self.rect_size.y, o.y, px))
if self.sunken > 0.5 {
return cp_tray(p, lo, hi, 10.0, 3.0, px)
}
return cp_plate(p, lo, hi, 12.0, 4.0, px, o, 0.0, 1.0, 1.0)
}
// The rounded view keeps the colour its caller gives it, cut on the chamfer,
// with its hairline when it has one.
mod.widgets.RoundedView.draw_bg.pixel = fn() {
let px = cp_px(self.draw_pass.dpi_factor)
let p = self.pos * self.rect_size
let lo = vec2(self.border_inset.x, self.border_inset.y)
let hi = self.rect_size - vec2(self.border_inset.z, self.border_inset.w)
let c = (lo + hi) * 0.5
let h = max((hi - lo) * 0.5, vec2(0.5, 0.5))
let r = max(self.border_radius, 1.0)
let d = cp_cut(p, c, h, r * 2.8, r)
var fill = self.color
if self.color_2.x > -0.5 {
fill = mix(self.color, self.color_2, mix(self.pos.y, self.pos.x, self.gradient_fill_horizontal))
}
let ink = self.border_color
let bw = max(self.border_size, 0.0)
let band = Finish.band(d, 0.0, max(bw, px), px) * step(0.01, bw) * ink.a
let a = Finish.cover(d, px)
let col = mix(fill.rgb, ink.rgb, band)
return vec4(col * a * fill.a, a * fill.a) + vec4(ink.rgb, 1.0) * band * a * (1.0 - fill.a)
}
// The card: a plate of its own; a pressable card brightens under the hand.
mod.widgets.Card.clip_x = false
mod.widgets.Card.clip_y = false
mod.widgets.Card.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 12.0 bottom: 12.0}
mod.widgets.Card.draw_bg.vertex = fn() {
let m = 10.0
return self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
mod.widgets.Card.draw_bg.pixel = fn() {
let m = 10.0
let px = cp_px(self.draw_pass.dpi_factor)
let o = self.rect_pos
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let lo = vec2(cp_snap(0.0, o.x, px), cp_snap(0.0, o.y, px))
let hi = vec2(cp_snap(self.rect_size.x, o.x, px), cp_snap(self.rect_size.y, o.y, px))
return cp_plate(p, lo, hi, 12.0, 4.0, px, o, self.hover, 1.0, 0.0)
}
// List rows: under the hand a cyan wash; the chosen row is dark teal glass
// in a small cut with a cyan hairline over and under it and a magenta lamp
// bar at its left.
mod.widgets.ListItem.draw_bg.pixel = fn() {
let px = cp_px(self.draw_pass.dpi_factor)
let o = self.rect_pos
let p = self.pos * self.rect_size
let live = 1.0 - self.disabled
var res = vec4(0.08, 0.94, 1.0, 1.0) * (0.06 * self.hover + 0.04 * self.down) * live
let lo = vec2(cp_snap(0.0, o.x, px), cp_snap(0.0, o.y, px))
let hi = vec2(cp_snap(self.rect_size.x, o.x, px), cp_snap(self.rect_size.y, o.y, px))
let c = (lo + hi) * 0.5
let d = cp_cut(p, c, (hi - lo) * 0.5, 5.0, 1.5)
let t = clamp((p.y - lo.y) / max(hi.y - lo.y, 1.0), 0.0, 1.0)
var col = mix(vec3(0.050, 0.185, 0.220), vec3(0.040, 0.135, 0.165), t)
col = mix(col, vec3(0.08, 0.94, 1.0), (1.0 - smoothstep(0.3, 1.0, abs(d + 0.6))) * 0.55)
let a = Finish.cover(d, px)
var sel = vec4(col * a, a)
let lamp = Finish.cover(max(abs(p.x - (lo.x + 5.0)) - 1.0, abs(p.y - c.y) - max((hi.y - lo.y) * 0.25, 2.0)), px)
sel = Finish.over(sel, vec4(1.0, 0.12, 0.72, 1.0) * exp(-max(abs(p.x - (lo.x + 5.0)) - 1.0, 0.0) / 2.0) * 0.4 * a * live)
sel = Finish.over(sel, vec4(1.0, 0.55, 0.88, 1.0) * lamp * live)
return mix(res, sel, self.selected)
}
mod.widgets.ListItem.draw_text.color = #e6fbff
mod.widgets.ListItem.draw_text_2.color = #7390a6
// A chip is a small seated key; its role colour tints the face.
mod.widgets.ChipFlat.draw_bg.pixel = fn() {
let px = cp_px(self.draw_pass.dpi_factor)
let o = self.rect_pos
let p = self.pos * self.rect_size
let lo = vec2(cp_snap(0.0, o.x, px), cp_snap(0.0, o.y, px))
let hi = vec2(cp_snap(self.rect_size.x, o.x, px), cp_snap(self.rect_size.y, o.y, px))
let l = Finish.lum(self.color.rgb)
let tint = (self.color.rgb - vec3(l, l, l)) * 0.35 * self.color.a
var res = cp_key(p, lo, hi, 6.0, 2.0, px, o, self.hover, self.down, self.keyed, 0.0, 0.0, vec3(0.08, 0.94, 1.0), vec3(1.0, 0.12, 0.72), 0.0, 1.0, 0.5, 0.0)
let a = res.a
return vec4(res.rgb + tint * a, a)
}
// A count is a small chip of black glass with a magenta hairline.
mod.widgets.BadgePalette.neutral = #0b0d12
mod.widgets.BadgePalette.on_neutral = #e6fbff
mod.widgets.BadgeFlat.draw_bg.pixel = fn() {
let px = cp_px(self.draw_pass.dpi_factor)
let o = self.rect_pos
let p = self.pos * self.rect_size
let lo = vec2(cp_snap(0.0, o.x, px), cp_snap(0.0, o.y, px))
let hi = vec2(cp_snap(self.rect_size.x - px * 0.49, o.x, px), cp_snap(self.rect_size.y - px * 0.49, o.y, px))
let c = (lo + hi) * 0.5
let h = (hi - lo) * 0.5
let d = cp_cut(p, c, h, min(h.y * 0.8, 5.0), 1.5)
var col = self.color.rgb
col = mix(col, vec3(1.0, 0.12, 0.72), (1.0 - smoothstep(0.3, 1.0, abs(d + 0.6))) * 0.8)
let a = Finish.cover(d, px) * max(self.color.a, 0.6)
return vec4(col * a, a)
}
// ---------------------------------------------------------------- field row
// A ROW OF FIELDS IS ONE HEIGHT. Left to themselves the library's fields each
// come out at a size of their own -- in the widget catalogue's toolbar the
// search box, the number beside it and the drop down after it stood at three
// different heights -- and a sheet that reshapes the text box alone widens
// that gap instead of closing it. So the box metrics are stated ONCE here and
// handed to each field below, and a field added to the library later joins
// the row by being named here rather than by having these numbers copied
// into it. Fields and keys share the one 26 point row.
let field_height = 26.0
let field_pad_v = 4.0
let field_radius = mod.theme.corner_radius
let field_border = mod.theme.beveling
// The room a Fit field takes. A minimum height rather than a height,
// because a line of text is shorter than a row, and the padding with it:
// where a field is Fit, the padding IS its height, and two fields padded
// differently part company again the moment the type grows.
let field_room = fn(w) {
w.min_height = field_height
w.padding.top = field_pad_v
w.padding.bottom = field_pad_v
}
// The box a field draws. Given the draw object and not the widget, because
// not every field draws its own box, and because writing a corner onto a
// draw struct that has no corner would add a slot its shader never reads.
let field_face = fn(d) {
d.border_radius = field_radius
d.border_size = field_border
}
// A field that states a frame and lays its own parts out inside it: a number
// with a stepper, a date, a time. The height is all these take. A padding
// here would NOT hold the line off the box -- each of them measures its
// parts against the turtle's whole rect and draws them from the content
// origin, so the padding displaces the line instead of insetting it, and the
// number came out sitting on the bottom edge with its arrows split around it.
let field_frame = fn(w) {
w.min_height = field_height
}
field_room(mod.widgets.TextInput)
field_face(mod.widgets.TextInput.draw_bg)
field_room(mod.widgets.ComboBox)
field_face(mod.widgets.ComboBox.draw_bg)
field_room(mod.widgets.DropDown)
field_face(mod.widgets.DropDown.draw_bg)
field_room(mod.widgets.DropDown2)
field_face(mod.widgets.DropDown2.draw_bg)
field_room(mod.widgets.FieldWell)
field_face(mod.widgets.FieldWell.draw_bg)
field_room(mod.widgets.TagField)
field_face(mod.widgets.TagField.draw_bg)
field_frame(mod.widgets.NumberField)
// The number's box strokes its own hairline and declares no border size.
mod.widgets.NumberField.draw_bg.border_radius = field_radius
// The drag-number's corner is written into its own shader, so it takes the
// room and keeps the box it draws.
field_frame(mod.widgets.ValueInput)
field_frame(mod.widgets.DateField)
field_frame(mod.widgets.TimeField)
// A picker is a date field in an anchor: the field inside fills the frame.
field_frame(mod.widgets.DatePicker)
field_frame(mod.widgets.DateRangePicker)
// The chrome-less input a well holds must impose no height of its own. It is
// a TextInput, so it inherits the minimum above, and a minimum as tall as the
// whole field, applied inside a well already that tall, pushed the line out
// through the bottom of the box.
mod.widgets.WellInput.min_height = 0.0
// ---------------------------------------------------------------- rules
// A rule is a cyan hairline at a third of its strength, a short magenta
// tick at its start.
mod.widgets.Hr.draw_bg.pixel = fn() {
let px = cp_px(self.draw_pass.dpi_factor)
let p = self.pos * self.rect_size
let y0 = cp_snap(self.rect_size.y * 0.5 - px * 0.5, self.rect_pos.y, px)
let line = step(y0, p.y) * step(p.y, y0 + px)
let tick = step(p.x, 14.0)
return vec4(mix(vec3(0.08, 0.94, 1.0) * 0.32, vec3(1.0, 0.12, 0.72) * 0.8, tick), 1.0) * line
}
mod.widgets.Vr.draw_bg.pixel = fn() {
let px = cp_px(self.draw_pass.dpi_factor)
let p = self.pos * self.rect_size
let x0 = cp_snap(self.rect_size.x * 0.5 - px * 0.5, self.rect_pos.x, px)
let line = step(x0, p.x) * step(p.x, x0 + px)
let tick = step(p.y, 14.0)
return vec4(mix(vec3(0.08, 0.94, 1.0) * 0.32, vec3(1.0, 0.12, 0.72) * 0.8, tick), 1.0) * line
}
// ---------------------------------------------------------------- ground
// Plain views stop clipping so a shadow or a glow can fall past their edge;
// scroll views keep clipping their content.
mod.widgets.View.clip_x = false
mod.widgets.View.clip_y = false
mod.widgets.Window.pass.clear_color = #121317
mod.widgets.Window.caption_bar.caption_label.padding = mod.turtle.Inset{left: 8.0}
mod.widgets.Window.caption_bar.caption_label.label.draw_text.color = #14f0ff
mod.widgets.Modal.bg_view.draw_bg.color = #000000c0
// The window's own ground (`cp_hull`): the plate in its frame.
mod.widgets.Window.show_bg = true
// It is opaque (alpha 1): drawn without blending, the tile GPU's hidden-surface
// removal skips it under every opaque draw above it (a game's 3D view).
mod.widgets.Window.draw_bg.alpha_blend = false
// Nothing in it moves (it reads no clock), and it is the costliest shader of
// a frame (an 800-line procedural plate over every pixel): rendered once per
// size into a texture and drawn back from it.
mod.widgets.Window.cache_bg = true
mod.widgets.Window.draw_bg.pixel = fn() {
let px = cp_px(self.draw_pass.dpi_factor)
return vec4(cp_hull(self.pos * self.rect_size, self.rect_size, px), 1.0)
}
// ---------------------------------------------------------------- states
// Every held state is written as a plain value that cuts in at once (the
// library writes some as snap() values, which a state held as a default --
// the states page -- never applies). Live, the cut is the same as the snap.
mod.widgets.ButtonFlat.animator.hover.on.from = {all: Snap}
mod.widgets.ButtonFlat.animator.hover.on.apply = {draw_bg: {down: 0.0 hover: 1.0} draw_text: {down: 0.0 hover: 1.0}}
mod.widgets.ButtonFlat.animator.hover.down.from = {all: Snap}
mod.widgets.ButtonFlat.animator.hover.down.apply = {draw_bg: {down: 1.0 hover: 1.0} draw_text: {down: 1.0 hover: 1.0}}
mod.widgets.CheckBoxFlat.animator.hover.down.from = {all: Snap}
mod.widgets.CheckBoxFlat.animator.hover.down.apply = {draw_bg: {down: 1.0 hover: 1.0} draw_text: {down: 1.0 hover: 1.0}}
mod.widgets.RadioButtonFlat.animator.hover.down.from = {all: Snap}
mod.widgets.RadioButtonFlat.animator.hover.down.apply = {draw_bg: {down: 1.0 hover: 1.0} draw_text: {down: 1.0 hover: 1.0}}
mod.widgets.TextInputFlat.animator.hover.on.from = {all: Snap}
mod.widgets.TextInputFlat.animator.hover.on.apply = {draw_bg: {down: 0.0 hover: 1.0} draw_text: {down: 0.0 hover: 1.0}}
mod.widgets.TextInputFlat.animator.hover.down.from = {all: Snap}
mod.widgets.TextInputFlat.animator.hover.down.apply = {draw_bg: {down: 1.0 hover: 1.0} draw_text: {down: 1.0 hover: 1.0}}
mod.widgets.DropDownFlat.animator.hover.on.from = {all: Snap}
mod.widgets.DropDownFlat.animator.hover.on.apply = {draw_bg: {down: 0.0 hover: 1.0} draw_text: {down: 0.0 hover: 1.0}}
mod.widgets.DropDownFlat.animator.hover.down.from = {all: Snap}
mod.widgets.DropDownFlat.animator.hover.down.apply = {draw_bg: {down: 1.0 hover: 1.0} draw_text: {down: 1.0 hover: 1.0}}
// ---------------------------------------------------------------- lamps
// LAMPS: an LED, as the hardware in his pictures has them. The emitter is
// the light: at full current its middle burns toward white and its rim is
// its most saturated colour; at lower current the middle is no longer
// white, the colour deeper, the glow smaller; out, it is dark tinted glass,
// its colour barely there. Round it a bezel: a raised ring lit from the
// upper left, or a socket cut into the surface, its upper wall in shadow
// and a catch light on its lower rim. Over the lens one crisp highlight of
// a dome. Its light is added, never painted: a tight hot halo and a short
// bloom in its own hue, screened over whatever is there and gone in about
// two radii; on a pale ground, where light can barely brighten, its hue is
// also laid round it as a tint that keeps the ground's luminance (the
// library's halo rule: at most 0.45 one device pixel out, halving every
// 0.4 of its reach, gone at the reach), so it never darkens or greys its
// ground. A bar is the same light drawn out into a light pipe.
let cp_led_lamp_add = fn(res: vec4, light: vec3, ground: vec3) -> vec4 {
let est = res.rgb + ground * (1.0 - res.a)
let add = (vec3(1.0, 1.0, 1.0) - exp(-light)) * max(vec3(1.0, 1.0, 1.0) - est, vec3(0.0, 0.0, 0.0))
return vec4(res.rgb + add, res.a)
}
// An LED's colour at full strength: its brightest channel at one.
let cp_led_lamp_full = fn(ink: vec3) -> vec3 {
return ink / max(max(ink.x, max(ink.y, ink.z)), 0.01)
}
// The same colour at the emitter's own chroma: the hue kept and its
// saturation raised by `vivid` (0 leaves a pale colour pale; a colour with
// next to no hue, a white or a plain lamp, stays neutral either way).
let cp_led_lamp_hue = fn(ink: vec3, vivid: float) -> vec3 {
let f = cp_led_lamp_full(ink)
let mn = min(f.x, min(f.y, f.z))
let ch = 1.0 - mn
let pure = (f - vec3(mn, mn, mn)) / max(ch, 0.001)
return mix(f, pure, clamp(vivid, 0.0, 1.0) * 0.72 * smoothstep(0.03, 0.12, ch))
}
// The lamp: its lens a rounded box `hb` either side of `c` (round when the
// two are equal), `lit` its current 0..1, `ink` its colour and `vivid` how
// far its chroma is raised, `ground` what shows through where nothing is
// drawn, `room` how far its light may reach, `bez` and `raised` its bezel
// (raised 0: a recess cut in what is under it; 1: a raised ring of `bez`;
// 2: the same ring polished metal). Premultiplied over `dst`.
let cp_led_lamp = fn(dst: vec4, p: vec2, c: vec2, hb: vec2, lit: float, ink: vec3, vivid: float, px: float, ground: vec3, room: float, bez: vec3, raised: float) -> vec4 {
let rt = max(min(hb.x, hb.y), 0.4)
let d = Material.sd_box(p, c, hb, rt)
let bw = clamp(0.3 * rt, 0.7, 2.4)
let ds = d - bw
let L = clamp(lit, 0.0, 1.0)
let b = cp_led_lamp_full(ink)
let hue = cp_led_lamp_hue(ink, vivid)
let pw = L * sqrt(L)
let white = vec3(1.0, 1.0, 1.0)
let lum = vec3(0.2126, 0.7152, 0.0722)
let pale = smoothstep(0.35, 0.75, dot(ground, lum))
var res = dst
let x = max(ds, 0.0)
let reach = max(room, 1.0)
let win = 1.0 - smoothstep(0.55 * reach, reach, x)
// The lens's own frame: `q` in lens radii from its centre line, `t` its
// distance out, `nrm` the way its outline faces.
let ex = max(hb.x - hb.y, 0.0)
let ey = max(hb.y - hb.x, 0.0)
let qx = p.x - c.x
let qy = p.y - c.y
let q = vec2(qx - clamp(qx, 0.0 - ex, ex), qy - clamp(qy, 0.0 - ey, ey)) / rt
let t = clamp(length(q), 0.0, 1.0)
let nrm = q / max(length(q), 0.001)
// What the bead and its bezel throw on the ground: a soft shadow, down.
let shd = Finish.drop(p, c, hb + vec2(bw, bw), rt + bw, vec2(0.0, 0.22 * rt + 0.3), 0.32 * rt + px) * mix(0.18, 0.34, min(raised, 1.0)) * (1.0 - 0.6 * pw)
res = Finish.over(res, vec4(ground * 0.4 * shd, shd))
if L > 0.001 && pale > 0.001 {
// The tint, on a pale ground only: its hue laid over, never darker.
let h = clamp(0.42 * pw * pow(2.0, -x / max(0.4 * reach, 0.5)) * win, 0.0, 0.45) * pale
let est = res.rgb + ground * (1.0 - res.a)
let w = clamp(dot(hue, lum) / max(dot(est, lum), 0.001), 0.0, 1.0)
res = Finish.over(res, vec4(hue * h * w, h * w))
}
// The bezel: raised, a ring of `bez` rounded over, lit from the upper
// left; or a socket cut into what is under it, its upper wall in shadow
// and a catch light on its lower rim.
let a_s = Finish.cover(ds, px)
let u = clamp(d / max(bw, 0.01), 0.0, 1.0)
let facing = dot(nrm * (u - 0.5) * 2.0, vec2(-0.45, -0.89))
let metal = clamp(raised - 1.0, 0.0, 1.0)
var rc = bez * (0.94 + mix(0.12, 0.55, metal) * facing)
rc = mix(rc, min(bez + vec3(0.30, 0.30, 0.30), white), smoothstep(mix(0.35, 0.55, metal), 0.9, facing) * mix(0.6, 0.85, metal))
rc = mix(rc, rc * 0.8, Finish.band(ds, 0.0, max(px, 0.45), px) * smoothstep(0.0, 0.8, nrm.y) * 0.8)
let under = res.rgb / max(res.a, 0.001) * step(0.001, res.a) + ground * (1.0 - step(0.001, res.a))
let gy = clamp(nrm.y * t, -1.0, 1.0)
var sc = under * mix(0.55, 0.92, smoothstep(-0.9, 0.9, gy))
sc = mix(sc, min(under * 1.06 + vec3(0.28, 0.28, 0.28), white), Finish.band(ds, 0.0, max(px, 0.45), px) * smoothstep(0.2, 0.95, nrm.y) * 0.85)
sc = min(mix(sc, rc, min(raised, 1.0)), white)
res = Finish.over(res, vec4(sc * a_s, a_s))
// The light: a tight hot halo and a short bloom in its hue, gone in
// about two radii, falling on the bezel too.
if L > 0.001 {
let xl = max(d, 0.0)
let halo = exp(-xl / max(0.30 * rt, 0.4)) * 1.5 + exp(-xl / max(0.65 * rt, 0.7)) * 0.32
let hwin = 1.0 - smoothstep(1.1 * rt, 2.0 * rt + 1.0, xl)
res = cp_led_lamp_add(res, mix(hue, white, 0.15 * pw) * halo * pw * hwin * win, ground)
}
// THE BEAD: a glossy dome of coloured glass. Its colour deepens toward
// the top; light pools low inside, where it comes through the dome; lit,
// its middle burns toward white at full current and its rim is its most
// saturated colour.
let top = smoothstep(-1.0, 0.6, q.y)
let pool = exp(-(q.y - 0.55) * (q.y - 0.55) / 0.09 - q.x * q.x / 0.30)
var on = mix(hue * mix(0.55, 0.8, L), mix(b, hue, 0.5), top)
on = mix(on, min(mix(b, hue, 0.4) + vec3(0.35, 0.35, 0.35), white), pool * 0.5)
on = mix(on, vec3(1.0, 0.99, 0.975), exp(-t * t / 0.20) * smoothstep(0.5, 1.0, L) * 0.85)
on = mix(on, hue * (0.82 + 0.18 * L), smoothstep(0.62, 1.0, t))
on = on * (0.42 + 0.58 * L)
// Out: dark tinted glass on a dark ground, a grey bead on a pale one,
// its colour barely there, the same dome, a catch light along its lower
// rim.
let offc = mix(mix(vec3(0.085, 0.09, 0.115), ground * 0.62, pale), b * 0.4, 0.12)
var off = mix(offc * 0.62, offc * 1.08, top)
off = mix(off, min(offc + vec3(0.18, 0.18, 0.18), white), pool * 0.35)
off = off * mix(1.0, 0.75, smoothstep(0.75, 1.0, t))
let catchl = smoothstep(0.62, 0.9, t) * (1.0 - smoothstep(0.9, 1.0, t)) * smoothstep(0.2, 0.9, nrm.y)
off = off + vec3(0.30, 0.30, 0.32) * catchl * (1.0 - 0.5 * pale)
var col = mix(off, min(on, white), smoothstep(0.0, 0.35, L))
// One crisp highlight: a white oval across its upper third.
let e = length(vec2(q.x / 0.62, (q.y + 0.46) / 0.30))
let spec = (1.0 - smoothstep(0.55, 1.0, e)) * mix(1.0, 0.3, clamp((q.y + 0.76) / 0.6, 0.0, 1.0))
col = mix(col, white, spec * mix(0.8, 0.55, L) * smoothstep(0.9, 2.2, rt))
// Out, a faint seam where the dome meets its bezel; lit, none: the rim
// is the colour.
col = mix(col, col * 0.7, Finish.band(d, 0.0, px * 0.6, px) * 0.6 * (1.0 - smoothstep(0.0, 0.25, L)))
col = min(col, white)
let a_l = Finish.cover(d, px)
return Finish.over(res, vec4(col * a_l, a_l))
}
// The Lamp widget and the bar lamp: the lamp above, here in a dark gunmetal ring with its own soft shadow.
// The halo's room is held in the lamp's own box (the library reserves a
// lens thickness either side once a halo is asked for), so the light is
// never cut at its edge.
mod.widgets.Lamp.halo = 0.3
mod.widgets.Lamp.halo_reach = 1.0
mod.widgets.Lamp.draw_bg.vertex = fn() {
let m = 2.0
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
mod.widgets.Lamp.draw_bg.pixel = fn() {
let m = 2.0
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let reach = clamp(self.reach, 0.0, 1.0)
let t = self.rect_size.y / (1.0 + 2.0 * reach)
let room = t * reach
let c = self.rect_size * 0.5
let half_len = max((self.rect_size.x - 2.0 * room) * 0.5, t * 0.5)
// The lens is the lamp's size; its socket stands round it in the room.
let hb = vec2(mix(t * 0.5, half_len, self.bar), t * 0.5)
let res = cp_led_lamp(vec4(0.0, 0.0, 0.0, 0.0), p, c, hb, self.lit, self.intent_color().rgb, 1.0 - step(3.5, self.intent), px, vec3(0.071, 0.075, 0.090), max(room - 0.5, 2.0), vec3(0.20, 0.21, 0.25), 1.0)
return res * self.opacity
}
true