makepad/widgets/sheets/themes/glossy-anthracite/widgets.splash

3032 lines
168 KiB
Text

use mod.prelude.widgets_internal.*
use mod.sdf.Cap
// Glossy anthracite: blue-grey metal under a light from the top. Caps with
// a deep gloss fall and a specular sheen, polished metal knobs and slider
// balls, brushed plates, blue-white lamps. Every face here is drawn in true
// points (one device pixel is `px`), and every shadow, lip and glow is drawn
// OUTSIDE the layout box: the quad is grown in `vertex`, the templates that
// throw a shadow stop clipping, and no padding is spent on relief.
// ---------------------------------------------------------------- helpers
// One device pixel in points.
let an_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 an_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 an_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)
}
// A hash that does not line up: the library's hash nearly repeats every 97
// cells, so each cell is first sent to a far cell picked by its own hash.
let an_hash = fn(i: vec2) -> float {
let j = i + vec2(Finish.hash(i) * 419.0, Finish.hash(i + vec2(5.31, 9.17)) * 263.0)
return Finish.hash(j)
}
// Value noise on that hash, one unit per cell.
let an_noise = fn(q: vec2) -> float {
let i = floor(q)
let f = q - i
let u = f * f * (vec2(3.0, 3.0) - f * 2.0)
let a = an_hash(i)
let b = an_hash(i + vec2(1.0, 0.0))
let c = an_hash(i + vec2(0.0, 1.0))
let d = an_hash(i + vec2(1.0, 1.0))
return mix(mix(a, b, u.x), mix(c, d, u.x), u.y)
}
// Horizontal brush streaks, -0.5..0.5: fine lines `s` points apart across,
// long along, over a slower wander so no two strokes run the same.
let an_brush = fn(q: vec2, s: float) -> float {
let a = an_noise(vec2(q.x / (s * 60.0), q.y / s))
let b = an_noise(vec2(q.x / (s * 260.0) + 3.1, q.y / (s * 5.0) + 7.7))
return a * 0.7 + b * 0.3 - 0.5
}
// Grain, one value per device pixel, sd about 0.004 per 0.01 of `amt`.
let an_grain_at = fn(q: vec2, px: float, amt: float) -> float {
let i = floor(q / px)
return (an_hash(i) - an_hash(i + vec2(17.13, 3.71))) * amt
}
// A brushed-metal plate's own texture, in the plate's coordinates so it
// moves with it: fine streaks across, the unrepeating grain over them.
let an_plate_tex = fn(p: vec2, px: float) -> float {
return an_brush(p, 0.7) * 0.030 + an_grain_at(p, px, 0.012)
}
// The window ground at a window point `w` (points) in a window `ww` points
// wide: anthracite lit like a stage. A pool of light hangs over the top
// centre of the window (#5c6672 under it; the pictures measure #66707c at
// the page's top), falls to #2b333b over the middle of the page and to
// #1a1f25 in the lower corners.
let an_ground = fn(w: vec2, px: float, ww: float) -> vec3 {
let dx = w.x - ww * 0.5
let y = max(w.y, 0.0)
// The long fall from the middle of the window to its sides.
let hx = dx / 300.0
let base = 0.112 + 0.088 * exp(-hx * hx)
// The pool: a bright core 170 points to either side over a wide cone
// of 420, both falling over 210 points down. Its brightest ground stays
// at #5c6672, so light ink (#dde2e8) keeps 4.5:1 on it even under the
// light.
let px2 = dx / 170.0
let px3 = dx / 420.0
let py2 = y / 210.0
let pool = (0.15 * exp(-px2 * px2) + 0.085 * exp(-px3 * px3)) * exp(-py2 * py2)
// Soft shafts of the light falling through the pool, and a faint
// brushing that only shows where the light lies.
let rays = an_noise(vec2(dx / 34.0, y / 420.0 + 5.3)) - 0.5
let br = an_brush(w, 0.8)
let v = base * (1.0 + 0.05 * br) + pool * (1.0 + 0.2 * rays + 0.06 * br) + an_grain_at(w, px, 0.012)
return mix(vec3(0.855, 1.0, 1.16), vec3(0.905, 1.0, 1.10), clamp(pool * 3.2, 0.0, 1.0)) * v
}
// A raised cap: the drop and contact shadows under it, the black outline,
// the quiet face gradient, the one top line, the faint side light, the
// bottom inner rim. `lift` 1 casts the drop shadow, 0 seats the cap on the
// ground with the contact shadow only. `rim` is the bottom inner rim's ink
// (the LED's spill on a lit cap). Premultiplied.
let an_cap = fn(p: vec2, lo: vec2, hi: vec2, r: float, px: float, hover: float, down: float, disabled: float, focus: float, lift: float, tint: vec3, rim: vec4, gloss: float) -> vec4 {
let c = (lo + hi) * 0.5
let h = (hi - lo) * 0.5
let hh = hi.y - lo.y
// The shadow is sized by the cap's height up to a 24 point cap.
let sh_h = min(hh, 24.0)
let d = Material.sd_box(p, c, h, r)
let up = (1.0 - down) * (1.0 - disabled)
let small = 1.0 - step(23.5, hh)
// What the cap throws: a slab standing off the ground. A dense shadow
// right under the foot (0.9 at half a point, half at 2 points) over a
// soft fall still a fifth dark 4.5 points down and gone by 9, as the
// pictures measure it; to the sides both stay inside half an 8 point
// gap.
let drop1 = Finish.drop(p, c, h - vec2(0.06 * sh_h, 0.0), r, vec2(0.0, 0.06 * sh_h), 0.05 * sh_h) * 0.9
let drop2 = Finish.drop(p, c, h - vec2(0.14 * sh_h, 0.02 * sh_h), r, vec2(0.0, 0.12 * sh_h), 0.15 * sh_h) * 0.78
let drop = (1.0 - (1.0 - drop1) * (1.0 - drop2)) * lift * up
let contact = Finish.drop(p, c, h, r, vec2(0.0, px), px * 0.6) * 0.9 * (1.0 - 0.6 * disabled)
let sh = 1.0 - (1.0 - drop) * (1.0 - contact)
var under = vec4(0.0, 0.0, 0.0, sh)
// A held cap sits in the ground: a 1 dp lip #474c56 shows on the ground
// right under its lower edge.
let lip = Finish.ring_out(d, 0.0, px, px) * step(c.y + h.y * 0.5, p.y) * down * (1.0 - disabled)
under = Finish.over(under, vec4(0.36, 0.40, 0.45, 1.0) * lip)
// The face: blue-grey metal lit from the top, #616a75 under the rim
// falling fast to #23282e at the foot (luminance 0.41, 0.31 at a third,
// 0.24 at half, 0.17 at the foot), brushed across.
let t = clamp((p.y - lo.y) / max(hh, 1.0), 0.0, 1.0)
let ft = 1.0 - pow(1.0 - t, 1.6)
var face = mix(vec3(0.380, 0.415, 0.458), vec3(0.137, 0.157, 0.180), ft) + tint
let tx = an_plate_tex(p, px)
face = face + vec3(tx, tx, tx * 1.05) * 0.8 * up
// Under the hand the face lifts; a small cap (a check box, a radio, a
// thumb, a handle) lifts twice as far, or the change is lost on it.
face = face + vec3(0.050, 0.053, 0.060) * (1.0 + small) * hover * up
// Held or chosen: sunk like the chosen cell of the segmented strip -- a
// floor that falls a little, and a shade under the top edge that fades
// over a fifth of the height. `tint` casts a chosen well blue.
let shade = 0.55 * (1.0 - smoothstep(0.0, 0.22 * min(hh, 24.0), p.y - lo.y - px))
let held = mix(mix(vec3(0.125, 0.145, 0.172), vec3(0.094, 0.110, 0.133), t) + tint, vec3(0.0, 0.0, 0.0), shade)
face = mix(face, held, down * (1.0 - disabled))
face = mix(face, mix(vec3(0.176, 0.196, 0.224), vec3(0.149, 0.165, 0.192), t), disabled)
// Which way the outline faces here: the lines below follow it, so a
// round cap gets an arc where a square one gets a straight line.
let s = vec2(step(0.0, p.x - c.x) * 2.0 - 1.0, step(0.0, p.y - c.y) * 2.0 - 1.0)
let q = abs(p - c) - (h - vec2(r, r))
let qc = max(q, vec2(0.0, 0.0))
var n = vec2(0.0, s.y)
if q.x > q.y {
n = vec2(s.x, 0.0)
}
if q.x > 0.0 && q.y > 0.0 {
n = qc / max(length(qc), 0.0001) * s
}
let r1 = Finish.band(d, px, px * 2.0, px)
let r2 = Finish.band(d, px * 2.0, px * 3.0, px)
let r3 = Finish.band(d, px * 3.0, px * 4.0, px)
// Lacquer: a sheen across the upper part of a raised face, +0.06 at the
// top, its lower edge rising toward the sides and gone before half the
// height; `gloss` 1 (buttons, thumbs, the segmented caps) takes all of
// it, 0 six tenths. A held or disabled cap is matte.
let uv = clamp((p - lo) / max(hi - lo, vec2(1.0, 1.0)), vec2(0.0, 0.0), vec2(1.0, 1.0))
face = face + vec3(0.060, 0.064, 0.070) * Finish.sheen(uv, 0.0, 0.46, 0.25) * up * (0.6 + 0.4 * gloss)
// The specular: a pale bloom on the upper left of the lacquer, where the
// light from the top left lands on every cap.
let sw = max(min(0.26 * (hi.x - lo.x), 18.0), 2.0)
let sq = (p - vec2(lo.x + min(0.22 * (hi.x - lo.x), 14.0), lo.y + 0.12 * hh)) / vec2(sw, max(0.22 * hh, 1.5))
face = face + vec3(0.130, 0.136, 0.146) * exp(-dot(sq, sq)) * up * (0.55 + 0.45 * gloss)
// The chamfer: a bevel of two device pixels inside the outline all the
// way round, lit by the way it faces. Along the top a near-white row
// (#e0e7ec, luminance 0.9 as measured on the pictures' caps), two rows
// under it fading into the face; the top-left corner catches a white
// spot; the sides a light line; the foot the lip's light.
let topw = smoothstep(0.45, 0.97, -n.y)
let botw = smoothstep(0.45, 0.97, n.y)
let sidew = smoothstep(0.6, 1.0, abs(n.x))
let hot = mix(vec3(0.780, 0.810, 0.840), vec3(0.880, 0.905, 0.925), max(gloss, hover))
face = mix(face, hot, r1 * topw * up)
face = mix(face, hot * 0.62, r2 * topw * up)
face = mix(face, hot * 0.52, r3 * topw * up * 0.45)
let nl = dot(n, vec2(-0.62, -0.78))
face = mix(face, vec3(0.97, 0.98, 1.0), (r1 + 0.6 * r2) * smoothstep(0.93, 0.995, nl) * up * 0.9)
let sidec = mix(vec3(0.440, 0.470, 0.510), vec3(0.240, 0.262, 0.292), t)
face = mix(face, sidec, (r1 + 0.4 * r2) * sidew * (0.35 + 0.65 * up))
// The bottom rim: the slab's lower edge catching the light (the LED's
// spill on a lit cap).
face = mix(face, rim.rgb, (r1 + 0.35 * r2) * botw * rim.a * (1.0 - disabled))
// The outline, black at the top, blacker at the bottom.
let ol = mix(vec3(0.039, 0.039, 0.047), vec3(0.012, 0.012, 0.016), t)
face = mix(face, mix(ol, vec3(0.035, 0.043, 0.055), disabled), Finish.band(d, 0.0, px, px))
let a = Finish.cover(d, px)
var o = Finish.over(under, vec4(face * a, a))
// Focus: a thin ring of the LED's rim colour just outside the outline,
// and a 2 point breath of it.
let ring = Finish.ring_out(d, 0.0, px, px) * 0.9 + Finish.glow(d - px, 0.7) * 0.3 * (1.0 - Finish.cover(d, px))
o = Finish.over(o, vec4(0.290, 0.560, 0.930, 1.0) * clamp(ring, 0.0, 1.0) * focus * (1.0 - disabled))
return o
}
// An LED bar or dot laid over `dst`: a white-hot core, a 1 dp saturated rim
// and a short halo when lit (`halo` scales it; 0 for a light that shows a
// value); a dark lens when not (a bar: one dark glass). `hs` is the half
// size; the ends are round.
let an_led = fn(dst: vec4, p: vec2, c: vec2, hs: vec2, lit: float, halo: float, px: float, core: vec3, rimc: vec3) -> vec4 {
let d = Finish.sd_pill(p, c, hs)
let slot = Finish.cover(d, px)
// Unlit: a dark lens, not a hole -- a black rim, near-black glass that
// catches a little of the light on its upper half, and on a dot large
// enough to show it a small catch-light up and to the left.
let tl = clamp((p.y - (c.y - hs.y)) / max(2.0 * hs.y, px), 0.0, 1.0)
var lens = mix(vec3(0.165, 0.176, 0.200), vec3(0.071, 0.078, 0.090), smoothstep(0.2, 0.9, tl))
let sp = p - (c + vec2(-0.3 * hs.x, -0.35 * hs.y))
lens = lens + vec3(0.16, 0.17, 0.19) * exp(-dot(sp, sp) / max(2.0 * 0.09 * hs.y * hs.y, 0.001)) * step(1.5, hs.y)
// A bar has no room for a rim, a light row and a catch-light: unlit it
// is one glass of three device pixels, falling #1e2127 -> #15171a with a
// faint cast of the lamp's blue, its top row lifted 0.03.
let bar = step(hs.y * 2.5, hs.x)
let gc = c + vec2(0.0, px * 0.5)
let gd = Finish.sd_pill(p, gc, vec2(hs.x + px * 0.5, px * 1.5))
let gt = clamp((p.y - (gc.y - px * 1.5)) / (px * 3.0), 0.0, 1.0)
var glass = mix(vec3(0.118, 0.129, 0.153), vec3(0.082, 0.090, 0.102), gt)
glass = mix(glass, vec3(0.290, 0.560, 0.930), 0.08) + vec3(0.03, 0.03, 0.03) * (1.0 - step(gc.y - px * 0.5, p.y))
var o = Finish.over(dst, vec4(0.024, 0.024, 0.031, 1.0) * Finish.ring_out(d, 0.0, px, px) * 0.55 * (1.0 - lit) * (1.0 - bar))
o = Finish.over(o, vec4(lens, 1.0) * slot * (1.0 - lit) * (1.0 - bar))
o = Finish.over(o, vec4(glass, 1.0) * Finish.cover(gd, px) * (1.0 - lit) * bar)
// Lit: the halo first, under the core. Two falls: a thin one that is
// gone in a device pixel or two, and a wide weak one that lies a little
// light on the floor round the lamp.
let x = max(d - px, 0.0)
let g = (0.45 * exp(-(x * x) / (2.0 * 0.5 * 0.5)) + 0.22 * exp(-(x * x) / (2.0 * 1.4 * 1.4))) * (1.0 - slot)
o = Finish.over(o, vec4(rimc, 1.0) * g * 0.8 * halo * lit)
// The saturated rim, one device pixel round the core.
let rim = Finish.ring_out(d, 0.0, px, px)
o = Finish.over(o, vec4(rimc, 1.0) * rim * lit)
// A hot top on the core.
let hot = 1.0 - smoothstep(0.0, hs.y * 1.2, p.y - (c.y - hs.y))
let col = mix(core, vec3(1.0, 1.0, 1.0), hot * 0.5)
o = Finish.over(o, vec4(col, 1.0) * slot * lit)
return o
}
// The lamp under a chosen label -- the chosen segment's, the tab in use's,
// the Primary's -- counted in whole device pixels. Its core is two rows
// between `xs.x` and `xs.y` (snapped), from the row at `y0` (snapped): the
// top row `core`, the lower one tinted toward `glow`, and one light-blue
// column at either end. Round it the halo lies on the floor in two layers:
// a tight one -- a pale row just above the core (brightest over its
// middle), a blue row under it, two blue-grey rows fading after that, and
// at the ends a pale fall gone three columns out -- over a soft wide one
// that rises three rows above the core, lies faintly along its sides and
// runs on down to the foot of the well. What lies above the core stays
// under an eighth of its light past the pale row, so a descender keeps its
// well. `halo` scales the light round the core; premultiplied over `dst`.
let an_lamp = fn(dst: vec4, p: vec2, xs: vec2, y0: float, px: float, lit: float, halo: float, core: vec3, glow: vec3) -> vec4 {
// Which row from the core's top, and how many columns past its ends
// (0 inside, 0.5 on the first column out).
let k = floor((p.y - y0) / px)
let e = max(max(xs.x - p.x, p.x - xs.y), 0.0) / px
let hw = max((xs.y - xs.x) * 0.5, px)
let dx = clamp(abs(p.x - (xs.x + xs.y) * 0.5) / hw, 0.0, 1.0)
let pale = mix(glow, core, 0.5)
let dim = mix(glow, vec3(0.56, 0.58, 0.64), 0.45)
let m4 = step(-4.5, k) * step(k, -3.5)
let m3 = step(-3.5, k) * step(k, -2.5)
let m2 = step(-2.5, k) * step(k, -1.5)
let m1 = step(-1.5, k) * step(k, -0.5)
let body = step(-0.5, k) * step(k, 1.5)
let p1 = step(1.5, k) * step(k, 2.5)
let p2 = step(2.5, k) * step(k, 3.5)
let p3 = step(3.5, k) * step(k, 4.5)
let p4 = step(4.5, k) * step(k, 5.5)
let p5 = step(5.5, k) * step(k, 6.5)
// The soft wide layer.
let wide = exp(-(e * e) / (2.0 * 3.4 * 3.4)) * (1.0 - 0.3 * dx * dx)
let wa = (0.12 * m4 + 0.22 * m3 + 0.32 * m2 + 0.15 * m1 + 0.20 * body + 0.08 * (p1 + p2 + p3) + 0.25 * p4 + 0.15 * p5) * wide
var o = Finish.over(dst, vec4(dim, 1.0) * clamp(wa * halo, 0.0, 1.0) * lit)
// The tight layer.
let top = exp(-(e * e) / (2.0 * 1.6 * 1.6)) * (1.0 - 0.3 * dx * dx)
let side = exp(-(e * e) / (2.0 * 2.2 * 2.2))
let ce = max(e - 1.5, 0.0)
let ends = 0.5 * exp(-(ce * ce) / (2.0 * 0.75 * 0.75)) * step(1.0, e) * (1.0 - 0.4 * step(0.5, k))
var a = 0.72 * top * m1 + ends * body
a = a + (0.50 * p1 + 0.46 * p2 + 0.36 * p3 + 0.10 * p4) * side
let hc = mix(mix(pale, glow, p1), dim, p2 + p3 + p4)
o = Finish.over(o, vec4(hc, 1.0) * clamp(a * halo, 0.0, 1.0) * lit)
// The core and its two end columns; the lower row takes a little of
// the glow's own hue.
let hue = glow / max(max(glow.x, glow.y), max(glow.z, 0.001))
let lower = core * mix(vec3(1.0, 1.0, 1.0), hue, 0.35)
let inside = 1.0 - step(0.25, e)
let endc = step(0.25, e) * (1.0 - step(1.0, e))
let cc = mix(mix(core, lower, step(0.5, k)), mix(core, glow, 0.4), endc)
return Finish.over(o, vec4(cc, 1.0) * (inside + 0.95 * endc) * body * lit)
}
// A lamp lying in the bottom rim of a lit cap, as the pictures light the
// Primary, the chosen segment and the tab in use: a white-hot bar three
// device pixels tall and `hw` half wide whose last row is `yb` (the row above
// the outline), the glow's colour on the face round it (over 2 points up
// and 4 to either side), a pale halo hugging the bar, and a spill that runs
// out past the outline onto the ground under the cap, a tenth left 8 points
// down. `inside` is the cap's cover, `below` 1 on the ground under it.
// Premultiplied over `dst`.
let an_rim_lamp = fn(dst: vec4, p: vec2, cx: float, hw: float, yb: float, px: float, lit: float, halo: float, core: vec3, glow: vec3, inside: float, below: float) -> vec4 {
let cy = yb - px
let d = Finish.sd_pill(p, vec2(cx, cy), vec2(hw, px * 1.5))
let ex = max(abs(p.x - cx) - hw, 0.0)
let dy = max(cy - p.y, 0.0)
let fg = exp(-ex / 4.0) * exp(-dy / 2.0)
var o = Finish.over(dst, vec4(glow, 1.0) * clamp(fg * 0.6 * halo, 0.0, 1.0) * inside * lit)
let x = max(d, 0.0)
let tight = exp(-(x * x) / (2.0 * 0.7 * 0.7)) * (1.0 - Finish.cover(d, px))
o = Finish.over(o, vec4(mix(glow, core, 0.55), 1.0) * tight * 0.85 * halo * lit * max(inside, below))
let sy = max(p.y - yb, 0.0)
let sp = exp(-(ex * ex) / (2.0 * 5.0 * 5.0)) * (0.35 * exp(-sy / 1.6) + 0.30 * exp(-sy / 5.0))
o = Finish.over(o, vec4(glow * 0.75, 1.0) * sp * below * halo * lit)
return Finish.over(o, vec4(core, 1.0) * Finish.cover(d, px) * lit)
}
// The three-quarter arc a waiting button turns in place of its label.
let an_spinner = fn(dst: vec4, p: vec2, c: vec2, rad: float, t: float, amt: float, stroke: 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, stroke)
sdf.fill(vec4(0.94, 0.98, 1.0, amt))
return Finish.over(dst, sdf.result)
}
// ---------------------------------------------------------------- buttons
// Every button is the same slim cap: 26 points on a 13 point line, 10
// points either side. Its shadow falls outside the box, so the box is the
// outline and nothing is paid in padding.
mod.widgets.ButtonFlat.padding = mod.turtle.Inset{left: 10.0 right: 10.0 top: 4.0 bottom: 4.0}
mod.widgets.ButtonFlat.min_height = 26.0
// Glyphs sit at 14 points so an icon never makes a cap taller than the row;
// an icon alone gets a square cap.
mod.widgets.ButtonFlat.icon_walk.width = 14.0
mod.widgets.ButtonIcon.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 4.0 bottom: 4.0}
mod.widgets.ButtonFlatIcon.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 4.0 bottom: 4.0}
mod.widgets.ButtonFlatterIcon.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 4.0 bottom: 4.0}
// The caps that carry a lamp lift their label one device pixel, so the lamp
// can stand two rows off the bottom rim and still clear the descenders.
mod.widgets.ButtonPrimary.padding = mod.turtle.Inset{left: 10.0 right: 10.0 top: 3.3333 bottom: 4.6667}
mod.widgets.ButtonSecondary.padding = mod.turtle.Inset{left: 10.0 right: 10.0 top: 3.3333 bottom: 4.6667}
mod.widgets.ButtonDanger.padding = mod.turtle.Inset{left: 10.0 right: 10.0 top: 3.3333 bottom: 4.6667}
// The short rungs keep their own heights under the row minimum.
mod.widgets.ButtonXs.min_height = 0.0
mod.widgets.ButtonSm.min_height = 0.0
// The smallest rung's label one step down, so its cap height is a third of
// its cap like every other rung's.
mod.widgets.ButtonXs.draw_text.text_style.font_size = 7.0
mod.widgets.ButtonFlat.draw_bg.border_radius = uniform(1.0)
mod.widgets.ButtonFlat.draw_bg.border_size = uniform(0.34)
mod.widgets.ButtonFlat.clip_x = false
mod.widgets.ButtonFlat.clip_y = false
mod.widgets.ButtonFlat.draw_bg.vertex = fn() {
let m = 14.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 cap and its states, shared by every button template; `kind` picks the
// variant: 0 seated cap, 1 raised cap, 2 primary (lit LED), 3 secondary (dark
// LED slot), 4 tonal (tinted face), 5 danger (red LED), 6 outline, 7 flatter.
let an_button = fn(pos: vec2, size: vec2, o: vec2, dpi: float, hover: float, down: float, disabled: float, focus: float, loading: float, anim: float, kind: float, dash: float) -> vec4 {
let m = 14.0
let px = an_px(dpi)
let p = pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let lo = vec2(an_snap(0.0, o.x, px), an_snap(0.0, o.y, px))
let hi = vec2(an_snap(size.x, o.x, px), an_snap(size.y, o.y, px))
let c = (lo + hi) * 0.5
let h = (hi - lo) * 0.5
let r = 4.0
var res = vec4(0.0, 0.0, 0.0, 0.0)
if kind > 5.5 {
// Outline: no face, the black ring cut into the ground with its lip;
// under the hand a faint cap shows, held it sinks.
let d = Material.sd_box(p, c, h, r)
if kind > 6.5 {
// Flatter: nothing at rest.
if hover + down + focus > 0.001 {
res = an_cap(p, lo, hi, r, px, hover, down, disabled, 0.0, 0.0, vec3(0.0, 0.0, 0.0), vec4(0.270, 0.298, 0.330, 1.0), 1.0) * clamp(max(hover, down), 0.0, 1.0)
}
} else {
var ring = Finish.band(d, 0.0, px, px)
if dash > 0.0 {
ring = ring * step(0.5, fract((p.x + p.y) / (dash * 2.0)))
}
let lip = Finish.ring_out(d, 0.0, px, px) * step(c.y + h.y * 0.5, p.y)
let inner = Finish.band(d, px, px * 2.0, px) * step(p.y, c.y - h.y * 0.5)
res = vec4(0.36, 0.40, 0.45, 1.0) * lip * 0.9
res = Finish.over(res, vec4(0.0, 0.0, 0.0, 1.0) * inner * 0.25)
res = Finish.over(res, vec4(0.039, 0.039, 0.047, 1.0) * ring)
if hover + down > 0.001 {
res = Finish.over(res, an_cap(p, lo, hi, r, px, hover, down, disabled, 0.0, 0.0, vec3(0.0, 0.0, 0.0), vec4(0.270, 0.298, 0.330, 1.0), 1.0) * clamp(max(hover, down), 0.0, 1.0) * 0.85)
}
}
let fr = Finish.ring_out(d, 0.0, px, px) * 0.9 + Finish.glow(d - px, 0.7) * 0.3 * (1.0 - Finish.cover(d, px))
res = Finish.over(res, vec4(0.290, 0.560, 0.930, 1.0) * clamp(fr, 0.0, 1.0) * focus * (1.0 - disabled))
} else {
var tint = vec3(0.0, 0.0, 0.0)
var rim = vec4(0.270, 0.298, 0.330, 1.0)
let lit = 1.0 - disabled
if kind > 3.5 && kind < 4.5 {
// The tonal cap: the face a breath toward the LED's blue, no
// more than 4 steps of blue over the family's face.
tint = vec3(-0.004, 0.0, 0.012)
}
if kind > 1.5 && kind < 2.5 {
// Primary: the face a little blue, the rim the lamp's.
rim = vec4(0.290, 0.560, 0.930, 0.6 * lit)
tint = vec3(-0.008, 0.014, 0.045) * lit
}
if kind > 4.5 {
rim = vec4(0.56, 0.22, 0.18, 0.45 * lit)
}
var lift = 1.0
if kind < 0.5 {
lift = 0.0
}
res = Finish.over(res, an_cap(p, lo, hi, r, px, hover, down, disabled, focus, lift, tint, rim, 1.0))
let dp = Material.sd_box(p, c, h, r)
let inside = Finish.cover(dp, px)
if kind > 1.5 && kind < 2.5 {
// Primary is lit from within: a blue-white bloom fills the top
// of the face under the rim (luminance 0.75 there, 0.45 a
// quarter down, as the picture's Primary measures) and fades
// toward the ends.
let top = max(p.y - lo.y - px * 2.0, 0.0)
let ux = (p.x - c.x) / max(h.x, 1.0)
let bloom = exp(-top / (0.24 * (hi.y - lo.y))) * (1.0 - 0.4 * ux * ux) * Finish.cover(dp + px * 2.0, px)
let on = lit * (1.0 - down * 0.6)
res = Finish.over(res, vec4(0.640, 0.760, 0.920, 1.0) * bloom * 0.68 * on)
}
if kind > 1.5 && kind < 3.5 || kind > 4.5 {
// The lamp lies in the bottom rim: a white-hot bar three device
// pixels tall on the row above the outline, 15 points long,
// lighting the face round it and spilling onto the ground under
// the cap. Held, its light halves. Unlit (Secondary, or
// disabled) it is the dark glass.
let yb = an_snap(hi.y, o.y, px) - px
let hw = min(6.0, h.x * 0.36)
var on = lit
var core = vec3(0.960, 0.985, 1.0)
var glow = vec3(0.330, 0.600, 0.980)
if kind > 2.5 && kind < 3.5 {
on = 0.0
}
if kind > 4.5 {
// Danger: the same lamp, red.
core = vec3(1.0, 0.86, 0.82)
glow = vec3(0.92, 0.26, 0.2)
}
let below = (1.0 - inside) * step(hi.y - px, p.y)
let dark = an_led(res, p, vec2(c.x, yb - px * 1.5), vec2(hw, px), 0.0, 0.0, px, core, glow)
let lamp = an_rim_lamp(res, p, c.x, hw, yb, px, 1.0, 1.0 - down * 0.5, core, glow, inside, below)
res = mix(dark, lamp, on)
}
}
if loading > 0.001 {
// The arc ends two device pixels or more above where a lamp's rim
// stands: a smaller ring, lifted one device pixel.
let rad = max(min(h.x, h.y) - 7.25, 2.0)
res = an_spinner(res, p, c - vec2(0.0, px), rad, anim, loading, 1.5)
}
return res
}
mod.widgets.ButtonFlat.draw_bg.pixel = fn() {
return self.host_over(an_button(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.down, self.disabled, self.focus, self.loading, self.anim_time, 0.0, 0.0), 14.0)
}
mod.widgets.Button.draw_bg.pixel = fn() {
return self.host_over(an_button(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.down, self.disabled, self.focus, self.loading, self.anim_time, 1.0, 0.0), 14.0)
}
mod.widgets.ButtonPrimary.draw_bg.pixel = fn() {
return self.host_over(an_button(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.down, self.disabled, self.focus, self.loading, self.anim_time, 2.0, 0.0), 14.0)
}
mod.widgets.ButtonSecondary.draw_bg.pixel = fn() {
return self.host_over(an_button(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.down, self.disabled, self.focus, self.loading, self.anim_time, 3.0, 0.0), 14.0)
}
mod.widgets.ButtonTertiary.draw_bg.pixel = fn() {
return self.host_over(an_button(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.down, self.disabled, self.focus, self.loading, self.anim_time, 4.0, 0.0), 14.0)
}
mod.widgets.ButtonDanger.draw_bg.pixel = fn() {
return self.host_over(an_button(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.down, self.disabled, self.focus, self.loading, self.anim_time, 5.0, 0.0), 14.0)
}
mod.widgets.ButtonOutline.draw_bg.pixel = fn() {
return self.host_over(an_button(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.down, self.disabled, self.focus, self.loading, self.anim_time, 6.0, self.border_dash), 14.0)
}
mod.widgets.ButtonFlatter.draw_bg.pixel = fn() {
return self.host_over(an_button(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.down, self.disabled, self.focus, self.loading, self.anim_time, 7.0, 0.0), 14.0)
}
// Label ink per state: caps #d4dae2, the primary's lit legend near white,
// every variant dims to #626a74 when disabled.
mod.widgets.ButtonPrimary.draw_text.color = #f4f7fa
mod.widgets.ButtonPrimary.draw_text.color_hover = #f8f9fa
mod.widgets.ButtonPrimary.draw_text.color_down = #f4f7fa
mod.widgets.ButtonPrimary.draw_text.color_focus = #f4f7fa
mod.widgets.ButtonPrimary.draw_text.color_disabled = #626a74
mod.widgets.ButtonPrimary.draw_icon.color = #f4f7fa
mod.widgets.ButtonSecondary.draw_text.color = #d4dae2
mod.widgets.ButtonSecondary.draw_text.color_hover = #dde2e8
mod.widgets.ButtonSecondary.draw_text.color_down = #c3c9d1
mod.widgets.ButtonSecondary.draw_text.color_focus = #d4dae2
mod.widgets.ButtonSecondary.draw_icon.color = #d4dae2
mod.widgets.ButtonTertiary.draw_text.color = #d4dae2
mod.widgets.ButtonTertiary.draw_text.color_hover = #dde2e8
mod.widgets.ButtonTertiary.draw_text.color_down = #c3c9d1
mod.widgets.ButtonTertiary.draw_text.color_focus = #d4dae2
mod.widgets.ButtonTertiary.draw_icon.color = #d4dae2
mod.widgets.ButtonDanger.draw_text.color = #f4f7fa
mod.widgets.ButtonDanger.draw_text.color_hover = #f8f9fa
mod.widgets.ButtonDanger.draw_text.color_down = #f4f7fa
mod.widgets.ButtonDanger.draw_text.color_focus = #f4f7fa
mod.widgets.ButtonDanger.draw_icon.color = #f4f7fa
mod.widgets.ButtonOutline.draw_text.color = #d4dae2
mod.widgets.ButtonOutline.draw_text.color_hover = #dde2e8
mod.widgets.ButtonOutline.draw_text.color_down = #c3c9d1
mod.widgets.ButtonOutline.draw_text.color_focus = #d4dae2
mod.widgets.ButtonOutline.draw_text.color_disabled = #626a74
mod.widgets.ButtonOutline.draw_icon.color = #d4dae2
// The trailing icon takes the cap's ink too, never the drawing's own black.
mod.widgets.ButtonFlat.draw_icon_end.color = #d4dae2
// ---------------------------------------------------------------- selection
// A recessed well: no outer shadow, a 1 dp inner top shade, the black
// outline, and a 1 dp light lip on the ground under its lower edge.
// Premultiplied; `fill` is the well's own ink, `lip` 0 drops the lip.
let an_well = fn(p: vec2, lo: vec2, hi: vec2, r: float, px: float, fill: vec3, focus: float, disabled: float, lip: float) -> vec4 {
let c = (lo + hi) * 0.5
let h = (hi - lo) * 0.5
let d = Material.sd_box(p, c, h, r)
let t = clamp((p.y - lo.y) / max(hi.y - lo.y, 1.0), 0.0, 1.0)
// The bezel round the well: a quiet light row over its top half and a
// bright lip under its foot, where the light from the top lands.
let bzc = mix(vec3(0.250, 0.278, 0.315), vec3(0.470, 0.515, 0.575), step(c.y, p.y))
var res = vec4(bzc, 1.0) * Finish.ring_out(d, 0.0, px, px) * lip * (1.0 - disabled * 0.6)
// The lower half a shade lighter, the first device pixels under the top
// outline a shade darker.
var col = fill + vec3(0.008, 0.008, 0.009) * step(0.5, t)
let top = p.y - lo.y
// Whole device-pixel rows (the edges are snapped): the row under the
// outline #1a1b20, the next half way to it.
col = mix(col, vec3(0.035, 0.043, 0.055), 1.0 - step(px * 2.0, top))
col = mix(col, vec3(0.035, 0.043, 0.055), 0.5 * step(px * 2.0, top) * (1.0 - step(px * 3.0, top)))
col = mix(col, mix(vec3(0.039, 0.039, 0.047), vec3(0.024, 0.043, 0.067), t), Finish.band(d, 0.0, px, px))
let a = Finish.cover(d, px)
res = Finish.over(res, vec4(col * a, a))
let ring = Finish.ring_out(d, 0.0, px, px) * 0.9 + Finish.glow(d - px, 0.7) * 0.3 * (1.0 - a)
res = Finish.over(res, vec4(0.290, 0.560, 0.930, 1.0) * clamp(ring, 0.0, 1.0) * focus * (1.0 - disabled))
return res
}
// The quad of a small control grows by this much so its shadow and focus
// ring can 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
mod.widgets.CheckBoxFlat.padding = mod.turtle.Inset{left: 8.0 right: 8.0 top: 4.0 bottom: 4.0}
mod.widgets.CheckBoxFlat.draw_bg.size = uniform(16.0)
mod.widgets.CheckBoxFlat.draw_bg.border_radius = uniform(1.0)
mod.widgets.CheckBoxFlat.label_walk.margin.left = 16.0
// A lit mark: a pale core along the stroke's middle, the accent at its
// edges and a soft halo of the lamp's blue round it. `dk` is the distance
// to the stroke's edge, `w` its half width.
let an_lit_mark = fn(dst: vec4, dk: float, w: float, px: float, on: float, disabled: float, error: float) -> vec4 {
let edge = mix(mix(vec3(0.320, 0.660, 1.0), vec3(0.94, 0.32, 0.23), error), vec3(0.294, 0.306, 0.341), disabled)
let core = mix(mix(vec3(0.88, 0.96, 1.0), vec3(1.0, 0.86, 0.82), error), vec3(0.36, 0.37, 0.40), disabled)
let glow = mix(vec3(0.290, 0.560, 0.930), vec3(0.72, 0.25, 0.18), error)
let x = max(dk, 0.0)
let g = (0.30 * exp(-(x * x) / (2.0 * 0.45 * 0.45)) + 0.14 * exp(-(x * x) / (2.0 * 1.3 * 1.3))) * (1.0 - Finish.cover(dk, px)) * (1.0 - disabled)
var res = Finish.over(dst, vec4(glow, 1.0) * g * on)
let ink = mix(edge, core, 1.0 - smoothstep(0.15 * w, 0.8 * w, dk + w))
return Finish.over(res, vec4(ink, 1.0) * Finish.cover(dk, px) * on)
}
// The check box: a 16 point raised cap. Checked, the cap is lit blue from
// within and its mark is lit; the glass glows onto the ground round it.
mod.widgets.CheckBoxFlat.draw_bg.pixel = fn() {
let m = 5.0
let px = an_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(an_snap(x0, o.x, px), an_snap(y0, o.y, px))
let hi = vec2(an_snap(x0 + sz, o.x, px), an_snap(y0 + sz, o.y, px))
let c = (lo + hi) * 0.5
let r = min(self.border_radius * 2.0, sz * 0.5)
let on = max(self.active, self.mixed)
// Checked, the cap stays up and is lit from within (a steel-blue face
// under its rim, saturated blue round the mark); only the hand sinks it.
let held = max(self.down, self.pressed)
let tint = vec3(0.012, 0.045, 0.110) * on * (1.0 - self.disabled)
var res = an_cap(p, lo, hi, r, px, self.hover, held, self.disabled, self.focus, 1.0, tint, vec4(0.270, 0.298, 0.330, 1.0), 0.0)
// The error: the outline turns the alt red.
let d = Material.sd_box(p, c, (hi - lo) * 0.5, r)
// The glass is lit from under the mark: a soft blue pool filling it.
let gq = (p - c - vec2(0.0, sz * 0.2)) / (sz * 0.34)
res = Finish.over(res, vec4(0.020, 0.380, 0.780, 1.0) * exp(-dot(gq, gq)) * 0.85 * on * (1.0 - self.disabled) * Finish.cover(d + px * 2.0, px))
// Lit, the glass glows through its outline: a blue halo on the ground
// round the box, gone within four points.
res = Finish.over(res, vec4(0.290, 0.560, 0.930, 1.0) * Finish.glow(d, 1.0) * 0.45 * (1.0 - Finish.cover(d, px)) * on * (1.0 - self.disabled))
res = Finish.over(res, vec4(0.94, 0.32, 0.23, 1.0) * Finish.band(d, 0.0, px, px) * self.error)
// The mark: a check 8 points across in a 1.7 point stroke, lit; mixed
// is a dash of three device pixel rows, a pale core row between two of
// the accent, on the row through the middle of the well. The check
// stands one device pixel lower, in the well.
let cm = c + vec2(0.0, px * 0.5)
let k = sz / 14.0
let dm = min(an_seg(p, cm + vec2(-3.6, 0.1) * k, cm + vec2(-1.1, 2.6) * k), an_seg(p, cm + vec2(-1.1, 2.6) * k, cm + vec2(3.7, -2.8) * k))
let cy = an_snap((lo.y + hi.y) * 0.5, o.y, px) + px * 0.5
let dd = an_seg(p, vec2(c.x - 3.5 * k, cy), vec2(c.x + 3.5 * k, cy))
let dk = mix(dm - 0.85, dd - px * 1.5, self.mixed)
return an_lit_mark(res, dk, mix(0.85, px * 1.5, self.mixed), px, on, self.disabled, self.error)
}
// The toggle: a 34 x 18 rocker, a darker raised body, a glossy thumb half its
// length; the thumb carries an LED bar, lit when on, a dark slot when off.
mod.widgets.ToggleFlat.draw_bg.size = uniform(18.0)
mod.widgets.ToggleFlat.draw_bg.pill_aspect = uniform(1.89)
mod.widgets.ToggleFlat.label_walk.margin.left = 34.0
mod.widgets.ToggleFlat.draw_bg.pixel = fn() {
let m = 5.0
let px = an_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 tw = self.size * self.pill_aspect
let th = self.size
let x0 = (self.rect_size.x - tw) * self.mark_at_end
let y0 = self.rect_size.y * 0.5 - th * 0.5
let lo = vec2(an_snap(x0, o.x, px), an_snap(y0, o.y, px))
let hi = vec2(an_snap(x0 + tw, o.x, px), an_snap(y0 + th, o.y, px))
// On, the track's floor takes the chosen segment's blue cast.
let on = self.active * (1.0 - self.disabled)
// A rocker: the body a darker raised cap, blue-cast when on; the thumb
// a brighter glossy cap standing on it.
var res = an_cap(p, lo, hi, 3.0, px, 0.0, 0.0, self.disabled, self.focus, 1.0, vec3(-0.07, -0.07, -0.07) + vec3(0.0, 0.03, 0.07) * on, vec4(0.200, 0.220, 0.245, 1.0), 0.0)
// The thumb travels half the track.
let kt = mix(self.active, self.drag_pos, self.drag)
let kw = an_snap(x0 + tw * 0.5, o.x, px) - lo.x
let kx = an_snap(x0 + (tw - tw * 0.5) * kt, o.x, px)
let klo = vec2(kx, lo.y)
let khi = vec2(kx + kw, hi.y)
let thumb = an_cap(p, klo, khi, 3.0, px, self.hover, max(self.pressed, self.down), self.disabled, 0.0, 0.6, vec3(0.02, 0.022, 0.025), vec4(0.270, 0.298, 0.330, 1.0), 1.0)
res = Finish.over(res, thumb)
// The LED bar, 8 x 1.5 points, in the middle of the thumb.
let lc = vec2((klo.x + khi.x) * 0.5, an_snap((lo.y + hi.y) * 0.5, o.y, px))
res = an_led(res, p, lc, vec2(4.0, px), on, 0.8, px, vec3(0.94, 0.984, 1.0), vec3(0.290, 0.560, 0.930))
return res
}
// The radio: the check box's cap made round. Chosen, it is lit blue from
// within and an LED dot is lit in its middle; not chosen, the dot is a dark
// lens. Only the hand sinks it.
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.size = uniform(16.0)
mod.widgets.RadioButtonFlat.padding = mod.turtle.Inset{left: 8.0 right: 8.0 top: 4.0 bottom: 4.0}
mod.widgets.RadioButtonFlat.label_walk.margin.left = 16.0
mod.widgets.RadioButtonFlat.draw_bg.pixel = fn() {
let m = 5.0
let px = an_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(an_snap(0.0, o.x, px), an_snap(y0, o.y, px))
let hi = vec2(an_snap(sz, o.x, px), an_snap(y0 + sz, o.y, px))
let c = (lo + hi) * 0.5
let on = self.active * (1.0 - self.disabled)
let tint = vec3(0.025, 0.070, 0.150) * on
var res = an_cap(p, lo, hi, sz * 0.5, px, self.hover, self.down, self.disabled, self.focus, 1.0, tint, vec4(0.270, 0.298, 0.330, 1.0), 0.0)
// Chosen, the cap stays up and is lit from the dot: saturated blue
// round it, strongest under it.
let gq = (p - c - vec2(0.0, sz * 0.15)) / (sz * 0.36)
res = Finish.over(res, vec4(0.040, 0.420, 0.800, 1.0) * exp(-dot(gq, gq)) * 0.65 * on * Finish.cover(length(p - c) - sz * 0.5 + px * 2.0, px))
let dro = length(p - c) - sz * 0.5
res = Finish.over(res, vec4(0.290, 0.560, 0.930, 1.0) * Finish.glow(dro, 1.0) * 0.45 * (1.0 - Finish.cover(dro, px)) * on)
// The dot: 5.5 points, one device pixel lower in the well when lit.
let cd = c + vec2(0.0, px * 0.5 * on)
res = an_led(res, p, cd, vec2(3.0 - px, 3.0 - px), on, 1.0, px, vec3(0.96, 0.99, 1.0), vec3(0.290, 0.560, 0.930))
return res
}
// The radio tab: a segment of a joined strip. At rest a raised cap; chosen,
// it sinks, flat and tinted toward the light, its legend lit and the
// chosen segment's lamp under it.
mod.widgets.RadioButtonTabFlat.draw_bg.vertex = fn() {
let m = 13.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: 0.0 right: 8.0 top: 4.0 bottom: 4.0}
mod.widgets.RadioButtonTabFlat.min_height = 26.0
mod.widgets.RadioButtonTabFlat.draw_bg.pixel = fn() {
let m = 13.0
let px = an_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(an_snap(0.0, o.x, px), an_snap(0.0, o.y, px))
let hi = vec2(an_snap(self.rect_size.x, o.x, px), an_snap(self.rect_size.y, o.y, px))
let sel = self.active
let tint = vec3(-0.010, 0.045, 0.110) * sel
let res = an_cap(p, lo, hi, 4.0, px, self.hover, max(self.down, sel), self.disabled, self.focus, 1.0, tint, vec4(0.270, 0.298, 0.330, 1.0), 0.0)
// The lamp lies in the bottom rim under the legend, well clear of its
// descenders, and spills onto the ground under the cap.
let c = (lo + hi) * 0.5
let d = Material.sd_box(p, c, (hi - lo) * 0.5, 4.0)
let inside = Finish.cover(d, px)
let below = (1.0 - inside) * step(hi.y - px, p.y)
return an_rim_lamp(res, p, c.x, min(6.0, (hi.x - lo.x) * 0.3), hi.y - px, px, sel * (1.0 - self.disabled), 1.0, vec3(0.960, 0.985, 1.0), vec3(0.330, 0.600, 0.980), inside, below)
}
mod.widgets.RadioButtonTabFlat.draw_text.color = #c3c9d1
mod.widgets.RadioButtonTabFlat.draw_text.color_hover = #dde2e8
mod.widgets.RadioButtonTabFlat.draw_text.color_active = #fafafa
mod.widgets.RadioButtonTabFlat.draw_text.color_disabled = #626a74
// ---------------------------------------------------------------- sliders
// A lit tube laid in a groove, as the pictures draw every value: it fills
// the groove's floor between `lo` and `hi`, counted in whole device-pixel
// rows from `lo.y` (snapped) -- a pale top row, a white-hot band over the
// upper third, the role colour below it and a deeper foot row, so it reads
// round. Past the tube's end its light runs on along the floor, falling by
// half every device pixel, between `gx.x` and `gx.y` (the floor's ends);
// above and below the groove a faint blue breath lies on the ground, gone
// within 3 points. `body` is the role colour. Premultiplied over `dst`.
let an_lit_bar = fn(dst: vec4, p: vec2, lo: vec2, hi: vec2, gx: vec2, px: float, body: vec3, disabled: float) -> vec4 {
let n = max(floor((hi.y - lo.y) / px + 0.5), 1.0)
let k = floor((p.y - lo.y) / px)
let rows = step(0.0, k) * step(k, n - 1.0)
let f = (k + 0.5) / n
// A glass tube lit from within: a white-hot core over its upper third,
// the role colour deep below it, darker toward the foot.
let deepb = body * vec3(0.62, 0.68, 0.74)
let hot = mix(body, vec3(1.0, 1.0, 1.0), 0.92)
let fc = (f - 0.30) / 0.15
var col = mix(mix(deepb, deepb * 0.62, smoothstep(0.35, 1.0, f)), hot, exp(-fc * fc))
let big = step(3.5, n)
col = mix(col, mix(body, vec3(1.0, 1.0, 1.0), 0.35), (1.0 - step(0.5, k)) * big * 0.7)
col = mix(col, deepb * 0.45, step(n - 1.5, k) * big)
col = mix(col, mix(body, vec3(0.3, 0.33, 0.38), 0.7), disabled)
let inx = step(lo.x, p.x) * step(p.x, hi.x)
let past = max(max(p.x - hi.x, lo.x - p.x), 0.0) / px
let fall = exp(-max(past - 0.5, 0.0) * 0.76) * (1.0 - inx) * step(gx.x, p.x) * step(p.x, gx.y)
let deep = mix(body, vec3(0.13, 0.14, 0.17), 0.3)
var res = Finish.over(dst, vec4(mix(body, deep, 0.4), 1.0) * fall * rows * 0.42 * (1.0 - disabled))
// The breath on the ground over and under the groove (its outline and
// lip stand between, so it starts two device pixels out).
let dy = max(max(lo.y - px * 2.0 - p.y, p.y - hi.y - px * 3.0), 0.0)
let outside = 1.0 - step(lo.y - px * 2.0, p.y) * step(p.y, hi.y + px * 3.0)
let ex = max(max(lo.x - p.x, p.x - hi.x), 0.0)
let breath = exp(-dy / 1.8) * exp(-ex / 2.0) * outside * step(0.001, hi.x - lo.x) * 0.38 * (1.0 - disabled)
res = Finish.over(res, vec4(body, 1.0) * breath)
return Finish.over(res, vec4(col, 1.0) * inx * rows)
}
let an_fill = fn(dst: vec4, p: vec2, lo: vec2, hi: vec2, gx: vec2, px: float, disabled: float) -> vec4 {
return an_lit_bar(dst, p, lo, hi, gx, px, vec3(0.300, 0.640, 1.0), disabled)
}
// A turned-metal disc: a knob's cap or a slider's ball. Its drop and
// contact shadows, lathe grooves throwing a fan along the light's axis, a
// soft specular on the upper left, a lit rim on the top edge and a dark one
// on the foot, the black outline. `bright` 0 is the knob's darker steel,
// 1 the polished ball. Premultiplied.
let an_metal = fn(p: vec2, c: vec2, rad: float, px: float, hover: float, disabled: float, bright: float) -> vec4 {
let q = p - c
let rl = length(q)
let d = rl - rad
let hr = vec2(rad, rad)
let sh = Finish.drop(p, c, hr * 0.92, rad * 0.92, vec2(0.0, min(0.2 * rad, 4.0)), min(0.15 * rad, 3.0)) * 0.9
let contact = Finish.drop(p, c, hr, rad, vec2(0.0, px), px * 0.6) * 0.95
var res = vec4(0.0, 0.0, 0.0, 1.0 - (1.0 - sh) * (1.0 - contact))
let t = clamp(q.y / max(rad, 0.5) * 0.5 + 0.5, 0.0, 1.0)
let top = mix(vec3(0.540, 0.570, 0.610), vec3(0.680, 0.710, 0.740), bright)
let foot = mix(vec3(0.135, 0.152, 0.176), vec3(0.235, 0.255, 0.282), bright)
var m = mix(top, foot, smoothstep(0.05, 0.95, t))
// Lathe grooves: a bowtie of light along the diagonal the light comes
// from, dark across it, fine rings over both.
let fan = cos(2.0 * (atan2(q.y, q.x) - atan2(-0.94, -0.35)))
let rings = an_noise(vec2(rl * 1.6, 0.37)) - 0.5 + 0.6 * (an_noise(vec2(rl * 0.45, 9.1)) - 0.5)
m = m * (1.0 + smoothstep(0.0, 2.0, rl) * (0.55 * fan + 0.16 * rings))
// The specular, up and to the left.
let sp = (q - vec2(-0.32 * rad, -0.40 * rad)) / max(0.36 * rad, 0.8)
m = m + mix(vec3(0.10, 0.105, 0.11), vec3(0.20, 0.205, 0.21), bright) * exp(-dot(sp, sp))
m = m + vec3(0.035, 0.037, 0.040) * hover
let n = q / max(rl, 0.001)
let rim = Finish.band(d, px, px * 2.0, px)
// The rims: a near-white chrome row where the edge faces up, a second
// row under it, and a lit lip where it faces down (the light bounced
// off the ground, 0.42 as the pictures' knobs measure).
let rim2 = Finish.band(d, px * 2.0, px * 3.0, px)
m = mix(m, mix(vec3(0.860, 0.885, 0.910), vec3(0.950, 0.965, 0.980), bright), rim * smoothstep(0.1, 0.9, -n.y) * 0.95)
m = mix(m, mix(vec3(0.600, 0.630, 0.662), vec3(0.760, 0.780, 0.800), bright), rim2 * smoothstep(0.3, 0.9, -n.y) * 0.6)
m = mix(m, vec3(0.400, 0.425, 0.460), rim * smoothstep(0.35, 0.95, n.y) * 0.8)
m = mix(m, mix(m, vec3(0.170, 0.188, 0.212), 0.65), disabled)
m = mix(m, vec3(0.016, 0.020, 0.026), Finish.band(d, 0.0, px, px))
let a = Finish.cover(d, px)
return Finish.over(res, vec4(m * a, a))
}
// A slider's ball: the polished metal disc, a small LED dot lit in its
// middle while it is held or focused.
let an_handle = fn(dst: vec4, p: vec2, c: vec2, rad: float, px: float, hover: float, held: float, disabled: float) -> vec4 {
var res = Finish.over(dst, an_metal(p, c, rad, px, hover, disabled, 1.0))
res = an_led(res, p, c, vec2(1.5 - px, 1.5 - px), held * (1.0 - disabled), 0.6, px, vec3(0.94, 0.984, 1.0), vec3(0.290, 0.560, 0.930))
return res
}
// The bar slider: a 4 point well under its label row, the lit fill, and a
// 14 point round cap riding it.
mod.widgets.SliderFlat.clip_x = false
mod.widgets.SliderFlat.clip_y = false
mod.widgets.SliderFlat.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.SliderFlat.draw_bg.pixel = fn() {
let m = 6.0
let px = an_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 = 8.0
let cy = an_snap(20.0 + (self.rect_size.y - 20.0) * 0.5, o.y, px)
let lo = vec2(an_snap(0.0, o.x, px), cy - 3.3333)
let hi = vec2(an_snap(self.rect_size.x, o.x, px), cy + 2.6667)
var res = an_well(p, lo, hi, 2.0, px, vec3(0.078, 0.094, 0.118), 0.0, self.disabled, 1.0)
let span = self.rect_size.x - rad * 2.0
let hx = rad + self.slide_pos * span
let ox = rad + self.origin_pos * span
let f0 = mix(lo.x + px, min(ox, hx), self.arc_origin)
let f1 = mix(hx, max(ox, hx), self.arc_origin)
res = an_fill(res, p, vec2(f0, lo.y + px), vec2(f1, hi.y - px), vec2(lo.x + px, hi.x - px), px, self.disabled)
let held = max(self.drag, self.focus)
return an_handle(res, p, vec2(hx, cy), rad, px, self.hover, held, self.disabled)
}
// A slider's value is a text input, and the field row's 26 point 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 20 point legend row above the ring: at
// the field row's 26 points it hung over the ring's bezel, so here it is
// its line and two points either side, and clears the ring.
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; a slim cap shows
// under the hand.
mod.widgets.SliderMinimal.draw_bg.pixel = fn() {
let px = an_px(self.draw_pass.dpi_factor)
let o = self.rect_pos
let p = self.pos * self.rect_size
let cy = an_snap(self.offset_y + (self.rect_size.y - self.offset_y) * 0.5, o.y, px)
// The bar sliders' groove, a lit tube in it; no ball at rest.
let lo = vec2(an_snap(0.0, o.x, px), cy - 3.3333)
let hi = vec2(an_snap(self.rect_size.x, o.x, px), cy + 2.6667)
var res = an_well(p, lo, hi, 1.0, px, vec3(0.078, 0.094, 0.118), 0.0, self.disabled, 1.0)
let lo2 = min(self.origin_pos, self.slide_pos)
let hi2 = max(self.origin_pos, self.slide_pos)
let f0 = mix(0.0, lo2, self.arc_origin) * self.rect_size.x
let f1 = mix(self.slide_pos, hi2, self.arc_origin) * self.rect_size.x
res = an_fill(res, p, vec2(max(f0, lo.x + px), lo.y + px), vec2(min(f1, hi.x - px), hi.y - px), vec2(lo.x + px, hi.x - px), px, self.disabled)
let hx = self.slide_pos * self.rect_size.x
let show = max(self.hover, self.drag)
let cap = an_cap(p, vec2(hx - 3.0, cy - 5.0), vec2(hx + 3.0, cy + 5.0), 1.5, px, 0.0, 0.0, self.disabled, 0.0, 1.0, vec3(0.0, 0.0, 0.0), vec4(0.270, 0.298, 0.330, 1.0), 1.0)
return Finish.over(res, cap * show)
}
// The round slider: a label column, then the bar slider's 4 point slot with
// the lit fill and the round cap. The slot ends short of the right edge, so
// the value the widget writes there stands on the ground, not on the track.
mod.widgets.SliderRoundFlat.clip_x = false
mod.widgets.SliderRoundFlat.clip_y = false
mod.widgets.SliderRoundFlat.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.SliderRoundFlat.draw_bg.pixel = fn() {
let m = 6.0
let px = an_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 = 8.0
let cy = an_snap(self.rect_size.y * 0.5, o.y, px)
let x0 = an_snap(self.label_size, o.x, px)
let lo = vec2(x0, cy - 3.3333)
let hi = vec2(an_snap(self.rect_size.x - 37.0, o.x, px), cy + 2.6667)
var res = an_well(p, lo, hi, 2.0, px, vec3(0.078, 0.094, 0.118), 0.0, self.disabled, 1.0)
let span = hi.x - lo.x - rad * 2.0
let hx = lo.x + rad + self.slide_pos * span
res = an_fill(res, p, vec2(lo.x + px, lo.y + px), vec2(hx, hi.y - px), vec2(lo.x + px, hi.x - px), px, self.disabled)
let held = max(self.drag, self.focus)
return an_handle(res, p, vec2(hx, cy), rad, px, self.hover, held, self.disabled)
}
// The fader: a 3 point slot with a black centre line, a ladder of LED dots
// beside it lit from the bottom up to the value, a tick scale on the other
// side, and lacquer red's cap: the slider bench's crossfader block, drawn by
// the cap engine (`mod.sdf.Cap`), a low block of smoked chrome 30 points
// along the travel and 0.55 of the control across (22 on a 40 point fader),
// 6.5 points high, its ends chamfered up to a level top, its sides falling
// over a rounded shoulder, six ribs cut across the middle of its top. The
// block is lacquer red's number for number, its material and its ink
// included; the plain sky it mirrors stands over this sheet's ground.
// Hovered, it lifts; held, it sinks half a point and the lift goes;
// focused, the sheet's blue ring; disabled, it dulls and throws no shadow.
mod.widgets.SliderFader.cap_size = 30.0
mod.widgets.SliderFader.draw_bg.track_size = uniform(3.0)
mod.widgets.SliderFader.draw_bg.cap_cross = uniform(0.55)
mod.widgets.SliderFader.clip_x = false
mod.widgets.SliderFader.clip_y = false
// The block's material, lacquer red's, as uniforms so the tweaker moves it
// live: the key light (screen space, x right, y down, z toward the viewer)
// and its intensity; the highlight, its roughness, the sheen and the rim;
// the occlusion and its reach; the plain sky's reflection and how near the
// eye is; metal, spun grain and glow; the exposure in stops and the
// highlight roll-off; the cast shadow, its blur and the contact shadow; the
// slot's inner shadow (unused here) and the block's height in points.
mod.widgets.SliderFader.draw_bg.cap_light = uniform(vec3(-0.3, -0.62, 0.72))
mod.widgets.SliderFader.draw_bg.cap_light_intensity = uniform(2.0)
mod.widgets.SliderFader.draw_bg.cap_specular = uniform(1.0)
mod.widgets.SliderFader.draw_bg.cap_roughness = uniform(0.72)
mod.widgets.SliderFader.draw_bg.cap_gloss = uniform(1.0)
mod.widgets.SliderFader.draw_bg.cap_rim = uniform(1.0)
mod.widgets.SliderFader.draw_bg.cap_ao = uniform(1.0)
mod.widgets.SliderFader.draw_bg.cap_ao_reach = uniform(4.0)
mod.widgets.SliderFader.draw_bg.cap_reflection = uniform(1.0)
mod.widgets.SliderFader.draw_bg.cap_reflection_perspective = uniform(0.67)
mod.widgets.SliderFader.draw_bg.cap_metal = uniform(1.0)
mod.widgets.SliderFader.draw_bg.cap_spun = uniform(0.0)
mod.widgets.SliderFader.draw_bg.cap_glow = uniform(0.0)
mod.widgets.SliderFader.draw_bg.cap_exposure = uniform(0.0)
mod.widgets.SliderFader.draw_bg.cap_rolloff = uniform(0.6)
mod.widgets.SliderFader.draw_bg.cap_shadow = uniform(0.36)
mod.widgets.SliderFader.draw_bg.cap_shadow_blur = uniform(2.5)
mod.widgets.SliderFader.draw_bg.cap_contact = uniform(0.4)
mod.widgets.SliderFader.draw_bg.cap_inner = uniform(0.42)
mod.widgets.SliderFader.draw_bg.cap_height = uniform(6.5)
// The inks: lacquer red's smoked chrome, white light and its shadow
// multiplier; the ground the chrome mirrors is this sheet's.
mod.widgets.SliderFader.draw_bg.ground_color = uniform(theme.color_bg_app)
mod.widgets.SliderFader.draw_bg.handle_color = uniform(#343338)
mod.widgets.SliderFader.draw_bg.light_color = uniform(#ffffff)
mod.widgets.SliderFader.draw_bg.shadow_color = uniform(#5c6066)
// How far the fader's quad reaches past its rect, in points: the throw of
// the block's shadow from its height along the key light, the blur at its
// tip (the engine holds it between 0.35 and 1 times the height), the glow's
// reach when there is one, and four points for the contact shadow's tail
// and the focus ring's breath; never less than the 5 the slot's lip needs.
let an_fader_room = fn(l: vec3, height: float, blur: float, glow: float, cap: float) -> float {
let n = normalize(l)
let rise = max(height, 0.0)
let reach = rise * 0.7 * length(n.xy) / max(n.z, 0.25)
let tip = clamp(blur, max(rise * 0.35, 1.0), max(rise, 1.0))
return max(reach + tip + step(0.001, glow) * max(cap * 0.4, 1.0) * 4.0 + 4.0, 5.0)
}
mod.widgets.SliderFader.draw_bg.vertex = fn() {
let m = an_fader_room(self.cap_light, self.cap_height, self.cap_shadow_blur, self.cap_glow, self.cap_px)
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 = an_fader_room(self.cap_light, self.cap_height, self.cap_shadow_blur, self.cap_glow, self.cap_px)
let px = an_px(self.draw_pass.dpi_factor)
let o = self.rect_pos
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 mid = across * 0.5
// The slot, snapped on screen.
let slo = mix(vec2(self.inset_px, mid - 1.5), vec2(mid - 1.5, self.inset_px), v)
let shi = mix(vec2(sz.x - self.inset_px, mid + 1.5), vec2(mid + 1.5, sz.y - self.inset_px), v)
let lo = vec2(an_snap(slo.x, o.x, px), an_snap(slo.y, o.y, px))
let hi = vec2(an_snap(shi.x, o.x, px), an_snap(shi.y, o.y, px))
var res = an_well(p, lo, hi, 0.0, px, vec3(0.078, 0.094, 0.118), 0.0, self.disabled, 1.0)
let cl = 1.0 - smoothstep(px * 0.4, px * 0.6, abs(b - mid))
let inslot = step(self.inset_px + px, a) * step(a, along - self.inset_px - px)
res = Finish.over(res, vec4(0.067, 0.071, 0.086, 1.0) * cl * inslot)
// The ladder: 2.5 point dots at a 5 point pitch, lit up to the value.
let pitch = 5.0
let k = floor((a - foot) / pitch + 0.5)
let ka = foot + k * pitch
let dc = mid - 9.0
let inrange = step(0.0, k) * step(ka, foot + travel + 0.1)
let dotp = mix(vec2(ka, dc), vec2(dc, sz.y - ka), v)
let lit = step(ka, foot + self.slide_pos * travel + 0.1) * (1.0 - self.disabled)
res = mix(res, an_led(res, p, dotp, vec2(1.25 - px, 1.25 - px), lit, 0.0, px, vec3(0.94, 0.984, 1.0), vec3(0.290, 0.560, 0.930)), inrange)
// The scale: a 1 dp tick at every dot, the ends and middle longer.
let tk = mid + 7.5
let major = step(abs(k - floor(travel / pitch * 0.5 + 0.5)), 0.1) + step(k, 0.1) + step(abs(ka - (foot + travel)), 0.1)
let tlen = mix(2.5, 4.5, clamp(major, 0.0, 1.0))
let tick = (1.0 - smoothstep(px * 0.4, px * 0.6, abs(a - ka))) * step(tk, b) * step(b, tk + tlen) * inrange
res = Finish.over(res, vec4(0.361, 0.369, 0.4, 1.0) * tick)
// THE CAP, lacquer red's crossfader block: corner 0.08 of the smaller
// half, edge 0.2, blend 0.02, the height less the half point a press
// sinks it; six ribs 0.3 deep over the middle 0.8 of its length. Its
// leading end (the left one lying, the top one standing) sits on the
// device pixel grid, so its ends stand crisp as it moves.
let live = 1.0 - self.disabled
let ab = vec2(a, b)
let hs = vec2(self.cap_px * 0.5, min(max(across * self.cap_cross, 4.0), across) * 0.5)
let at_free = foot + self.slide_pos * travel
let lead = Finish.snap(mix(o.x + at_free - hs.x, o.y + sz.y - at_free - hs.x, v), px)
let at = mix(lead - o.x + hs.x, o.y + sz.y - lead - hs.x, v)
let l = normalize(self.cap_light)
let lt = Cap.light(l, v)
let ground = self.ground_color.rgb
let light_ink = self.light_color.rgb
let shadow_ink = self.shadow_color.rgb
let body = self.handle_color.rgb
let ev = pow(2.0, self.cap_exposure)
let ro = self.cap_rolloff
let press = self.drag * live
let shape = vec4(0.08, 0.2, 0.02, max(self.cap_height - 0.5 * press, 0.5))
let rib = vec4(6.0, 0.3, 0.1, 0.9)
let zero4 = vec4(0.0, 0.0, 0.0, 0.0)
let q = ab - vec2(at, mid)
let r = shape.x * min(hs.x, hs.y)
let fp = Cap.sd_cap(q, hs, r, vec2(1.0, 0.0))
let cov = Cap.cover(fp.x, px)
var cap = body
var under = 0.0
var halo = 0.0
// Only a pixel within reach of the cap and what it throws runs the rest.
let far = abs(q) - hs
if max(far.x, far.y) < m + 2.0 {
// lacquer red's curves: along, chamfered up from each end to the
// level top; across, level then over the rounded shoulder; the
// two-step edge; the rib profile tiled over the rib band
let c_along = Cap.curve((q.x / hs.x + 1.0) * 0.5, 6.0, vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(0.07, 0.8), vec2(0.0, 0.0), vec2(-0.0233, 0.0), vec2(0.14, 1.0), vec2(0.0233, 0.0), vec2(-0.0233, 0.0), vec2(0.86, 1.0), vec2(0.0233, 0.0), vec2(0.0, 0.0), vec2(0.93, 0.8), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 0.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 0.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 0.0))
let c_across = Cap.curve(abs(q.y) / hs.y, 4.0, vec2(0.0, 1.0), vec2(0.1333, 0.0), vec2(-0.0433, 0.014), vec2(0.8, 0.98), vec2(0.0433, -0.014), vec2(-0.0233, 0.1143), vec2(0.93, 0.7), vec2(0.0233, -0.1143), vec2(0.0, 0.0), vec2(1.0, 0.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 0.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 0.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 0.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 0.0))
let c_edge = Cap.curve(Cap.edge_x(fp.x, hs, shape.y), 3.0, vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(0.5, 0.55), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 1.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 1.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 1.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 1.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 1.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 1.0))
let c_tooth = Cap.curve(Cap.rib_x(q, hs, rib), 4.0, vec2(0.0, 1.0), vec2(0.0917, 0.0), vec2(-0.05, 0.0), vec2(0.55, 1.0), vec2(0.05, 0.0), vec2(0.0, 0.0), vec2(0.7, 0.2), vec2(0.0, 0.0), vec2(-0.05, 0.0), vec2(1.0, 0.0), vec2(0.05, 0.0), vec2(0.0, 0.0), vec2(1.0, 0.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 0.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 0.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 0.0))
let f = Cap.field(q, hs, fp, c_along, c_across, c_edge, c_tooth, shape, rib, zero4, zero4, zero4)
let n = Cap.normal(f.yz, shape.w, v)
let eye = Cap.view(q, hs, self.cap_reflection_perspective, v)
cap = Cap.shade(n, l, eye, f.x, body, 0.0, 0.0, 1.0, self.cap_specular, self.cap_roughness, self.cap_gloss, self.cap_rim, self.cap_ao, self.cap_ao_reach, self.cap_reflection, self.cap_metal, self.cap_light_intensity, body, light_ink, shadow_ink, ground)
let grain = Cap.spun(q, hs, lt.xy) * self.cap_spun
cap = Cap.in_shadow(Cap.in_light(cap, clamp(grain, 0.0, 1.0) * 0.55, light_ink), clamp(-grain, 0.0, 1.0) * 0.42, shadow_ink)
cap = Cap.emit(cap, f.x, body, light_ink, self.cap_glow)
// Under it, the cast shadow from the block's top outline (0.862 of
// the length, 0.8655 of the width, its corner 0.253) and the
// contact shade; disabled, the cast shadow goes and the contact
// thins.
let sh = Cap.shadow(q, hs, r, vec3(0.862, 0.8655, 0.253), lt, shape.w, self.cap_shadow_blur, self.cap_shadow * live, px)
let ct = Cap.contact(fp.x, max(shape.w * 0.3, 1.0), self.cap_contact * (1.0 - 0.6 * self.disabled))
under = clamp(sh + ct, 0.0, 1.0) * (1.0 - cov)
halo = Cap.glow(fp.x, hs, self.cap_glow) * 0.9 * (1.0 - cov)
}
// The block toned as the bench tones. Hovered, it lifts, twice as much
// when it stands less than 23.5 points tall on screen; held, the lift
// goes; disabled, it dulls, grey drawn into it.
var face = Cap.tone(cap, ev, ro)
let small = 1.0 - step(23.5, mix(hs.y, hs.x, v) * 2.0)
face = face + vec3(0.04, 0.04, 0.042) * (1.0 + small) * self.hover * (1.0 - press) * live
let fl = Finish.lum(face)
face = mix(face, mix(face, vec3(fl, fl, fl), 0.45) * 0.8, self.disabled)
// Laid over the slot, the ladder and the scale and whatever the fader
// lies on, premultiplied: the shadow darkens all of it by the shadow
// ink's mean, a glow adds its light, the block covers it.
res = Finish.over(res, vec4(0.0, 0.0, 0.0, under * (1.0 - (shadow_ink.x + shadow_ink.y + shadow_ink.z) / 3.0)))
res = res + vec4(Cap.tone(body * halo, ev, ro), 0.0)
res = mix(res, vec4(face, 1.0), cov)
// Focus: the sheet's blue ring just outside the outline, and a breath
// of it.
let ring = Finish.ring_out(fp.x, 0.0, px, px) * 0.9 + Finish.glow(fp.x - px, 0.7) * 0.35 * (1.0 - Finish.cover(fp.x, px))
return Finish.over(res, vec4(0.290, 0.560, 0.930, 1.0) * clamp(ring, 0.0, 1.0) * self.focus * live)
}
// ---------------------------------------------------------------- knobs
// How much of one shape in the room a mirrored direction sees: an oval in
// azimuth `a` and height `z`, centred on `a0`, `z0` with half sizes `wa`,
// `wz`; its edge sharp, softened by `soft` (a share of the oval). Seen on a
// turned band it is a crescent that tapers at both ends.
let an_room_patch = fn(a: float, z: float, a0: float, wa: float, z0: float, wz: float, soft: float) -> float {
let da = atan2(sin(a - a0), cos(a - a0)) / wa
let dz = (z - z0) / wz
return 1.0 - smoothstep(1.0 - soft, 1.0, da * da + dz * dz)
}
// The room a knob's chrome bezel mirrors, fixed on the screen so the pattern
// stands still as the knob turns: grey walls, pale toward the viewer, darker
// at the height of the bezel's middle, a little paler again toward the
// panel round the knob, and paler overhead than below; a big soft box up
// and to the left and a bright window down and to the right; a dark doorway
// on either side; a pale blue-grey lamp low down and a dim blue-grey shelf
// up and to the left, so the chrome stays cool like the sheet's metal. Each shape is an oval with a sharp edge,
// so on the turned bezel it lies as a crisp crescent, as reflections lie in
// polished chrome. `r` the mirrored direction (x right, y down, z toward
// the viewer); `wide` 1 on a band wide enough for the detail, 0 on a narrow
// one, where the edges soften and the lamp and the shelf, too small there
// to read as anything but grit, are left out.
let an_chrome_room = fn(r: vec3, wide: float) -> vec3 {
let a = atan2(r.y, r.x)
let z = r.z
let k = mix(0.45, 0.25, wide)
var w = mix(0.40, 0.68, smoothstep(0.0, 0.6, z))
w = mix(w, 0.56, 1.0 - smoothstep(-0.70, -0.35, z))
var c = vec3(w, w + 0.02, w + 0.045) + vec3(0.07, 0.07, 0.07) * clamp(-r.y, -1.0, 1.0)
c = mix(c, vec3(1.0, 1.0, 1.0), an_room_patch(a, z, -2.25, 0.62, 0.05, 0.50, k))
c = mix(c, vec3(0.97, 0.97, 0.96), an_room_patch(a, z, 0.80, 0.55, -0.15, 0.55, k))
c = mix(c, vec3(0.06, 0.06, 0.07), an_room_patch(a, z, 0.0, 0.42, -0.30, 0.55, k))
c = mix(c, vec3(0.10, 0.11, 0.13), an_room_patch(a, z, 3.14159265, 0.50, 0.25, 0.60, k))
c = mix(c, vec3(0.66, 0.71, 0.78), an_room_patch(a, z, 1.80, 0.40, 0.30, 0.42, k) * wide)
c = mix(c, vec3(0.40, 0.44, 0.50), an_room_patch(a, z, -2.80, 0.22, 0.45, 0.30, k) * wide)
return c * mix(1.0, 0.9, smoothstep(0.55, 0.90, z))
}
// A knob: a slim bezel of dark glass round a cap with a polished chrome
// bezel and a face of the ground's own blue-grey, on a soft wide shadow;
// the value lit in the bezel as a glowing arc whose halo runs out onto the
// ground, and an LED dot on the face for a pointer. Angles run from
// straight down, clockwise, the gap at the bottom.
let an_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 {
// The bezel scales with a small knob and stops at a 0.8 point gap and a
// 2.8 point width, so the cap takes most of the knob.
let rc = max(rr / 1.17, rr - 3.6)
let ring_in = rc + min(0.05 * rc, 0.8)
let ring_out = ring_in + min(0.16 * rc, 2.8)
let ring_mid = (ring_in + ring_out) * 0.5
let ring_w = ring_out - ring_in
let dr = Finish.sd_ring(p, c, ring_mid, ring_w)
let q = p - c
let rl = length(q)
// The ring well: lip on the ground under its lower outer edge, the
// black outline both sides, a dark fill.
// Round it on the ground a thin bright bezel line, lit on the top and
// on the foot, quieter down the sides.
let nk = q / max(rl, 0.001)
let bzl = mix(0.30, 0.62, smoothstep(0.3, 0.95, abs(nk.y)))
var res = vec4(0.62, 0.66, 0.71, 1.0) * Finish.ring_out(rl - ring_out, 0.0, px, px) * bzl
// The bezel: near-black glass whose top catches the light above.
var col = vec3(0.035, 0.043, 0.055)
let refl = smoothstep(0.35, 0.95, -nk.y) * exp(-((rl - ring_mid) * (rl - ring_mid)) / (2.0 * 0.35 * ring_w * 0.35 * ring_w))
col = col + vec3(0.380, 0.450, 0.530) * refl * 0.75
col = mix(col, vec3(0.012, 0.014, 0.020), Finish.band(dr, 0.0, px, px))
let ra = Finish.cover(dr, px)
res = Finish.over(res, vec4(col * ra, ra))
// The cap's radii in whole device pixels from the snapped centre, so
// every line lands on a pixel row: out to in, a dark metal collar, a
// dark line, the chrome band, a dark line where it meets the face, and
// the face, two thirds of the cap across. The band is 0.12 of the
// radius and never under two and a half device pixels, so on a small
// knob it is a thinner ring that still reads as chrome.
let on = 1.0 - disabled
let R = Finish.snap(rc, px)
let lip_w = max(1.5 * px, Finish.snap(0.13 * R, px))
let ol_w = max(px, Finish.snap(0.035 * R, px))
let ch_w = max(2.5 * px, Finish.snap(0.12 * R, px))
let il_w = max(px, Finish.snap(0.018 * R, px))
let r_ol = R - lip_w
let r_ch = r_ol - ol_w
let r_il = r_ch - ch_w
let rf = r_il - il_w
// The soft wide shadow the cap sits on: a close one under it on the
// bezel, and a wide one round the knob and its bezel together that
// falls a little down and fades over a few points of the ground; both
// under the lit arc. Faded out before the quad's edge.
let s1 = min(0.07 * R, 2.5)
let s2 = min(0.13 * R, 4.5)
let rs = ring_out * 0.97
let sh1 = Finish.drop(p, c, vec2(R, R), R, vec2(0.0, 0.06 * R), s1) * 0.55
let sh2 = Finish.drop(p, c, vec2(rs, rs), rs, vec2(0.0, min(0.07 * R, 3.0)), s2) * 0.6
let fade = 1.0 - smoothstep(rr + 6.0, rr + 10.5, rl)
let sh = (1.0 - (1.0 - sh1) * (1.0 - sh2)) * fade * (1.0 - 0.5 * disabled)
res = Finish.over(res, vec4(0.0, 0.0, 0.0, sh))
// The value arc, lit like the lamp: 1.5 points on the middle of the
// ring, a pale core, the accent at its edges and a soft halo on the
// ring's floor round it. Angles from straight down, clockwise.
let one = PI / 180.0
let sweep = 2.0 * PI - gap * one
let start = gap * one * 0.5
let ve = start + sweep * slide
let oe = start + sweep * origin
let a0 = mix(start, min(oe, ve), arc_origin)
let a1 = mix(ve, max(oe, ve), arc_origin)
var ang = atan2(-q.x, q.y)
if ang < 0.0 {
ang = ang + 2.0 * PI
}
let ac = clamp(ang, a0, a1)
let ap = c + vec2(-sin(ac), cos(ac)) * ring_mid
let da = length(p - ap)
let live = step(0.002, a1 - a0)
let ink = mix(vec3(0.320, 0.660, 1.0), vec3(0.227, 0.29, 0.388), disabled)
let floor_in = Finish.cover(dr + px, px)
// The arc glows: a bright halo on the bezel round it and a wide soft one
// that runs out over the bezel's rim onto the ground.
let xa = max(da - 1.0, 0.0)
let ga = (0.55 * exp(-(xa * xa) / (2.0 * 0.9 * 0.9)) * floor_in + 0.38 * exp(-(xa * xa) / (2.0 * 2.4 * 2.4))) * (1.0 - Finish.cover(da - 1.0, px)) * (1.0 - disabled) * live
res = Finish.over(res, vec4(0.320, 0.660, 1.0, 1.0) * ga)
let acol = mix(mix(vec3(0.320, 0.680, 1.0), vec3(1.0, 1.0, 1.0), 0.72 * (1.0 - disabled)), ink, smoothstep(0.1, 0.7, da))
res = Finish.over(res, vec4(acol, 1.0) * Finish.cover(da - 1.0, px) * live)
// The cap, out to in. `t` runs down the cap, top to foot.
let t = clamp((p.y - (c.y - R)) / max(2.0 * R, 1.0), 0.0, 1.0)
// The collar: dark blue-grey metal round the chrome, lit where it faces
// up (0.55 at the crown), about 0.3 down the sides and 0.2 at the foot,
// with a pale row on its outer top edge.
var kc = mix(vec3(0.185, 0.205, 0.235), vec3(0.52, 0.555, 0.60), smoothstep(-0.6, 1.0, -nk.y))
kc = mix(kc, vec3(0.70, 0.735, 0.775), Finish.band(rl - R, px, 2.0 * px, px) * smoothstep(0.3, 0.95, -nk.y) * 0.8)
// The dark outer line.
kc = mix(kc, vec3(0.025, 0.028, 0.035), Finish.cover(rl - r_ol, px))
// The chrome: across the band the surface turns from nearly facing the
// viewer (its inner edge) to facing out and back (its outer edge), and
// it mirrors the room (`an_chrome_room`) along that turn. The room
// stands still, so as the knob turns its chrome does not. On a narrow
// band the turn is shallower, so a small knob's chrome shows a few
// broad patches rather than a stack of hairlines. Under the hand it
// brightens a touch.
let u = clamp((rl - r_il) / max(ch_w, px), 0.0, 1.0)
let wide = smoothstep(3.0, 9.0, ch_w / px)
let th = mix(mix(0.62, 0.30, wide), mix(0.98, 1.20, wide), u)
var chrome = an_chrome_room(vec3(nk * sin(2.0 * th), cos(2.0 * th)), wide)
chrome = min(chrome * (1.0 + 0.10 * hover) + vec3(0.05, 0.05, 0.05) * hover, vec3(1.0, 1.0, 1.0))
kc = mix(kc, chrome, Finish.cover(rl - r_ch, px))
// The dark line where the chrome meets the face, darkest at the top.
kc = mix(kc, mix(vec3(0.045, 0.050, 0.060), vec3(0.090, 0.100, 0.115), t), Finish.cover(rl - r_il, px))
// The face: the ground's own blue-grey, as the pale face in the
// reference is its pale ground's colour: a little lighter than the
// ground over its upper half, a little darker at the foot, with a broad
// soft sheen up and to the left.
let tf = clamp((p.y - (c.y - rf)) / max(2.0 * rf, 1.0), 0.0, 1.0)
var face = mix(vec3(0.270, 0.300, 0.335), vec3(0.130, 0.155, 0.180), smoothstep(0.15, 1.0, tf))
let fsp = (q - vec2(-0.30 * rf, -0.25 * rf)) / max(0.65 * rf, 1.0)
face = face + vec3(0.065, 0.068, 0.072) * exp(-dot(fsp, fsp)) + vec3(0.02, 0.022, 0.025) * hover
let fa = Finish.cover(rl - rf, px)
kc = mix(kc, face, fa)
// Disabled: greyed and dimmed, the chrome gone dull.
let kl = Finish.lum(kc)
kc = mix(kc, vec3(kl, kl, kl) * 0.55 + vec3(0.05, 0.055, 0.065), disabled)
let ka = Finish.cover(rl - R, px)
res = Finish.over(res, vec4(kc * ka, ka))
// The pointer: an LED dot on the face toward the value, 0.72 of the
// face's radius out, a white-hot core in the lamp's blue rim, blooming
// blue on the face round it about its own size; held, the bloom and
// the lamp's halo swell.
let vd = vec2(-sin(ve), cos(ve))
let rd = max(0.08 * rf, 1.25)
let dc = vd * (0.72 * rf)
let dd = max(length(q - dc) - rd, 0.0)
let bloom = exp(-(dd * dd) / max(2.0 * rd * rd, 0.001)) * (0.50 + 0.25 * held) * fa * on
res = Finish.over(res, vec4(0.290, 0.600, 1.0, 1.0) * bloom)
res = an_led(res, q, dc, vec2(rd, rd), on, 0.6 + 0.4 * held, px, vec3(0.78, 0.89, 1.0), vec3(0.290, 0.560, 0.930))
// Focus: the lamp's blue ring just outside the cap, and a breath of it.
let fr = Finish.ring_out(rl - R, 0.0, px, px) * 0.9 + Finish.glow(rl - R - px, 0.7) * 0.3 * (1.0 - ka)
res = Finish.over(res, vec4(0.290, 0.560, 0.930, 1.0) * clamp(fr, 0.0, 1.0) * focus * on)
return res
}
// The quad runs 10 points past the box, room for the knob's wide shadow.
mod.widgets.RotaryKnob.clip_x = false
mod.widgets.RotaryKnob.clip_y = false
mod.widgets.RotaryKnob.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.RotaryKnob.draw_bg.pixel = fn() {
let m = 10.0
let px = an_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 an_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 = 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.RotaryFlat.draw_bg.pixel = fn() {
let m = 10.0
let px = an_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 an_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)
}
// ---------------------------------------------------------------- fields
// A field well that keeps inside its own box: a text field clips what it
// holds, so its right outline takes the box's last whole column and its lip
// the last whole row. Focus lights a lamp along the foot of the well: a
// pale line with the lamp's rim colour under it and a soft halo rising on
// the floor; the outline takes a little of the blue.
let an_field = fn(p: vec2, size: vec2, o: vec2, px: float, fill: vec3, hover: float, focus: float, disabled: float) -> vec4 {
// The well keeps one device pixel of the box on every side for its
// bezel: the box's far edges rounded DOWN to a whole device pixel.
let lo = vec2(an_snap(0.0, o.x, px) + px, an_snap(0.0, o.y, px) + px)
let hi = vec2(an_snap(size.x - px * 0.49, o.x, px) - px, an_snap(size.y - px * 0.49, o.y, px) - px)
let c = (lo + hi) * 0.5
let d = Material.sd_box(p, c, (hi - lo) * 0.5, 3.0)
// Under the hand the floor lifts 0.025 and the outline takes a fifth of
// the lamp's blue.
var f = mix(fill + vec3(0.025, 0.025, 0.027) * hover, vec3(0.125, 0.141, 0.165), disabled)
var res = an_well(p, lo, hi, 3.0, px, f, 0.0, disabled, 0.0)
// The floor rises toward the foot, where the lip's light comes in
// (luminance 0.10 under the top to 0.17 at the foot, as measured).
let tt = clamp((p.y - lo.y) / max(hi.y - lo.y, 1.0), 0.0, 1.0)
res = Finish.over(res, vec4(0.420, 0.460, 0.520, 1.0) * smoothstep(0.15, 1.0, tt) * 0.17 * Finish.cover(d + px, px) * (1.0 - disabled))
// The bezel, one device pixel round the outline: a light row on the
// top (#69737e), quieter down the sides (#4f5761), and a near-white lip
// on the foot (#d2dae2) where the light from the top lands.
let bz = Finish.ring_out(d, 0.0, px, px)
var bc = mix(vec3(0.412, 0.451, 0.494), vec3(0.310, 0.341, 0.380), smoothstep(0.0, 0.35, tt))
bc = mix(bc, vec3(0.824, 0.855, 0.886), step(hi.y - 1.0, p.y) * smoothstep(0.0, 3.0, min(p.x - lo.x, hi.x - p.x) + 1.5))
res = Finish.over(res, vec4(bc, 1.0) * bz * (1.0 - 0.35 * disabled))
let fo = focus * (1.0 - disabled)
let ho = hover * (1.0 - disabled) * (1.0 - focus)
res = Finish.over(res, vec4(0.290, 0.560, 0.930, 1.0) * Finish.band(d, 0.0, px, px) * (0.45 * fo + 0.2 * ho))
// The foot lamp: the two rows above the bottom outline, inset past the
// corners.
let y1 = hi.y - px
let infloor = Finish.cover(d + px, px) * step(lo.x + 3.0, p.x) * step(p.x, hi.x - 3.0)
let dy = max(y1 - px - p.y, 0.0)
let g = (0.34 * exp(-(dy * dy) / (2.0 * 0.7 * 0.7)) + 0.14 * exp(-(dy * dy) / (2.0 * 2.0 * 2.0))) * step(p.y, y1 - px) * infloor
res = Finish.over(res, vec4(0.290, 0.560, 0.930, 1.0) * g * fo)
let rim = step(y1 - px, p.y) * step(p.y, y1) * infloor
let core = step(y1 - px * 2.0, p.y) * step(p.y, y1 - px) * infloor
res = Finish.over(res, vec4(0.290, 0.560, 0.930, 1.0) * rim * fo)
res = Finish.over(res, vec4(0.84, 0.94, 1.0, 1.0) * core * fo)
return res
}
// Text fields: a sunk well 26 points tall with a metal lip under it. An
// input with no fill of its own (the one a slider, a combo box or a well
// holds) draws nothing.
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 = an_px(self.draw_pass.dpi_factor)
return an_field(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, vec3(0.078, 0.094, 0.118), self.hover, self.focus, self.disabled)
}
mod.widgets.TextInputFlat.padding = mod.turtle.Inset{left: 8.0 right: 8.0 top: 4.0 bottom: 4.0}
mod.widgets.TextInputFlat.min_height = 26.0
mod.widgets.TextInputFlat.draw_cursor.color = #f0fbff
mod.widgets.TextInputFlat.draw_text.color = #d4dae2
mod.widgets.TextInputFlat.draw_text.color_empty = #7d8793
mod.widgets.TextInputFlat.draw_text.color_disabled = #626a74
// The number field: the same well, its stepper a pair of small caps joined
// by an engraved line.
mod.widgets.NumberField.draw_bg.pixel = fn() {
let px = an_px(self.draw_pass.dpi_factor)
return an_field(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, vec3(0.078, 0.094, 0.118), self.hover, self.focus, self.disabled)
}
mod.widgets.NumberField.spin_width = 16.0
mod.widgets.NumberSpin.width = 16.0
// The widget's own filled triangles are hidden; the face strokes a 5 x 3
// point chevron in a 1 dp line of #9aa2ac on each cap instead.
mod.widgets.NumberSpin.draw_text.color = #00000000
mod.widgets.NumberSpin.draw_text.color_disabled = #00000000
mod.widgets.NumberSpin.draw_bg.pixel = fn() {
let px = an_px(self.draw_pass.dpi_factor)
let o = self.rect_pos
let p = self.pos * self.rect_size
// The stepper stands in the field's well as two small glossy caps, one
// over the other, two device pixels in from the well's outline and a
// device pixel of floor between them. Under the hand a cap lifts;
// pressed, it sinks.
let x0 = an_snap(0.0, o.x, px) + px
let xr = an_snap(self.rect_size.x - px * 0.49, o.x, px) - px * 4.0
let yt = an_snap(0.0, o.y, px) + px * 4.0
let yb = an_snap(self.rect_size.y - px * 0.49, o.y, px) - px * 4.0
let mid = an_snap(self.rect_size.y * 0.5, o.y, px)
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)
var res = an_cap(p, vec2(x0, yt), vec2(xr, mid - px), 1.5, px, hu, du, self.disabled, 0.0, 0.0, vec3(0.0, 0.0, 0.0), vec4(0.270, 0.298, 0.330, 1.0), 1.0)
res = Finish.over(res, an_cap(p, vec2(x0, mid + px), vec2(xr, yb), 1.5, px, hd, dd, self.disabled, 0.0, 0.0, vec3(0.0, 0.0, 0.0), vec4(0.270, 0.298, 0.330, 1.0), 1.0))
// The chevrons: 5 x 3 points in a 1 dp line of #c3c9d1.
let ink = mix(vec3(0.765, 0.788, 0.820), vec3(0.36, 0.37, 0.4), self.disabled)
let cx = (x0 + xr) * 0.5
let cu = (yt + mid) * 0.5 + px * 0.5 * du
let cd = (mid + yb) * 0.5 + px * 0.5 * dd
let du2 = min(an_seg(p, vec2(cx - 2.5, cu + 1.25), vec2(cx, cu - 1.25)), an_seg(p, vec2(cx, cu - 1.25), vec2(cx + 2.5, cu + 1.25)))
let dd2 = min(an_seg(p, vec2(cx - 2.5, cd - 1.25), vec2(cx, cd + 1.25)), an_seg(p, vec2(cx, cd + 1.25), vec2(cx + 2.5, cd - 1.25)))
let k = min(du2, dd2) - px * 0.8
return Finish.over(res, vec4(ink, 1.0) * Finish.cover(k, px) * (0.4 + 0.6 * live))
}
// The drop-down: a button cap with an 18 point chevron zone at its right,
// behind an engraved divider.
mod.widgets.DropDownFlat.padding = mod.turtle.Inset{left: 8.0 right: 26.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 = 7.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 engraved divider and the chevron, laid on a cap or a well.
let an_chevron_zone = fn(dst: vec4, p: vec2, lo: vec2, hi: vec2, px: float, disabled: float) -> vec4 {
let dx = hi.x - 18.0
let inside = step(lo.y + 4.0, p.y) * step(p.y, hi.y - 4.0)
let dark = step(dx - px, p.x) * step(p.x, dx) * inside
let light = step(dx, p.x) * step(p.x, dx + px) * inside
var res = Finish.over(dst, vec4(0.039, 0.039, 0.047, 1.0) * dark)
res = Finish.over(res, vec4(0.30, 0.33, 0.37, 1.0) * light * 0.8)
// The chevron: 7 x 4 points, a 1.5 point stroke, a dark line under it.
let cc = vec2(dx + 9.0, (lo.y + hi.y) * 0.5)
let sd = Sdf2d.viewport(p - vec2(0.0, px))
sd.move_to(cc.x - 3.5, cc.y - 2.0)
sd.line_to(cc.x, cc.y + 2.0)
sd.line_to(cc.x + 3.5, cc.y - 2.0)
sd.stroke(vec4(0.0, 0.0, 0.0, 0.6), 1.0)
res = Finish.over(res, sd.result)
let sdf = Sdf2d.viewport(p)
sdf.move_to(cc.x - 3.5, cc.y - 2.0)
sdf.line_to(cc.x, cc.y + 2.0)
sdf.line_to(cc.x + 3.5, cc.y - 2.0)
sdf.stroke(vec4(mix(vec3(0.828, 0.850, 0.878), vec3(0.36, 0.37, 0.4), disabled), 1.0), 1.0)
return Finish.over(res, sdf.result)
}
mod.widgets.DropDownFlat.draw_bg.pixel = fn() {
let m = 7.0
let px = an_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(an_snap(0.0, o.x, px), an_snap(0.0, o.y, px))
let hi = vec2(an_snap(self.rect_size.x, o.x, px), an_snap(self.rect_size.y, o.y, px))
var res = an_cap(p, lo, hi, 4.0, px, self.hover, max(self.down, self.active), self.disabled, self.focus, 1.0, vec3(0.0, 0.0, 0.0), vec4(0.270, 0.298, 0.330, 1.0), 1.0)
return an_chevron_zone(res, p, lo, hi, px, self.disabled)
}
// The combo box: a raised cap of brushed metal, as the pictures draw it,
// its list opened from the same chevron zone; focus rings it in blue.
mod.widgets.ComboBoxFlat.padding = mod.turtle.Inset{left: 8.0 right: 26.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 = 7.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 = 7.0
let px = an_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(an_snap(0.0, o.x, px), an_snap(0.0, o.y, px))
let hi = vec2(an_snap(self.rect_size.x, o.x, px), an_snap(self.rect_size.y, o.y, px))
var res = an_cap(p, lo, hi, 4.0, px, self.hover, 0.0, self.disabled, self.focus, 1.0, vec3(0.0, 0.0, 0.0), vec4(0.270, 0.298, 0.330, 1.0), 1.0)
// The brushing, laid on what shows of the face.
let d = Material.sd_box(p, (lo + hi) * 0.5, (hi - lo) * 0.5, 4.0)
let tx = an_plate_tex(p, px) * Finish.cover(d + px * 2.0, px) * (1.0 - self.disabled)
res = vec4(res.rgb + vec3(tx, tx, tx * 1.05) * res.a, res.a)
return an_chevron_zone(res, p, lo, hi, px, self.disabled)
}
// Open lists: a flat dark face, a 1 dp top line, rows that lift 0.03 under
// the hand, and the chosen row filled with the accent -- the one fill in
// this family.
let an_menu = fn(p: vec2, size: vec2, px: float) -> vec4 {
let lo = vec2(0.0, 0.0)
let hi = size
let c = (lo + hi) * 0.5
let d = Material.sd_box(p, c, (hi - lo) * 0.5, 3.0)
var col = mix(vec3(0.145, 0.165, 0.196), vec3(0.118, 0.133, 0.160), p.y / max(size.y, 1.0))
col = mix(col, vec3(0.32, 0.35, 0.39), Finish.band(d, px, px * 2.0, px) * step(p.y, c.y) * step(3.0, p.y))
col = mix(col, vec3(0.039, 0.043, 0.055), Finish.band(d, 0.0, px, px))
let a = Finish.cover(d, px)
return vec4(col * a, a)
}
let an_row = fn(p: vec2, size: vec2, px: float, hover: float, active: float, disabled: float) -> vec4 {
let top = 1.0 - step(px, p.y)
var col = vec4(1.0, 1.0, 1.0, 1.0) * 0.035 * hover * (1.0 - disabled)
let sel = vec3(0.227, 0.498, 0.753)
let fill = vec4(mix(sel, vec3(0.45, 0.66, 0.86), top), 1.0)
return mix(col, fill, active)
}
// The open list is clipped to its own box by the popup that holds it, so
// its shadow cannot be drawn from here.
mod.widgets.PopupMenuFlat.draw_bg.pixel = fn() {
let px = an_px(self.draw_pass.dpi_factor)
return an_menu(self.pos * self.rect_size, self.rect_size, px)
}
mod.widgets.PopupMenuItem.draw_bg.pixel = fn() {
let px = an_px(self.draw_pass.dpi_factor)
return an_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 = an_px(self.draw_pass.dpi_factor)
return an_menu(self.pos * self.rect_size, self.rect_size, px)
}
mod.widgets.ComboBoxFlat.draw_item.pixel = fn() {
let px = an_px(self.draw_pass.dpi_factor)
return an_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 whole strip one raised cap; the chosen answer
// sinks, flat and tinted toward the light, its legend lit and a lamp under
// it.
mod.widgets.SegmentedControlFlat.draw_bg.vertex = fn() {
let m = 7.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 = 7.0
let px = an_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(an_snap(0.0, o.x, px), an_snap(0.0, o.y, px))
let hi = vec2(an_snap(self.rect_size.x, o.x, px), an_snap(self.rect_size.y, o.y, px))
return an_cap(p, lo, hi, 3.0, px, 0.0, 0.0, 0.0, 0.0, 1.0, vec3(0.0, 0.0, 0.0), vec4(0.270, 0.298, 0.330, 1.0), 1.0)
}
// The sunk segment (the tab in use): pushed in and lit from within like
// the chosen answer -- blue glass, a pale bloom under its top edge falling
// to the foot, two rows of shade under the top, and a 1 dp tinted edge
// either side where it meets its neighbours.
let an_sunk = fn(p: vec2, lo: vec2, hi: vec2, px: float, hover: float) -> vec4 {
let hh = hi.y - lo.y
let t = clamp((p.y - lo.y) / max(hh, 1.0), 0.0, 1.0)
let ux = ((p.x - lo.x) / max(hi.x - lo.x, 1.0)) * 2.0 - 1.0
var col = mix(vec3(0.150, 0.225, 0.315), vec3(0.090, 0.140, 0.205), smoothstep(0.0, 1.0, t)) + vec3(0.01, 0.01, 0.01) * hover
col = col + vec3(0.160, 0.230, 0.320) * exp(-max(p.y - lo.y - px * 2.0, 0.0) / (0.22 * hh)) * (1.0 - 0.45 * ux * ux)
col = mix(col, vec3(0.0, 0.0, 0.0), 0.7 * exp(-max(p.y - lo.y, 0.0) / (1.2 * px)))
let edge = (1.0 - step(lo.x + px, p.x)) + step(hi.x - px, p.x)
col = mix(col, vec3(0.349, 0.412, 0.471), edge * 0.8)
let inside = step(lo.x, p.x) * step(p.x, hi.x) * step(lo.y, p.y) * step(p.y, hi.y)
return vec4(col, 1.0) * inside
}
// The chosen answer is a push button pressed in: Day, Week and Month are
// three caps and the chosen one is sunk deep between the others. Its well
// runs from under the strip's top outline to its bottom outline, black
// under the top edge and shaded along both walls, and the neighbours' ends
// stand lit over it. The strip lays the answers 2 points in from its edge;
// the quad grows so the well can reach the outline and the neighbours' ends.
mod.widgets.SegmentedControlFlat.draw_pill.vertex = fn() {
let m = 13.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))
}
// On the column (its pill's `radius`, unused by this face, set to -1 as the
// mark) the well keeps to its own segment's height -- the answers above and
// below are the neighbours -- and reaches the strip's side outlines instead.
mod.widgets.SegmentedControlFlat.draw_pill.pixel = fn() {
let m = 13.0
let v = 1.0 - step(0.0, self.radius)
let hz = 1.0 - v
let px = an_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(an_snap(-2.0 * v, o.x, px) + px * v, an_snap(-2.0 * hz, o.y, px) + px * hz)
let hi = vec2(an_snap(self.rect_size.x + 2.0 * v, o.x, px) - px * v, an_snap(self.rect_size.y + 2.0 * hz, o.y, px) - px * hz)
let hh = hi.y - lo.y
let t = clamp((p.y - lo.y) / max(hh, 1.0), 0.0, 1.0)
// The chosen cap is pushed in and lit from within, as the picture's
// Week is: blue glass, a pale bloom under its top edge (#40607f there)
// falling to #1a2838 at the foot, two rows of shade under the top edge
// where the neighbours stand over it, the walls shaded.
let ux = ((p.x - lo.x) / max(hi.x - lo.x, 1.0)) * 2.0 - 1.0
var col = mix(vec3(0.150, 0.225, 0.315), vec3(0.090, 0.140, 0.205), smoothstep(0.0, 1.0, t))
col = col + vec3(0.160, 0.230, 0.320) * exp(-max(p.y - lo.y - px * 2.0, 0.0) / (0.22 * hh)) * (1.0 - 0.45 * ux * ux)
let tx = an_plate_tex(p, px) * 0.6
col = col + vec3(tx, tx, tx * 1.05)
let top = 0.7 * exp(-max(p.y - lo.y, 0.0) / (1.2 * px))
let wl = 0.6 * exp(-max(p.x - lo.x, 0.0) / (1.6 * px))
let wr = 0.6 * exp(-max(hi.x - px - p.x, 0.0) / (1.6 * px))
let dk = 1.0 - (1.0 - top) * (1.0 - wl) * (1.0 - wr)
col = mix(col, vec3(0.0, 0.0, 0.0), dk)
// A blue-white rim along the top, under the shade: the glass's edge.
let rimt = step(lo.y + px * 2.0, p.y) * (1.0 - step(lo.y + px * 3.0, p.y)) * (1.0 - 0.6 * ux * ux)
col = mix(col, vec3(0.520, 0.680, 0.880), rimt * 0.6)
let inside = step(lo.x, p.x) * (1.0 - step(hi.x, p.x)) * step(lo.y, p.y) * (1.0 - step(hi.y, p.y))
// The strip's own shadow lies under the chosen cap as under the others:
// the strip is one slab standing on the ground.
var res = vec4(col, 1.0) * inside
// The neighbours' ends standing over the well: a lit column on each,
// and a fainter one behind it on the left.
let ey = step(lo.y + px, p.y) * (1.0 - step(hi.y - px, p.y))
let el = step(lo.x - px, p.x) * (1.0 - step(lo.x, p.x))
let el2 = step(lo.x - px * 2.0, p.x) * (1.0 - step(lo.x - px, p.x))
let er = step(hi.x, p.x) * (1.0 - step(hi.x + px, p.x))
res = Finish.over(res, vec4(0.40, 0.43, 0.47, 1.0) * (el + er) * ey * hz)
res = Finish.over(res, vec4(0.30, 0.33, 0.37, 1.0) * el2 * ey * 0.6 * hz)
// On the column the answer below stands over the well's foot: its top
// line, between the side outlines.
let eb = step(hi.y, p.y) * (1.0 - step(hi.y + px, p.y)) * step(lo.x + px, p.x) * (1.0 - step(hi.x - px, p.x))
res = Finish.over(res, vec4(0.40, 0.43, 0.47, 1.0) * eb * v)
// The lamp in the well's bottom rim, centred under the word (the widget
// sets the ink about a point left of the middle), lighting the glass
// round it and, on the row, spilling onto the ground under the strip.
let cx = (lo.x + hi.x) * 0.5 - 1.0
let hw = min(6.0, (hi.x - lo.x) * 0.3)
let below = step(hi.y, p.y) * (1.0 - inside) * hz * step(lo.x - 4.0, p.x) * step(p.x, hi.x + 4.0)
return an_rim_lamp(res, p, cx, hw, hi.y - px, px, 1.0, 1.0, vec3(0.960, 0.985, 1.0), vec3(0.330, 0.600, 0.980), inside, below)
}
// Cap labels at the family's 8.5 points, the chosen one included.
mod.widgets.SegmentedControlFlat.draw_text.text_style.font_size = 8.5
mod.widgets.SegmentedControlFlat.draw_text_selected.text_style.font_size = 8.5
mod.widgets.SegmentedControlFlat.draw_text.color = #c3c9d1
mod.widgets.SegmentedControlFlat.draw_text_selected.color = #fafafa
mod.widgets.SegmentedControlVertical.draw_pill.radius = -1.0
// Tabs: a row of round caps standing side by side. Each quiet tab is a raised cap; the tab in
// use is pushed in and lit from within like the chosen segment, its word
// lit and the lamp in its rim.
mod.widgets.Tabs.height = 28.0
mod.widgets.Tabs.clip_x = false
mod.widgets.Tabs.clip_y = false
let an_tab = fn(pos: vec2, size: vec2, o: vec2, dpi: float, hover: float, active: float) -> vec4 {
let m = 13.0
let px = an_px(dpi)
let p = pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let lo = vec2(an_snap(0.0, o.x, px), an_snap(0.0, o.y, px))
let hi = vec2(an_snap(size.x, o.x, px), an_snap(size.y, o.y, px))
// Each tab its own round cap filling its box, so two tabs share one
// outline along
// their sides and the ground shows only in the notches between their
// corners.
let clo = lo
let chi = hi + vec2(px, 0.0)
let c = (clo + chi) * 0.5
let d = Material.sd_box(p, c, (chi - clo) * 0.5, 5.0)
var res = an_cap(p, clo, chi, 5.0, px, hover, active, 0.0, 0.0, 1.35, vec3(0.0, 0.0, 0.0), vec4(0.270, 0.298, 0.330, 1.0), 1.0)
let well = an_sunk(p, clo + vec2(px, px), chi - vec2(px, px), px, hover) * Finish.cover(d + px, px)
res = mix(res, Finish.over(res, well), active)
// The tab in use carries the lamp in its bottom rim, spilling onto the
// ground under it. The strip sets its words flush left 10 points in and
// the face cannot know how long a word is, so the lamp is centred on the
// first 27 points of it -- under a one-word name such as Mixer, Effects
// or Notes it stands in the middle; under a longer word it stays under
// its first letters.
let lx = lo.x + 23.5
let inside = Finish.cover(d, px)
let below = (1.0 - inside) * step(chi.y - px, p.y) * step(clo.x - 4.0, p.x) * step(p.x, chi.x + 4.0)
return an_rim_lamp(res, p, lx, 6.0, chi.y - px, px, active, 1.0, vec3(0.960, 0.985, 1.0), vec3(0.330, 0.600, 0.980), inside, below)
}
mod.widgets.Tabs.draw_tab.vertex = fn() {
let m = 13.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 = 13.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 = 13.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 an_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 an_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 an_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 = #c3c9d1
mod.widgets.Tabs.draw_text.color = #c3c9d1
// The tab in use answers like the chosen segment: its ink #d4dae2 in
// semibold, in the sunk blue well; quiet tabs grey regular.
mod.widgets.Tabs.draw_text_active.color = #d4dae2
mod.widgets.Tabs.draw_text.text_style.font_size = 8.5
mod.widgets.Tabs.draw_text_active.text_style = mod.theme.font_bold{font_size: 8.5}
// ---------------------------------------------------------------- feedback
// The progress bar: an 8 point well; the value is the lit bar of the
// sliders, a pale core row over a body row in the middle of the floor, its
// halo on the floor round it.
mod.widgets.ProgressBarFlat.height = 8.0
mod.widgets.ProgressBarFlat.draw_bg.pixel = fn() {
let px = an_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 = an_snap((self.rect_size.y - th) * 0.5, o.y, px)
let x0 = an_snap(self.rect_size.x * self.start, o.x, px)
let x1 = an_snap(self.rect_size.x * self.end, o.x, px)
let lo = vec2(x0, y0)
let hi = vec2(x1, y0 + an_snap(th, 0.0, px) - px)
var res = vec4(0.0, 0.0, 0.0, 0.0)
if self.track > 0.5 {
res = an_well(p, lo, hi, 2.0, px, vec3(0.078, 0.094, 0.118), 0.0, 0.0, 1.0)
}
let fill = self.fill_color()
let ins = 2.5
let fl = vec2(lo.x + ins, lo.y + ins)
let span = hi.x - lo.x - ins * 2.0
var f0 = fl.x
var f1 = fl.x + span * clamp((self.value - self.start) / max(self.end - self.start, 0.001), 0.0, 1.0)
if self.value < 0.0 {
// Still: the sweep would need the pass clock, and a window at rest
// reads no clock, so the unknown is a segment standing in the
// middle with its ends fading.
f0 = fl.x + span * 0.3
f1 = fl.x + span * 0.7
}
// The fill is lit like the lamp: a pale core row over a row of the
// role colour in the middle of the well's floor, the halo on the floor
// round it and past its ends.
var fade = fill.a * step(0.001, f1 - f0)
if self.value < 0.0 {
fade = fade * smoothstep(f0, f0 + span * 0.12, p.x) * smoothstep(f1, f1 - span * 0.12, p.x)
}
// A thin lit tube, four device pixels, in the middle of the floor.
let fy = an_snap((lo.y + hi.y) * 0.5, 0.0, px) - px * 2.0
let lb = an_lit_bar(vec4(0.0, 0.0, 0.0, 0.0), p, vec2(f0, fy), vec2(f1, fy + px * 4.0), vec2(lo.x + px, hi.x - px), px, fill.rgb, 0.0)
res = Finish.over(res, lb * fade)
return res * self.opacity
}
mod.widgets.ProgressBarFlat.draw_bg.color_primary = #4db0ff
// The level meter: cells of 3 dp and a 1 dp black gap on black glass in a
// slim well; green, then yellow toward the top, red at the end; unlit cells
// stand at a fifth of the lit. The clip lamp is an LED. 8 points thick.
mod.widgets.LevelMeter.height = 8.0
mod.widgets.LevelMeterColumn.width = 8.0
mod.widgets.LevelMeter.draw_bg.pixel = fn() {
let px = an_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(p.y, p.x, v)
let lamp_room = self.lamp * (across + 3.0)
let bar = max(4.0, along - lamp_room)
// The window: black glass in a 1 dp outline with its lip.
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(an_snap(wlo.x, o.x, px), an_snap(wlo.y, o.y, px))
let hi = vec2(an_snap(whi.x, o.x, px), an_snap(whi.y, o.y, px) - px)
var res = an_well(p, lo, hi, 1.0, px, vec3(0.03, 0.035, 0.045), 0.0, 0.0, 1.0)
let pad = 2.0 * px
let span = max(1.0, bar - pad * 2.0)
let pitch = px * 4.0
let cell = step(fract((a - pad) / pitch), 0.75)
let share = clamp((a - pad) / span, 0.0, 1.0)
let inb = step(pad, a) * step(a, bar - pad) * step(pad, b) * step(b, across - pad) * step(p.y, hi.y - px)
var lit_col = vec3(0.560, 0.860, 0.220)
lit_col = mix(lit_col, vec3(0.902, 0.788, 0.251), step(self.hot_at, share))
lit_col = mix(lit_col, vec3(0.941, 0.318, 0.227), step(self.hot_to, share))
// The first device pixel of each cell a touch lighter and paler.
let edge = step(fract((a - pad) / pitch), 0.25)
lit_col = mix(lit_col, mix(lit_col, vec3(0.85, 0.87, 0.8), 0.35), edge)
let on = step(share, self.level) * step(0.001, self.level)
let unlit = vec3(0.145, 0.153, 0.176)
let col = mix(unlit, lit_col, on)
res = Finish.over(res, vec4(col, 1.0) * cell * inb * self.opacity)
// The hold: one cell at full strength.
let hold_at = pad + span * self.hold
let hc = step(abs(a - hold_at), pitch * 0.5) * step(0.002, self.hold)
res = Finish.over(res, vec4(mix(vec3(0.560, 0.860, 0.220), vec3(0.941, 0.318, 0.227), step(self.hot_to, self.hold)), 1.0) * hc * cell * inb)
// The lamp.
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)
// A small red lamp, 3.5 points: a white-hot core, a red rim and a
// soft red halo when lit; a dark lens when out.
let rl = clamp(across * 0.5 - 1.5, 1.0, 1.75)
res = an_led(res, p, lc, vec2(rl, rl), step(0.5, self.over), 0.45, px, vec3(1.0, 0.957, 0.941), vec3(0.941, 0.318, 0.227))
}
return res * self.opacity
}
// The spinner: a small turned-metal disc in a dark bezel whose ring is lit
// three quarters of the way round, as the pictures draw it. A sheet may not
// read the pass clock and the spinner carries no other, so it stands still.
mod.widgets.SpinnerFlat.size = 20.0
mod.widgets.SpinnerFlat.draw_bg.pixel = fn() {
let px = an_px(self.draw_pass.dpi_factor)
let p = self.pos * self.rect_size
let o = self.rect_pos
let c = vec2(Finish.snap(o.x + self.rect_size.x * 0.5, px), Finish.snap(o.y + self.rect_size.y * 0.5, px)) - o
let size = min(self.rect_size.x, self.rect_size.y)
let ro = size * 0.5 - 0.5
let rw = 2.2
let rm = ro - rw * 0.5
let rl = length(p - c)
let dr = abs(rl - rm) - rw * 0.5
var res = vec4(0.035, 0.043, 0.055, 1.0) * Finish.cover(dr, px)
let q = p - c
var ang = atan2(-q.x, q.y)
if ang < 0.0 {
ang = ang + 2.0 * PI
}
let ac = clamp(ang, 0.8, 0.8 + 1.5 * PI)
let ap = c + vec2(-sin(ac), cos(ac)) * rm
let da = length(p - ap)
let xa = max(da - 0.7, 0.0)
let g = (0.4 * exp(-(xa * xa) / (2.0 * 0.6 * 0.6)) + 0.18 * exp(-(xa * xa) / (2.0 * 1.4 * 1.4))) * (1.0 - Finish.cover(da - 0.7, px))
res = Finish.over(res, vec4(0.290, 0.560, 0.930, 1.0) * g)
let acol = mix(vec3(0.86, 0.94, 1.0), vec3(0.32, 0.66, 1.0), smoothstep(0.1, 0.7, da))
res = Finish.over(res, vec4(acol, 1.0) * Finish.cover(da - 0.7, px))
res = Finish.over(res, an_metal(p, c, ro - rw - 0.8, px, 0.0, 0.0, 1.0))
return res * self.alpha
}
// ---------------------------------------------------------------- surfaces
// The scroll bar: an 8 point well, the thumb a 6 point cap inset 1 point,
// no gloss, no glow.
mod.widgets.ScrollBar.bar_size = 8.0
mod.widgets.ScrollBar.bar_side_margin = 1.0
mod.widgets.ScrollBar.draw_bg.pixel = fn() {
let px = an_px(self.draw_pass.dpi_factor)
let o = self.rect_pos
let sz = self.rect_size
let p = self.pos * sz
let v = self.is_vertical
let lo = vec2(an_snap(0.0, o.x, px), an_snap(0.0, o.y, px))
let hi = vec2(an_snap(sz.x, o.x, px), an_snap(sz.y, o.y, px))
var res = an_well(p, lo, hi, 2.0, px, vec3(0.078, 0.094, 0.118), 0.0, 0.0, 0.0)
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 tlo = mix(vec2(t0, 1.0), vec2(1.0, t0), v)
let thi = mix(vec2(t1, sz.y - 1.0), vec2(sz.x - 1.0, t1), v)
let slo = vec2(an_snap(tlo.x, o.x, px), an_snap(tlo.y, o.y, px))
let shi = vec2(an_snap(thi.x, o.x, px), an_snap(thi.y, o.y, px))
res = Finish.over(res, an_cap(p, slo, shi, 3.0, px, max(self.hover, self.drag), 0.0, 0.0, 0.0, 0.0, vec3(0.0, 0.0, 0.0), vec4(0.270, 0.298, 0.330, 1.0), 1.0))
return res * self.opacity
}
// A raised panel: a brushed-metal plate. A firm drop under it that stays
// within the gap to its neighbours, the black outline, a face lit from the
// top (#434b55 falling to #2d333b), a gleam over its upper part, a light
// line along the top edge and a quieter one on the foot.
let an_panel = fn(p: vec2, size: vec2, o: vec2, px: float, r: float, hover: float) -> vec4 {
let lo = vec2(an_snap(0.0, o.x, px), an_snap(0.0, o.y, px))
let hi = vec2(an_snap(size.x, o.x, px), an_snap(size.y, o.y, px))
let c = (lo + hi) * 0.5
let h = (hi - lo) * 0.5
let d = Material.sd_box(p, c, h, r)
// A thick plate standing off the ground: a dense shadow right under the
// foot and a soft one carrying on about 10 points down (luminance 0.004
// under the outline, back to the ground by 12 points, as measured); to
// the sides both are gone within 6 points, half the gap.
let sh1 = Finish.drop(p, c, h - vec2(1.0, 0.0), r, vec2(0.0, 1.5), 1.3) * 0.88
let sh2 = Finish.drop(p, c, h - vec2(5.0, 2.0), r, vec2(0.0, 4.5), 4.0) * 0.62
let sh = 1.0 - (1.0 - sh1) * (1.0 - sh2)
let contact = Finish.drop(p, c, h, r, vec2(0.0, px), px * 0.6) * 0.9
var res = vec4(0.0, 0.0, 0.0, 1.0 - (1.0 - sh) * (1.0 - contact))
// The lit top edge breathes a little light on the ground over it.
let above = (1.0 - Finish.cover(d, px)) * step(p.y, lo.y) * smoothstep(0.0, 6.0, min(p.x - lo.x, hi.x - p.x))
res = Finish.over(res, vec4(0.560, 0.600, 0.650, 1.0) * exp(-max(lo.y - p.y, 0.0) / 1.6) * 0.20 * above)
let t = clamp((p.y - lo.y) / max(hi.y - lo.y, 1.0), 0.0, 1.0)
let top = p.y - lo.y
let u = (p.x - lo.x) / max(hi.x - lo.x, 1.0)
// The plate: #67707c under the top edge (0.44), 0.36 thirteen points
// down, #333941 over the middle and #2b3036 at the foot, a little darker
// toward its left and right ends.
var col = mix(vec3(0.225, 0.250, 0.285), vec3(0.160, 0.180, 0.205), smoothstep(0.0, 1.0, t)) + vec3(0.02, 0.02, 0.022) * hover
col = col + vec3(0.150, 0.158, 0.172) * exp(-max(top, 0.0) / 9.0)
col = col * (1.0 - 0.10 * (2.0 * u - 1.0) * (2.0 * u - 1.0))
// The sheen: a broad diagonal sweep of light from the top edge down to
// the lower left, strongest near the top, and a hot patch where it meets
// the edge.
let sweep = exp(-((u - 0.55 * (1.0 - t) - 0.1) * (u - 0.55 * (1.0 - t) - 0.1)) / 0.04) * (1.0 - 0.7 * t)
col = col + vec3(0.070, 0.074, 0.080) * sweep
let tx = an_plate_tex(p, px) * 0.9
col = col + vec3(tx, tx, tx * 1.05)
// The bevel: a near-white top edge two device pixels deep (0.93, then
// 0.57), the corners' arcs fading into the sides, a light line down
// each side, and a lit lip on the foot (0.37 over 0.29).
let r1 = Finish.band(d, px, px * 2.0, px)
let r2 = Finish.band(d, px * 2.0, px * 3.0, px)
let r3 = Finish.band(d, px * 3.0, px * 4.0, px)
let ty = 1.0 - smoothstep(0.0, r + px * 3.0, top)
let hot = mix(vec3(0.800, 0.830, 0.860), vec3(0.940, 0.955, 0.975), 0.5 + 0.5 * exp(-((u - 0.55) * (u - 0.55)) / 0.04))
col = mix(col, hot, r1 * ty)
col = mix(col, vec3(0.520, 0.560, 0.610), r2 * ty)
col = mix(col, vec3(0.450, 0.485, 0.530), r3 * ty * 0.5)
let sidel = (r1 + 0.4 * r2) * (1.0 - ty) * step(p.y, hi.y - r - px * 2.0)
col = mix(col, mix(vec3(0.380, 0.410, 0.450), vec3(0.250, 0.272, 0.300), t), sidel * 0.85)
let foot = step(hi.y - r - px * 2.0, p.y)
col = mix(col, vec3(0.345, 0.372, 0.408), r1 * foot)
col = mix(col, vec3(0.270, 0.292, 0.318), r2 * foot)
col = mix(col, vec3(0.012, 0.014, 0.020), Finish.band(d, 0.0, px, px))
let a = Finish.cover(d, px)
res = Finish.over(res, vec4(col * a, a))
return res
}
// A tray: the same box sunk into the ground, framed like a picture. A
// bright bezel hairline round it (0.72 on the top, 0.52 down the sides,
// near white on the foot), the black outline, a flat frame 3.5 points wide
// all round with a light step on its inner edge, then the floor: dark under
// the frame's top (0.07) and rising toward the foot (0.19), shaded along its
// walls.
let an_tray = fn(p: vec2, size: vec2, o: vec2, px: float, r: float) -> vec4 {
let lo = vec2(an_snap(0.0, o.x, px), an_snap(0.0, o.y, px))
let hi = vec2(an_snap(size.x, o.x, px), an_snap(size.y, o.y, px) - px)
let c = (lo + hi) * 0.5
let h = (hi - lo) * 0.5
let d = Material.sd_box(p, c, h, r)
let t = clamp((p.y - lo.y) / max(hi.y - lo.y, 1.0), 0.0, 1.0)
let bz = Finish.ring_out(d, 0.0, px, px)
var bc = mix(vec3(0.660, 0.700, 0.750), vec3(0.460, 0.500, 0.550), smoothstep(0.0, 0.2, t))
bc = mix(bc, vec3(0.880, 0.895, 0.915), step(hi.y - r * 0.5, p.y))
var res = vec4(bc, 1.0) * bz
// A faint breath of that light on the ground under the foot.
let below = step(hi.y + px, p.y) * (1.0 - Finish.cover(d, px)) * smoothstep(0.0, 6.0, min(p.x - lo.x, hi.x - p.x))
res = Finish.over(res, vec4(0.500, 0.540, 0.590, 1.0) * exp(-max(p.y - hi.y - px, 0.0) / 1.4) * 0.16 * below)
// The frame.
let fw = an_snap(3.5, 0.0, px)
let flo = lo + vec2(fw, fw)
let fhi = hi - vec2(fw, fw)
let fd = Material.sd_box(p, (flo + fhi) * 0.5, (fhi - flo) * 0.5, max(r - 2.0, 1.0))
let tx = an_plate_tex(p, px) * 0.6
var col = mix(vec3(0.150, 0.170, 0.198), vec3(0.120, 0.137, 0.160), t) + vec3(tx, tx, tx * 1.05)
// Its inner edge: a light step, brighter on the foot, and the black
// line where the floor drops away.
let fr = Finish.ring_out(fd, 0.0, px, px)
let ft = clamp((p.y - flo.y) / max(fhi.y - flo.y, 1.0), 0.0, 1.0)
col = mix(col, mix(vec3(0.215, 0.240, 0.272), vec3(0.300, 0.328, 0.365), step(0.9, ft)), fr)
// The floor.
let top = p.y - flo.y
var fl = mix(vec3(0.080, 0.094, 0.112), vec3(0.165, 0.188, 0.218), smoothstep(0.0, 0.95, ft)) + vec3(tx, tx, tx * 1.05)
let wall = 0.35 * (exp(-max(p.x - flo.x - px, 0.0) / 2.0) + exp(-max(fhi.x - px - p.x, 0.0) / 2.0))
let sh = 0.5 * exp(-max(top - px, 0.0) / 2.5)
fl = mix(fl, vec3(0.0, 0.0, 0.0), clamp(wall + sh, 0.0, 0.8))
fl = mix(fl, vec3(0.010, 0.012, 0.018), Finish.band(fd, 0.0, px, px))
col = mix(col, fl, Finish.cover(fd, px))
col = mix(col, vec3(0.008, 0.010, 0.016), Finish.band(d, 0.0, px, px))
let a = Finish.cover(d, px)
return Finish.over(res, vec4(col * a, a))
}
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 = 12.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 = 12.0
let px = an_px(self.draw_pass.dpi_factor)
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
if self.sunken > 0.5 {
return an_tray(p, self.rect_size, self.rect_pos, px, 4.0)
}
return an_panel(p, self.rect_size, self.rect_pos, px, 4.0, 0.0)
}
// The rounded view keeps the colour its caller gives it, square to the
// family: a true 2 point corner and, when it is opaque, a 1 dp outline.
mod.widgets.RoundedView.draw_bg.pixel = fn() {
let px = an_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 = min(2.0 * max(1.0, self.border_radius), min(h.x, h.y))
let d = Material.sd_box(p, c, h, 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))
}
var ink = self.border_color
if self.border_size < 0.01 {
ink = vec4(0.039, 0.039, 0.047, fill.a)
}
let a = Finish.cover(d, px)
var col = mix(fill.rgb, ink.rgb, Finish.band(d, 0.0, max(self.border_size, px), px) * ink.a)
return vec4(col * a * fill.a, a * fill.a) + vec4(ink.rgb * ink.a, ink.a) * Finish.band(d, 0.0, max(self.border_size, px), px) * a * (1.0 - fill.a)
}
// The card: a raised panel of its own; a pressable card lifts 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 = 12.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 = 12.0
let px = an_px(self.draw_pass.dpi_factor)
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
return an_panel(p, self.rect_size, self.rect_pos, px, 4.0, self.hover)
}
// List rows: 20 points, the text 10 points in; under the hand the row
// lifts 0.03. The chosen row is a shallow well -- the chosen segment's blue
// floor, a dark line and a short shade under its top edge, a light lip on
// its foot -- with a short lamp standing at its left.
mod.widgets.ListItem.height = 20.0
mod.widgets.ListItemOne.height = 20.0
mod.widgets.ListItem.padding = mod.turtle.Inset{left: 10.0 right: 8.0 top: 0.0 bottom: 0.0}
mod.widgets.ListItemOne.padding = mod.turtle.Inset{left: 10.0 right: 8.0 top: 0.0 bottom: 0.0}
mod.widgets.ListItem.draw_bg.pixel = fn() {
let px = an_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(1.0, 1.0, 1.0, 1.0) * (0.03 * self.hover + 0.02 * self.down) * live
let y0 = an_snap(0.0, o.y, px)
let y1 = an_snap(self.rect_size.y, o.y, px)
let x0 = an_snap(0.0, o.x, px)
let x1 = an_snap(self.rect_size.x, o.x, px)
let t = clamp((p.y - y0) / max(y1 - y0, 1.0), 0.0, 1.0)
let u = clamp((p.x - x0) / max(x1 - x0, 1.0), 0.0, 1.0)
// The chosen row is a bar of lit blue glass (#3e536a, luminance 0.3),
// brightest a little right of its middle and dimmer toward the lamp's
// end, a pale rim along its top (#819bb1) and a lit one on its foot
// (#6c8da9), the black outline round it.
let cb = vec2((x0 + x1) * 0.5, (y0 + y1) * 0.5)
let d = Material.sd_box(p, cb, vec2((x1 - x0) * 0.5, (y1 - y0) * 0.5), 2.5)
let bu = (u - 0.62) / 0.3
var col = mix(vec3(0.225, 0.300, 0.392), vec3(0.195, 0.262, 0.345), t)
col = col * (0.72 + 0.28 * smoothstep(0.0, 0.35, u)) + vec3(0.040, 0.055, 0.070) * exp(-bu * bu)
col = col + vec3(0.290, 0.560, 0.930) * 0.10 * exp(-max(p.x - x0, 0.0) / 14.0)
col = mix(col, vec3(0.506, 0.608, 0.694), Finish.band(d, px, px * 2.0, px) * (1.0 - step(cb.y, p.y)))
col = mix(col, vec3(0.424, 0.553, 0.663), Finish.band(d, px, px * 2.0, px) * step(cb.y, p.y))
col = mix(col, vec3(0.030, 0.035, 0.050), Finish.band(d, 0.0, px, px))
var sel = vec4(col, 1.0) * Finish.cover(d, px)
let lc = vec2(an_snap(3.5, o.x, px) - px * 0.5, (y0 + y1) * 0.5)
sel = an_led(sel, p, lc, vec2(px * 0.5 + 0.01, max((y1 - y0) * 0.22, 2.0)), live, 0.8, px, vec3(0.88, 0.96, 1.0), vec3(0.290, 0.560, 0.930))
res = mix(res, Finish.over(sel, res), self.selected)
return res
}
mod.widgets.ListItem.draw_text.color = #d4dae2
mod.widgets.ListItem.draw_text_2.color = #838b96
// A chip is a small seated cap; its role colour only tints the face. Its
// label is a cap label: 8.5 points.
mod.widgets.ChipFlat.draw_text.text_style.font_size = 8.5
mod.widgets.ChipFlat.draw_bg.pixel = fn() {
let px = an_px(self.draw_pass.dpi_factor)
let o = self.rect_pos
let p = self.pos * self.rect_size
let lo = vec2(an_snap(0.0, o.x, px), an_snap(0.0, o.y, px))
let hi = vec2(an_snap(self.rect_size.x, o.x, px), an_snap(self.rect_size.y, o.y, px) - px)
let l = Finish.lum(self.color.rgb)
let tint = (self.color.rgb - vec3(l, l, l)) * 0.35 * self.color.a
var res = an_cap(p, lo, hi, 2.0, px, self.hover, self.down, 0.0, 0.0, 0.0, tint, vec4(0.270, 0.298, 0.330, 1.0), 0.0)
if self.keyed > 0.0 {
let d = Material.sd_box(p, (lo + hi) * 0.5, (hi - lo) * 0.5, 2.0)
res = Finish.over(res, vec4(0.290, 0.560, 0.930, 1.0) * Finish.band(d, 0.0, px, px) * self.keyed)
}
return res
}
// ---------------------------------------------------------------- ink
// Words sit embossed-dark: a 1 dp shadow of black at half strength directly
// under every glyph of a label. Read from the glyph one device pixel above,
// so it costs one more sample and nothing else.
// On a cap the label also sinks with it: held, the glyphs are read one
// device pixel higher in the atlas, so they stand one pixel lower in the
// well. Small labels come from the grayscale atlas, large ones from the
// multi-channel one; both paths carry the shadow and the nudge.
mod.widgets.ButtonFlat.draw_text.pixel = fn() {
let nudge = dFdy(self.t.xy) * step(0.5, self.down)
let c = self.get_color()
let dxt = length(dFdx(self.t))
let dyt = length(dFdy(self.t))
if self.texture_index < 0.5 {
let tex_size = self.grayscale_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
}
if self.texture_index > 1.5 && self.texture_index < 2.5 {
let tex_size = self.msdf_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
}
return self.sample_text_pixel()
}
// A template made from another before this sheet ran keeps its own copy
// of the label's draw, so each cap template takes the literal.
mod.widgets.Button.draw_text.pixel = fn() {
let nudge = dFdy(self.t.xy) * step(0.5, self.down)
let c = self.get_color()
let dxt = length(dFdx(self.t))
let dyt = length(dFdy(self.t))
if self.texture_index < 0.5 {
let tex_size = self.grayscale_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
}
if self.texture_index > 1.5 && self.texture_index < 2.5 {
let tex_size = self.msdf_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
}
return self.sample_text_pixel()
}
mod.widgets.ButtonPrimary.draw_text.pixel = fn() {
let nudge = dFdy(self.t.xy) * step(0.5, self.down)
let c = self.get_color()
let dxt = length(dFdx(self.t))
let dyt = length(dFdy(self.t))
if self.texture_index < 0.5 {
let tex_size = self.grayscale_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
}
if self.texture_index > 1.5 && self.texture_index < 2.5 {
let tex_size = self.msdf_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
}
return self.sample_text_pixel()
}
mod.widgets.ButtonSecondary.draw_text.pixel = fn() {
let nudge = dFdy(self.t.xy) * step(0.5, self.down)
let c = self.get_color()
let dxt = length(dFdx(self.t))
let dyt = length(dFdy(self.t))
if self.texture_index < 0.5 {
let tex_size = self.grayscale_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
}
if self.texture_index > 1.5 && self.texture_index < 2.5 {
let tex_size = self.msdf_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
}
return self.sample_text_pixel()
}
mod.widgets.ButtonTertiary.draw_text.pixel = fn() {
let nudge = dFdy(self.t.xy) * step(0.5, self.down)
let c = self.get_color()
let dxt = length(dFdx(self.t))
let dyt = length(dFdy(self.t))
if self.texture_index < 0.5 {
let tex_size = self.grayscale_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
}
if self.texture_index > 1.5 && self.texture_index < 2.5 {
let tex_size = self.msdf_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
}
return self.sample_text_pixel()
}
mod.widgets.ButtonDanger.draw_text.pixel = fn() {
let nudge = dFdy(self.t.xy) * step(0.5, self.down)
let c = self.get_color()
let dxt = length(dFdx(self.t))
let dyt = length(dFdy(self.t))
if self.texture_index < 0.5 {
let tex_size = self.grayscale_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
}
if self.texture_index > 1.5 && self.texture_index < 2.5 {
let tex_size = self.msdf_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
}
return self.sample_text_pixel()
}
mod.widgets.ButtonOutline.draw_text.pixel = fn() {
let nudge = dFdy(self.t.xy) * step(0.5, self.down)
let c = self.get_color()
let dxt = length(dFdx(self.t))
let dyt = length(dFdy(self.t))
if self.texture_index < 0.5 {
let tex_size = self.grayscale_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
}
if self.texture_index > 1.5 && self.texture_index < 2.5 {
let tex_size = self.msdf_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
}
return self.sample_text_pixel()
}
mod.widgets.ButtonFlatter.draw_text.pixel = fn() {
let nudge = dFdy(self.t.xy) * step(0.5, self.down)
let c = self.get_color()
let dxt = length(dFdx(self.t))
let dyt = length(dFdy(self.t))
if self.texture_index < 0.5 {
let tex_size = self.grayscale_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
}
if self.texture_index > 1.5 && self.texture_index < 2.5 {
let tex_size = self.msdf_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
}
return self.sample_text_pixel()
}
mod.widgets.ButtonXs.draw_text.pixel = fn() {
let nudge = dFdy(self.t.xy) * step(0.5, self.down)
let c = self.get_color()
let dxt = length(dFdx(self.t))
let dyt = length(dFdy(self.t))
if self.texture_index < 0.5 {
let tex_size = self.grayscale_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
}
if self.texture_index > 1.5 && self.texture_index < 2.5 {
let tex_size = self.msdf_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
}
return self.sample_text_pixel()
}
mod.widgets.ButtonSm.draw_text.pixel = fn() {
let nudge = dFdy(self.t.xy) * step(0.5, self.down)
let c = self.get_color()
let dxt = length(dFdx(self.t))
let dyt = length(dFdy(self.t))
if self.texture_index < 0.5 {
let tex_size = self.grayscale_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
}
if self.texture_index > 1.5 && self.texture_index < 2.5 {
let tex_size = self.msdf_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
}
return self.sample_text_pixel()
}
mod.widgets.ButtonLg.draw_text.pixel = fn() {
let nudge = dFdy(self.t.xy) * step(0.5, self.down)
let c = self.get_color()
let dxt = length(dFdx(self.t))
let dyt = length(dFdy(self.t))
if self.texture_index < 0.5 {
let tex_size = self.grayscale_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
}
if self.texture_index > 1.5 && self.texture_index < 2.5 {
let tex_size = self.msdf_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
}
return self.sample_text_pixel()
}
mod.widgets.ButtonXl.draw_text.pixel = fn() {
let nudge = dFdy(self.t.xy) * step(0.5, self.down)
let c = self.get_color()
let dxt = length(dFdx(self.t))
let dyt = length(dFdy(self.t))
if self.texture_index < 0.5 {
let tex_size = self.grayscale_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
}
if self.texture_index > 1.5 && self.texture_index < 2.5 {
let tex_size = self.msdf_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
}
return self.sample_text_pixel()
}
// The same shadow on every label that stands on the ground or a cap; one
// literal per template, as the handbook advises. A lit legend (the tab or
// segment in use) takes a 2 dp halo of the LED's glow instead.
mod.widgets.Label.draw_text.pixel = fn() {
let main = self.sample_text_pixel()
if self.texture_index > 1.5 && self.texture_index < 2.5 {
let dxt = length(dFdx(self.t))
let dyt = length(dFdy(self.t))
let tex_size = self.msdf_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let ps = clamp(self.t.xy - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let c = self.get_color()
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
}
return main
}
mod.widgets.CheckBoxFlat.draw_text.pixel = fn() {
let main = self.sample_text_pixel()
if self.texture_index > 1.5 && self.texture_index < 2.5 {
let dxt = length(dFdx(self.t))
let dyt = length(dFdy(self.t))
let tex_size = self.msdf_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let ps = clamp(self.t.xy - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let c = self.get_color()
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
}
return main
}
mod.widgets.RadioButtonFlat.draw_text.pixel = fn() {
let main = self.sample_text_pixel()
if self.texture_index > 1.5 && self.texture_index < 2.5 {
let dxt = length(dFdx(self.t))
let dyt = length(dFdy(self.t))
let tex_size = self.msdf_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let ps = clamp(self.t.xy - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let c = self.get_color()
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
}
return main
}
mod.widgets.DropDownFlat.draw_text.pixel = fn() {
let main = self.sample_text_pixel()
if self.texture_index > 1.5 && self.texture_index < 2.5 {
let dxt = length(dFdx(self.t))
let dyt = length(dFdy(self.t))
let tex_size = self.msdf_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let ps = clamp(self.t.xy - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let c = self.get_color()
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
}
return main
}
mod.widgets.SegmentedControlFlat.draw_text.pixel = fn() {
let main = self.sample_text_pixel()
if self.texture_index > 1.5 && self.texture_index < 2.5 {
let dxt = length(dFdx(self.t))
let dyt = length(dFdy(self.t))
let tex_size = self.msdf_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let ps = clamp(self.t.xy - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let c = self.get_color()
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
}
return main
}
mod.widgets.SegmentedControlFlat.draw_text_selected.pixel = fn() {
let main = self.sample_text_pixel()
if self.texture_index > 1.5 && self.texture_index < 2.5 {
let dxt = length(dFdx(self.t))
let dyt = length(dFdy(self.t))
let tex_size = self.msdf_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let ex = dFdx(self.t.xy) * 1.5
let ey = dFdy(self.t.xy) * 1.5
let k = vec4(0.0, 0.0, 0.0, 1.0)
var g = self.msdf(scale, clamp(self.t.xy + ex, self.t_min + half_texel, self.t_max - half_texel), k)
g = g + self.msdf(scale, clamp(self.t.xy - ex, self.t_min + half_texel, self.t_max - half_texel), k)
g = g + self.msdf(scale, clamp(self.t.xy + ey, self.t_min + half_texel, self.t_max - half_texel), k)
g = g + self.msdf(scale, clamp(self.t.xy - ey, self.t_min + half_texel, self.t_max - half_texel), k)
let c = self.get_color()
let a = clamp(g * 0.25, 0.0, 1.0) * 0.4 * c.a
return main + vec4(0.22 * a, 0.38 * a, 0.56 * a, a) * (1.0 - main.a)
}
return main
}
mod.widgets.Tabs.draw_text.pixel = fn() {
let main = self.sample_text_pixel()
if self.texture_index > 1.5 && self.texture_index < 2.5 {
let dxt = length(dFdx(self.t))
let dyt = length(dFdy(self.t))
let tex_size = self.msdf_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let ps = clamp(self.t.xy - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let c = self.get_color()
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
}
return main
}
mod.widgets.Tabs.draw_text_active.pixel = fn() {
let main = self.sample_text_pixel()
if self.texture_index > 1.5 && self.texture_index < 2.5 {
let dxt = length(dFdx(self.t))
let dyt = length(dFdy(self.t))
let tex_size = self.msdf_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let ex = dFdx(self.t.xy) * 1.5
let ey = dFdy(self.t.xy) * 1.5
let k = vec4(0.0, 0.0, 0.0, 1.0)
var g = self.msdf(scale, clamp(self.t.xy + ex, self.t_min + half_texel, self.t_max - half_texel), k)
g = g + self.msdf(scale, clamp(self.t.xy - ex, self.t_min + half_texel, self.t_max - half_texel), k)
g = g + self.msdf(scale, clamp(self.t.xy + ey, self.t_min + half_texel, self.t_max - half_texel), k)
g = g + self.msdf(scale, clamp(self.t.xy - ey, self.t_min + half_texel, self.t_max - half_texel), k)
let c = self.get_color()
let a = clamp(g * 0.25, 0.0, 1.0) * 0.4 * c.a
return main + vec4(0.22 * a, 0.38 * a, 0.56 * a, a) * (1.0 - main.a)
}
return main
}
mod.widgets.SliderMinimal.draw_text.pixel = fn() {
let main = self.sample_text_pixel()
if self.texture_index > 1.5 && self.texture_index < 2.5 {
let dxt = length(dFdx(self.t))
let dyt = length(dFdy(self.t))
let tex_size = self.msdf_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let ps = clamp(self.t.xy - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let c = self.get_color()
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
}
return main
}
// ---------------------------------------------------------------- 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 buttons share the one 26 point row.
let field_height = 26.0
let field_pad_v = 4.0
let field_radius = 1.5
// The outline is one device pixel; the faces below draw it themselves.
let field_border = 0.34
// 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 engraved: one device pixel of the outline's black over one of
// light, across the middle of its room, no shadow.
mod.widgets.Hr.draw_bg.pixel = fn() {
let px = an_px(self.draw_pass.dpi_factor)
let p = self.pos * self.rect_size
let y0 = an_snap(self.rect_size.y * 0.5 - px, self.rect_pos.y, px)
let dark = step(y0, p.y) * step(p.y, y0 + px)
let light = step(y0 + px, p.y) * step(p.y, y0 + px * 2.0)
return vec4(0.039, 0.039, 0.047, 1.0) * dark + vec4(0.30, 0.33, 0.37, 1.0) * light
}
mod.widgets.Vr.draw_bg.pixel = fn() {
let px = an_px(self.draw_pass.dpi_factor)
let p = self.pos * self.rect_size
let x0 = an_snap(self.rect_size.x * 0.5 - px, self.rect_pos.x, px)
let dark = step(x0, p.x) * step(p.x, x0 + px)
let light = step(x0 + px, p.x) * step(p.x, x0 + px * 2.0)
return vec4(0.039, 0.039, 0.047, 1.0) * dark + vec4(0.30, 0.33, 0.37, 1.0) * light
}
// ---------------------------------------------------------------- ground
// Plain views stop clipping so a shadow 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 = #262c35
// The window's own ground (`an_ground`): #262c35 brushed across in fine
// streaks, a little light toward the top of the window, and a grain that
// does not repeat.
mod.widgets.Window.show_bg = true
mod.widgets.Window.draw_bg.pixel = fn() {
let px = an_px(self.draw_pass.dpi_factor)
let p = self.pos * self.rect_size
return vec4(an_ground(p + self.rect_pos, px, self.rect_size.x), 1.0)
}
// ---------------------------------------------------------------- badge
// A count is a small seated dark cap, never a light pill: face #2a313b,
// the black outline, the one top line, ink #d4dae2.
mod.widgets.BadgePalette.neutral = #2a313b
mod.widgets.BadgePalette.on_neutral = #d4dae2
mod.widgets.BadgeFlat.shape = mod.widgets.BadgeShape.Rounded
mod.widgets.BadgeFlat.radius = 2.0
mod.widgets.BadgeFlat.draw_bg.pixel = fn() {
let px = an_px(self.draw_pass.dpi_factor)
let o = self.rect_pos
let p = self.pos * self.rect_size
// The box's edges rounded DOWN to a device pixel, so the bottom and
// right outline rows stay inside the quad.
let lo = vec2(an_snap(0.0, o.x, px), an_snap(0.0, o.y, px))
let hi = vec2(an_snap(self.rect_size.x - px * 0.49, o.x, px), an_snap(self.rect_size.y - px * 0.49, o.y, px))
let c = (lo + hi) * 0.5
let h = (hi - lo) * 0.5
let r = min(2.0, min(h.x, h.y))
let d = Material.sd_box(p, c, h, r)
let t = clamp((p.y - lo.y) / max(hi.y - lo.y, 1.0), 0.0, 1.0)
var col = self.color.rgb + vec3(0.012, 0.012, 0.014) * (1.0 - t)
col = mix(col, vec3(0.416, 0.424, 0.455), Finish.band(d, px, px * 2.0, px) * step(p.y, lo.y + r + px * 2.0) * step(lo.x + r * 0.5, p.x) * step(p.x, hi.x - r * 0.5) * self.color.a)
col = mix(col, vec3(0.039, 0.039, 0.047), Finish.band(d, 0.0, px, px))
let fa = max(self.color.a, Finish.band(d, 0.0, px, px))
let a = Finish.cover(d, px) * fa
return vec4(col * a, a)
}
// ---------------------------------------------------------------- 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}}
// ---------------------------------------------------------------- band
// THE BAND is the sheet's own slider with two thumbs: the same track, the
// same lit value and the same thumb as the Slider, the value lit between
// the two thumbs. The widget's grab geometry is set to the thumbs' own, so
// what is drawn is what is dragged.
mod.widgets.RangeSliderFlat.clip_x = false
mod.widgets.RangeSliderFlat.clip_y = false
mod.widgets.RangeSliderFlat.track_inset = 0.0
mod.widgets.RangeSliderFlat.handle_size = 16.0
mod.widgets.RangeSliderFlat.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.RangeSliderFlat.draw_bg.pixel = fn() {
let m = 6.0
let px = an_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 = 8.0
let cy = an_snap(self.label_px + (self.rect_size.y - self.label_px) * 0.5, o.y, px)
let lo = vec2(an_snap(0.0, o.x, px), cy - 3.3333)
let hi = vec2(an_snap(self.rect_size.x, o.x, px), cy + 2.6667)
var res = an_well(p, lo, hi, 2.0, px, vec3(0.078, 0.094, 0.118), 0.0, self.disabled, 1.0)
let span = self.rect_size.x - rad * 2.0
let h0 = rad + self.start_pos * span
let h1 = rad + self.end_pos * span
res = an_fill(res, p, vec2(h0, lo.y + px), vec2(h1, hi.y - px), vec2(lo.x + px, hi.x - px), px, self.disabled)
res = an_handle(res, p, vec2(h0, cy), rad, px, self.hot_start, max(self.drag * self.hot_start, self.focus), self.disabled)
return an_handle(res, p, vec2(h1, cy), rad, px, self.hot_end, max(self.drag * self.hot_end, self.focus), self.disabled)
}
// ---------------------------------------------------------------- switches and meter
// THE SWITCHES are the sheet's own toggle, in proportion with the
// labels and the controls beside them: the rocker and the slide switch
// take the toggle's face, its size and shape, its quad and its label
// gap (the library's own faces for them are flat boxes sized for
// another face). The rocker turns on a click; the slide switch follows a
// drag, as the toggle does.
mod.widgets.ToggleRocker.draw_bg.size = uniform(18.0)
mod.widgets.ToggleSlide.draw_bg.size = uniform(18.0)
mod.widgets.ToggleRocker.draw_bg.pill_aspect = uniform(1.89)
mod.widgets.ToggleSlide.draw_bg.pill_aspect = uniform(1.89)
mod.widgets.ToggleRocker.label_walk.margin.left = 34.0
mod.widgets.ToggleSlide.label_walk.margin.left = 34.0
mod.widgets.ToggleRocker.draw_bg.pixel = mod.widgets.ToggleFlat.draw_bg.pixel
mod.widgets.ToggleSlide.draw_bg.pixel = mod.widgets.ToggleFlat.draw_bg.pixel
// THE METER sits in the same window as the readouts and the screen: the
// library's display housing drawn round its face -- the bezel ring, a shade
// of whatever ground it is set in with a device pixel of dark at its edge;
// the face with the surround's shadow falling across it from the top and a
// device pixel of black where the glass meets the bezel; the glass's quiet
// diagonal band and the light line inside its top edge; the lip of light on
// the ground under it -- with the same corner, so the meter and the
// readouts read as one family. The scale, the red zone, the needle and its
// pivot are the library's own.
mod.widgets.NeedleMeter.draw_bg.border_radius = uniform(theme.corner_radius * 0.5)
mod.widgets.NeedleMeter.draw_bg.color_bezel = uniform(theme.color_d_05)
mod.widgets.NeedleMeter.draw_bg.color_lip = uniform(theme.color_u_15)
mod.widgets.NeedleMeter.draw_bg.pixel = fn() {
let p = self.pos * self.rect_size
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let lo = vec2(Finish.snap(self.rect_pos.x, px), Finish.snap(self.rect_pos.y, px)) - self.rect_pos
let hi = vec2(Finish.snap(self.rect_pos.x + self.rect_size.x, px), Finish.snap(self.rect_pos.y + self.rect_size.y, px) - px) - self.rect_pos
let c = (lo + hi) * 0.5
let ho = (hi - lo) * 0.5
let r_out = min(self.border_radius, min(ho.x, ho.y))
let outer = Material.sd_box(p, c, ho, r_out)
let b = Finish.snap(2.0, px)
let hf = max(ho - vec2(b, b), vec2(0.5, 0.5))
let rf = max(r_out - b, 0.0)
let face_d = Material.sd_box(p, c, hf, rf)
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)
var face = self.color.rgb
// The scale: a hairline arc and a tick at every division.
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, 0.0 - q.y * cos(ak) + q.x * sin(ak))
face = mix(face, self.color_scale.rgb, max(arc, tick))
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)
face = mix(face, self.color_zone.rgb, zone_t * zone_r)
}
// The needle, from under the pivot cover to just past the scale.
let na = self.needle_angle(self.value)
let nd = vec2(sin(na), 0.0 - cos(na))
let along = dot(q, nd)
let across = abs(q.x * nd.y - q.y * nd.x)
let needle = Finish.cover(across - px * 0.5, px) * Finish.cover(along - (rr + 3.0), px) * Finish.cover(0.0 - along - max(self.pivot_size - px, 0.0), px)
face = mix(face, self.color_needle.rgb, needle)
let pd = r - self.pivot_size
face = mix(face, mix(self.color_needle.rgb, self.color.rgb, 0.35), Finish.cover(pd, px))
face = mix(face, face * 0.55, Finish.band(pd, 0.0, px, px))
// The housing: the surround's shadow across the face from the top, the
// black device pixel at the glass's edge, and the glass over it all.
let insh = Finish.inset(p, c, hf, rf, vec2(0.0, 2.0), 1.8)
face = face * (1.0 - 0.55 * insh)
face = mix(face, face * 0.4, Finish.band(face_d, 0.0, px, px))
let u = (p - (c - hf)) / max(hf * 2.0, vec2(1.0, 1.0))
let diag = u.x + u.y * 0.6
let band = smoothstep(0.0, 0.12, diag) * (1.0 - smoothstep(0.28, 0.5, diag))
let top = Finish.cover(abs(p.y - (c.y - hf.y) - px * 1.5) - px * 0.5, px) * 0.6
let ga = clamp(0.03 * (band + top), 0.0, 1.0) * Finish.cover(face_d + px, px)
face = face * (1.0 - ga) + vec3(ga, ga, ga)
let bz = self.color_bezel
var col = vec4(bz.rgb * bz.a, bz.a)
col = Finish.over(col, vec4(0.0, 0.0, 0.0, 0.45 * Finish.band(outer, 0.0, px, px)))
col = mix(col, vec4(face, 1.0), Finish.cover(face_d, px))
col = col * Finish.cover(outer, px)
let lip = Finish.ring_out(outer, 0.0, px, px) * step(hi.y, p.y)
let la = self.color_lip.a * lip
return Finish.over(vec4(self.color_lip.rgb * la, la), col) * self.color.a * self.opacity
}
// ---------------------------------------------------------------- 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 an_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 an_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 an_led_lamp_hue = fn(ink: vec3, vivid: float) -> vec3 {
let f = an_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 an_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 = an_led_lamp_full(ink)
let hue = an_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 = an_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 glossy anthracite ring.
// 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 = an_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.149, 0.173, 0.208), max(room - 0.5, 2.0), vec3(0.22, 0.245, 0.28), 1.0)
return res * self.opacity
}
true