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