makepad/widgets/sheets/themes/liquid-glass/widgets.splash

4234 lines
240 KiB
Text

use mod.prelude.widgets_internal.*
use mod.sdf.Cap
// THE GLASS. Thick clear glass lying on a plain grey ground. A face is
// nearly the ground's own colour, so a part is drawn by its edges: one
// device pixel of outline, a white hairline inside it along the top, a
// darker line under that, a bright exit line along the bottom where the
// light leaves the slab, and a dark contact line under the bottom edge.
// Under a standing part the ground carries a cool shadow with the light
// that passed through the glass pooled in its middle. Light comes from
// above, a little from the left. Distances are points; every shadow, pool
// and focus ring is drawn OUTSIDE the layout box, in a quad grown in
// `vertex`, so none of it costs padding.
// The ground, #cfd0d4.
let lq_ground = fn() -> vec3 {
return vec3(0.812, 0.816, 0.831)
}
// A clear face in the middle of its height, #cfd2d8.
let lq_clear = fn() -> vec3 {
return vec3(0.812, 0.824, 0.847)
}
// Blue glass in the middle of its height, #94acc8.
let lq_blue = fn() -> vec3 {
return vec3(0.580, 0.675, 0.784)
}
// Red glass, for the one destructive action, at the blue's strength.
let lq_red = fn() -> vec3 {
return vec3(0.800, 0.620, 0.620)
}
// The accent itself, #4f7fd1, for fills and marks.
let lq_accent = fn() -> vec3 {
return vec3(0.310, 0.498, 0.820)
}
// A dither, one value per device pixel in window space, about one level
// of 255: there only to keep the long gradients from banding.
let lq_grain = fn(q: vec2, px: float) -> float {
return Finish.grain(q / px, 2.0 / 255.0)
}
// A box snapped to whole device pixels in window space, as lo.xy, hi.zw in
// the quad's own points, so every hairline lands on one pixel row.
let lq_snap = fn(org: vec2, lo: vec2, hi: vec2, px: float) -> vec4 {
return vec4(Finish.snap(org.x + lo.x, px) - org.x, Finish.snap(org.y + lo.y, px) - org.y, Finish.snap(org.x + hi.x, px) - org.x, Finish.snap(org.y + hi.y, px) - org.y)
}
// Which way the edge of a rounded box nearest p faces.
let lq_norm = fn(p: vec2, c: vec2, h: vec2, r: float) -> vec2 {
let q = p - c
let sx = step(0.0, q.x) * 2.0 - 1.0
let sy = step(0.0, q.y) * 2.0 - 1.0
let e = max(abs(q) - (h - vec2(r, r)), vec2(0.0, 0.0))
let l = length(e)
let side = step(h.x - abs(q.x), h.y - abs(q.y))
let flat = vec2(sx * side, sy * (1.0 - side))
let bent = vec2(sx * e.x, sy * e.y) / max(l, 0.0001)
return mix(flat, bent, step(0.0001, l))
}
// What is left of a drawing when its face is taken away, on his word
// ('remove bg from the cards, meters, screens, readouts etc'): `dark` is
// the drawing over a black face and `light` the same over a white one
// (premultiplied). Their difference is the share of the face; the rest,
// its outline, rim, bevel and shade lines, stays, and the pane's glass
// shows where the face was, under a breath of white (`veil`).
let lq_unfill = fn(dark: vec4, light: vec4, veil: float) -> vec4 {
let k = clamp((light.x + light.y + light.z - dark.x - dark.y - dark.z) / 3.0, 0.0, 1.0)
return vec4(dark.xyz + vec3(veil * k, veil * k, veil * k), max(dark.w - k + veil * k, 0.0))
}
// A body of clear glass: `lq_body` with no grey base, its glass only as
// tinted as `tint` at its share `on` (a chosen tile's blue stays), its rim,
// highlights and shade lines kept; `lift` (the pointer's) brightens the
// breath of white over it, a press dims it.
let lq_glass_body = fn(p: vec2, c: vec2, h: vec2, r: float, px: float, s: float, tint: vec3, on: float, lift: float, span: float, alpha: float, hl: float, sh: float, fo: float, ex: float, rim: vec4, hi: vec3) -> vec4 {
let lo = lq_body(p, c, h, r, px, s, tint * on, span, alpha, hl, sh, fo, ex, rim, hi)
let up = lq_body(p, c, h, r, px, s, mix(vec3(1.0, 1.0, 1.0), tint, on), span, alpha, hl, sh, fo, ex, rim, hi)
return lq_unfill(lo, up, clamp(0.07 + lift * 2.0, 0.0, 0.3))
}
// A distance to a segment, for the few strokes drawn by hand.
let lq_seg = fn(p: vec2, a: vec2, b: vec2) -> float {
let pa = p - a
let ba = b - a
let t = clamp(dot(pa, ba) / max(dot(ba, ba), 0.0001), 0.0, 1.0)
return length(pa - ba * t)
}
// The body of a glass part, inside its edge only, premultiplied. `face` is
// the colour at the middle of the face; `span` scales the fall from a
// lighter top to a darker bottom (1 for clear glass). The face is `alpha`
// opaque, the lines are solid. `hl` scales the light lines, `sh` the shaded
// ones, `fo` is how deep the top hairline's glow falls, `ex` the exit
// line's width in device pixels, `rim` the outline and `hi` the hairline.
let lq_body = fn(p: vec2, c: vec2, h: vec2, r: float, px: float, s: float, face: vec3, span: float, alpha: float, hl: float, sh: float, fo: float, ex: float, rim: vec4, hi: vec3) -> vec4 {
let d = Material.sd_box(p, c, h, r)
let n = lq_norm(p, c, h, r)
let t = -d
let v = clamp((p.y - c.y + h.y) / max(2.0 * h.y, 1.0), 0.0, 1.0)
let g = (mix(0.04, 0.0, smoothstep(0.0, 0.5, v)) + mix(0.0, -0.065, smoothstep(0.5, 1.0, v))) * span
var col = face + vec3(g, g, g)
let lit = dot(n, vec2(-0.259, -0.966))
let up = smoothstep(0.15, 0.85, -n.y)
let low = smoothstep(0.3, 0.9, n.y)
let right = clamp(n.x, 0.0, 1.0)
let left = clamp(-n.x, 0.0, 1.0)
let xf = clamp((p.x - c.x + h.x) / max(2.0 * h.x, 1.0), 0.0, 1.0)
// The refraction band: a cool shade just inside the rim, heaviest on
// the side away from the light.
let rb = (1.0 - smoothstep(0.0, 0.06 * s + px, t)) * 0.13 * mix(0.6, 1.6, clamp(0.5 - 0.5 * lit, 0.0, 1.0)) * sh
col = mix(col, vec3(0.165, 0.192, 0.251), rb * (1.0 - up * 0.7))
// The light gathering inside the foot of the slab before it leaves.
let foot = (1.0 - smoothstep(px, 0.12 * s + px, t)) * low * 0.2 * hl
col = mix(col, vec3(0.945, 0.957, 0.980), foot)
// How the top light runs from left to right: full over the left third,
// seven tenths at the right end.
let run = smoothstep(0.33, 1.0, xf)
// The hairline's falloff into the face: two device pixels under it,
// three over the left third (and a pixel more under the pointer),
// falling from about 0.55 of the way to white to nothing.
let fl = max((fo - px) / px, 1.0) * mix(1.25, 0.85, run)
let fall = (1.0 - clamp((t - 2.0 * px) / (fl * px), 0.0, 1.0)) * step(2.0 * px, t) * up * 0.72 * mix(1.0, 0.8, run) * hl
col = mix(col, vec3(0.965, 0.973, 0.988), fall)
// The darker inner edge: along the top it lies just under the falloff,
// two or three device pixels in from the hairline, and gentle; down the right side,
// where the slab turns away from the light, it sits right inside.
let dk0 = (2.0 + floor(fl + 0.5)) * px
let de_top = Finish.band(d, dk0, dk0 + px, px) * up * 0.36
let de_side = Finish.band(d, 2.0 * px, 3.0 * px, px) * right * 0.6 * (1.0 - low) * (1.0 - up)
col = mix(col, vec3(0.560, 0.584, 0.635), max(de_top, de_side) * sh)
// The exit line: the light leaving the thick glass along the bottom,
// fading out toward the ends of the arc.
let exl = Finish.band(d, px, (1.0 + ex) * px, px) * low * 0.9 * hl
col = mix(col, vec3(0.945, 0.957, 0.980), exl)
// The top hairline, brightest over the left third, running on thinly
// down the lit side.
let hw = max(up * mix(1.0, 0.7, run), left * 0.5 * (1.0 - low))
let hc = mix(hi, vec3(1.0, 1.0, 1.0), (1.0 - run) * 0.8)
col = mix(col, hc, Finish.band(d, px, 2.0 * px, px) * hw * hl)
col = mix(col, rim.rgb, Finish.band(d, 0.0, px, px) * rim.a)
let a = Finish.cover(d, px)
let fa = a * mix(alpha, 1.0, Finish.band(d, 0.0, 3.0 * px, px))
return vec4(col * fa, fa)
}
// What a standing glass part throws on the ground, premultiplied, outside
// its edge only: a broad cool shadow a little right of and below it, a
// tight contact shadow under its bottom edge, and the caustic -- light that
// came through the glass, pooled in the middle of the shadow. `s` is the
// part's height, `k` the shadow's strength, `lift` 1 standing and 0
// pressed flat, `pool` the caustic's colour and `pk` its strength.
let lq_cast = fn(p: vec2, c: vec2, h: vec2, r: float, px: float, s: float, k: float, lift: float, ink: vec3, pool: vec3, pk: float) -> vec4 {
let d = Material.sd_box(p, c, h, r)
let out = 1.0 - Finish.cover(d, px)
// One soft cool shadow: at a 26 point part it lies half a point right
// and three and a half down with a five point blur, so it
// is gone by half the part's height below it. Its box is drawn in a little at the sides,
// so it does not reach a neighbour standing a gap away.
let oy = mix(0.05, 0.135, lift) * s
let sg = max(mix(0.08, 0.19, lift) * s, 0.6)
let hs = max(h - vec2(0.09 * s, 0.0), vec2(0.5, 0.5))
let rs = min(r, min(hs.x, hs.y))
let sh = Finish.drop(p, c + vec2(0.02 * s, 0.0), hs, rs, vec2(0.0, oy), sg) * k * 0.5 * mix(0.6, 1.0, lift)
var acc = vec4(ink * sh, sh)
// The contact line: one device pixel of black under the foot, where
// the glass meets the ground (about -0.19 against the bare ground).
let n = lq_norm(p, c, h, r)
let ct = Finish.ring_out(d, 0.0, px, px) * smoothstep(0.3, 0.9, n.y) * 0.15 * min(k * 5.0, 1.0)
acc = Finish.over(acc, vec4(0.0, 0.0, 0.0, ct))
// The caustic: light that came through the glass, pooled in the middle
// of the shadow, an ellipse 0.45 of the width and 0.3 of the height.
let pc = vec2(c.x, c.y + h.y + 0.16 * s)
let e = (p - pc) / vec2(max(0.45 * h.x, 2.0), max(0.15 * s, 1.0))
let pa = exp(-dot(e, e)) * pk
acc = Finish.over(acc, vec4(pool * pa, pa))
return acc * out
}
// The focus mark: a solid accent ring two device pixels wide, a point and
// a half outside the edge. Not a glow.
let lq_focus = fn(d: float, px: float, f: float) -> vec4 {
let a = Finish.ring_out(d, 1.5, 1.5 + 2.0 * px, px) * 0.55 * f
return vec4(lq_accent() * a, a)
}
// A well cut into the ground: seven hundredths down, a dark line along its
// top inside edge and a soft shade under it, a white lip along its bottom
// outside edge. Premultiplied.
let lq_well = fn(p: vec2, c: vec2, h: vec2, r: float, px: float, col: vec3, k: float) -> vec4 {
let d = Material.sd_box(p, c, h, r)
let n = lq_norm(p, c, h, r)
let top = smoothstep(0.1, 0.8, -n.y)
let low = smoothstep(0.1, 0.8, n.y)
let ins = Finish.inset(p, c, h, r, vec2(0.0, 0.9), 1.1)
// No face: a breath of shade over the glass (and of white where `col`
// is lifted by the pointer), the shade falling in under its top edge
// and the dark line along it.
let veil = clamp((col.y - 0.749) * 2.0, 0.0, 0.2) * k
var acc = vec4(0.0, 0.0, 0.0, 0.07 * k)
acc = Finish.over(acc, vec4(veil, veil, veil, veil))
let sh = ins * 0.28 * k
acc = Finish.over(acc, vec4(vec3(0.165, 0.192, 0.251) * sh, sh))
let ln = Finish.band(d, 0.0, px, px) * top * 0.6 * k
acc = Finish.over(acc, vec4(vec3(0.427, 0.443, 0.478) * ln, ln))
acc = acc * Finish.cover(d, px)
let lip = Finish.ring_out(d, 0.0, px, px) * low * 0.8 * k
return Finish.over(acc, vec4(lip, lip, lip, lip))
}
// A clear lens of radius kr at kc, over what is already drawn in `acc`:
// its shadows, a body that lifts what lies under it, a warm and a cool
// fringe just inside the rim, and the rim itself, bright toward the light.
// `tint` colours the glass.
// `under` is what lies under the lens seen through it: the caller draws its
// track a second time about the lens centre scaled down by 1.12, so the
// track's lines run on through the glass, magnified.
let lq_mag = fn(p: vec2, kc: vec2, m: float) -> vec2 {
return kc + (p - kc) / m
}
let lq_lens = fn(acc: vec4, under: vec4, p: vec2, kc: vec2, r0: float, px: float, k: float, hover: float, down: float, tint: vec4) -> vec4 {
// Held, the lens swells a point, as a drop of glass pressed flat. The
// radius is a whole number of device pixels, so the rim lines land on
// pixel rows where the centre does.
let kr = floor((r0 + down) / px + 0.5) * px
let dd = 2.0 * kr
let kd = length(p - kc) - kr
let a = Finish.cover(kd, px)
let out = 1.0 - a
let en = mix(0.5, 1.0, min(k, 1.0))
let ink = vec3(0.165, 0.192, 0.251)
let cs = Finish.drop(p, kc + vec2(0.02 * dd, 0.0), vec2(kr, kr) * 0.9, kr * 0.9, vec2(0.0, mix(0.10, 0.03, down) * dd), max(mix(0.12, 0.06, down) * dd, 0.6)) * mix(0.2, 0.12, down) * (1.0 + 0.3 * hover) * k * out
var o = Finish.over(acc, vec4(ink * cs, cs))
let ct = Finish.ring_out(kd, 0.0, px, px) * smoothstep(0.2, 0.8, (p.y - kc.y) / kr) * 0.2 * k
o = Finish.over(o, vec4(0.0, 0.0, 0.0, ct))
// Inside the rim the eye sees what lies under the glass, magnified.
o = o * out + under * a
let q = p - kc
let n = q / max(length(q), 0.0001)
let v = clamp(q.y / kr * 0.5 + 0.5, 0.0, 1.0)
// The body: clear glass at a third, lighter toward the top, so what is
// under it keeps about half its contrast and lifts a little.
var col = mix(vec3(0.985, 0.990, 1.0), vec3(0.900, 0.910, 0.930), v)
col = col + vec3(0.03, 0.03, 0.03) * hover - vec3(0.08, 0.08, 0.075) * down
col = mix(col, tint.rgb, tint.a)
let ba = a * (0.35 + 0.12 * hover + 0.1 * down) * en
o = Finish.over(o, vec4(col * ba, ba))
// The fringe: two device pixels a tenth of the lens in from the rim,
// warm on the lower left, cool on the upper right, faint.
let fr = Finish.band(kd, 0.1 * dd - px, 0.1 * dd + px, px) * 0.25 * en
let w = dot(n, vec2(-0.707, 0.707))
let fw = fr * max(w, 0.0)
let fc = fr * max(-w, 0.0)
o = Finish.over(o, vec4(vec3(0.769, 0.647, 0.549) * fw, fw))
o = Finish.over(o, vec4(vec3(0.549, 0.659, 0.769) * fc, fc))
// The rim: a bright hairline all round, strongest toward the light, a
// dark pixel just inside the lower right arc, and the outline.
let lit = dot(n, vec2(-0.259, -0.966)) * 0.5 + 0.5
let hla = Finish.band(kd, px, 2.0 * px, px) * mix(0.55, 1.0, lit) * mix(0.9, 1.0, hover) * en
o = Finish.over(o, vec4(vec3(0.975, 0.980, 0.992) * hla, hla))
let dka = Finish.band(kd, 2.0 * px, 3.0 * px, px) * clamp(dot(n, vec2(0.5, 0.866)), 0.0, 1.0) * 0.35 * en
o = Finish.over(o, vec4(ink * dka, dka))
let ola = Finish.band(kd, 0.0, px, px) * 0.85 * en
return Finish.over(o, vec4(vec3(0.604, 0.616, 0.651) * ola, ola))
}
// BUTTONS. `stand` 1 is glass standing off the ground with its shadow and
// caustic, 0 glass lying on it (the pointer lifts it); `frame` draws the
// rim alone; `hollow` shows nothing until the pointer asks. `tint` is the
// glass colour and how much of it. Pressed, the glass settles: the shadow
// draws in and pales, the pool nearly goes, the hairline dims.
let lq_button = fn(pq: vec2, size: vec2, org: vec2, px: float, hover: float, down: float, focus: float, disabled: float, loading: float, anim: float, ink: vec4, tint: vec4, stand: float, frame: float, hollow: float) -> vec4 {
let b = lq_snap(org, vec2(0.0, 0.0), size, px)
let c = (b.xy + b.zw) * 0.5
let h = (b.zw - b.xy) * 0.5
let s = min(size.y, 32.0)
let r = min(h.x, h.y)
let d = Material.sd_box(pq, c, h, r)
let en = 1.0 - disabled
let press = down * en
let hov = hover * en * (1.0 - press)
let up = max(stand, hov * 0.8) * (1.0 - hollow * (1.0 - hov))
let vis = mix(1.0, max(hov, press), hollow)
let dis = mix(1.0, 0.3, disabled)
let shade = mix(vec3(0.165, 0.192, 0.251), vec3(0.204, 0.282, 0.431), tint.a)
let pool = mix(vec3(1.0, 1.0, 1.0), vec3(0.663, 0.769, 0.949), step(0.01, tint.a) * step(0.5, tint.b - tint.r))
let pk = mix(mix(0.36, 0.5, hov), 0.14, press) * mix(1.0, 1.25, tint.a) * up * en
var acc = lq_cast(pq, c, h, r, px, s, 0.46 * up * dis, 1.0 - press, shade, pool, pk)
let lift = 0.03 * hov - 0.04 * press
let alpha = mix(0.88, 0.0, frame * (1.0 - hov * 0.6)) * mix(1.0, 0.55, disabled)
let hl = mix(0.95, 1.0, hov) * mix(1.0, 0.62, press) * mix(1.0, 0.5, disabled)
let fo = mix(0.06, 0.085, hov) * s + px
let hi = mix(vec3(0.965, 0.973, 0.988), vec3(0.957, 0.969, 1.0), tint.a)
let rim = vec4(mix(vec3(0.682, 0.690, 0.714), vec3(0.478, 0.561, 0.690), tint.a * 0.6), mix(0.9, 0.5, disabled))
let body = lq_glass_body(pq, c, h, r, px, s, tint.rgb, tint.a, lift, mix(1.0, 2.2, tint.a), alpha, hl, mix(1.0, 1.5, press), fo, 1.5, rim, hi)
acc = Finish.over(acc, body * vis)
acc = Finish.over(acc, lq_focus(d, px, focus * en))
// The wait spinner is drawn by the face, so a face that replaces the
// stock one draws it again: a 270 degree arc in the label's ink.
let rr = max(min(h.x, h.y) - 5.0, 2.0)
let q = pq - c
let a0 = anim * 2.0 * PI
var ap = atan2(q.x, -q.y) - a0
ap = ap - floor(ap / (2.0 * PI)) * 2.0 * PI
let arc = step(ap, 1.5 * PI) * Finish.cover(abs(length(q) - rr) - 0.75, px) * loading
return Finish.over(acc, vec4(ink.rgb * arc * ink.a, arc * ink.a))
}
// THE GLASS PILL. The buttons are clear glass pills as his references
// draw them on a light ground: a slab with a rounded rim lying a little
// above the ground, lit from above a little from the left.
// Distances are taken at the button's height `s`.
// - The edge: no dark outline; a bright line on the edge's inner pixel,
// strongest where the rim is lit (0.85), with the crescents and the
// shadow under the slab, draws it.
// - The rim band, 0.11 of the height along the sides, swelling to twice
// that in two crescents: the upper left end, where the edge faces the
// light, and the lower right, where the light leaves the slab. The
// ground seen through it is lifted toward white (0.10, 0.65 more in the
// crescents, 0.22 more along the foot, where the light leaves).
// - The face: clear glass, no base of its own (on his word, 'no grey base
// or bg for any glass ui element at all'): the pane shows through it, a
// soft glow along the rim (0.14) and a breath of white under the pointer;
// it lets the shadow under it through at half.
// - The light: a specular band in the upper crescent running on thinly
// along the top (0.80), a hot glint where the crescent turns (1.0), and a
// softer band in the lower crescent (0.45).
// - Under it: a faint contact shadow (on his word, 'the buttons should
// cast a lot less shadow': a third of what it was, 0.10, its offset and
// spread a third, widening a third as much under the pointer), and the
// light that came through the glass pooled under its lower right (0.12).
// Coloured glass (`tint` past a half) is the colour itself, translucent
// (the blue at 0.82, the red at 0.84: the pane faintly through it, deep
// enough that its white label reads over 4.5:1), a little lighter toward
// its top, with the same rim and light; it is thick, so a
// soft inner shadow in a deeper shade of its colour lies along its top
// inner edge (2.9 points deep at a 26 point button, blurred 2.1) and a
// little round its sides, fading toward the middle, under the rim's light;
// its shadow and pool take the colour, and its label is white. `look`: .x 1 for a body,
// 0 for the rim alone (the outline); .y 1 standing off the ground, 0
// lying on it (the flat button, lifted by the pointer); .z 1 shows
// nothing until the pointer asks (the flatter button). Under the pointer
// the glass lifts: the light on it brightens and the shadow widens.
//
// The press: held, the slab settles toward the ground (the shadow draws
// in, the light on it dims); let go, it lifts back a little past rest. On
// the standing buttons (`timed` 1) this is the lens button's press, timed
// by the animator (below), half again as slow: held, the glass settles
// over 1.17 seconds behind a ring that crosses it in 1.32 and fades over
// 1.6, its light gone gently over the last of that; let go, it springs
// back from as far as it got behind a second ring 0.9 as strong. The lit
// crest is 0.15 of the reach wide.
// `down` is 1 held and falls to 0 over 1.6 seconds when let go (the
// release's clock); `clock` (cap_time) runs 0 to 1 over 1.6 seconds while
// held and falls with `down`, so `clock / down` is how far the press got.
// A face held from the start (a states page) has the clock without
// `down` and is drawn settled with no ring. The ring is lit as a ridge of
// water: its crest and the flank facing the light lifted toward white, the
// far flank shaded a little, its colour split warm ahead and cool behind.
let lq_pill = fn(pq: vec2, size: vec2, org: vec2, px: float, hover: float, down: float, clock: float, timed: float, focus: float, disabled: float, loading: float, anim: float, ink: vec4, tint: vec4, look: vec4) -> vec4 {
let b = lq_snap(org, vec2(0.0, 0.0), size, px)
let c = (b.xy + b.zw) * 0.5
let h = (b.zw - b.xy) * 0.5
let r = min(h.x, h.y)
let s = 2.0 * h.y
let k = min(size.x, size.y) / 44.0
let d = Material.sd_box(pq, c, h, r)
let n = lq_norm(pq, c, h, r)
let t = max(-d, 0.0)
let en = 1.0 - disabled
let hov = hover * en
// The press: timed (held, let go, or held from the start), or plain.
let held = step(0.999, down)
let still = step(down + 0.001, clock) * timed
let going = (1.0 - held) * (1.0 - still) * step(0.0005, down) * timed
let tp = clamp(clock * 1.6 / 1.17, 0.0, 1.0)
let fp = tp * tp * (3.0 - 2.0 * tp)
let tr = clamp(clock / max(down, 0.001) * 1.6 / 1.17, 0.0, 1.0)
let fr = tr * tr * (3.0 - 2.0 * tr)
let age = mix(mix(1000.0, (1.0 - down) * 1.6, going), clock * 1.6, held * timed)
let press = mix(down, max(fp * held, still), timed) * en
let restore = fr * going * en
let strength = clamp(1.0 - age / 1.6, 0.0, 1.0) * mix(0.9, 1.0 - 0.1 * fp, held) * en
let lp = (pq - c) / (h + vec2(19.4 * k, 19.4 * k))
let dist = length(lp)
let wt = clamp(age / 1.32, 0.0, 1.0)
let centre = 1.25 * wt * wt * (3.0 - 2.0 * wt)
let delta = dist - centre
let life = clamp(1.0 - age / 1.6, 0.0, 1.0)
let wfl = mix(1.0, smoothstep(dist - 0.14, dist + 0.26, centre), timed)
let flat = clamp(press * wfl + restore * (1.0 - wfl), 0.0, 1.0)
let lift = restore * wfl * (1.0 - wt) * 0.45
// How the glass stands: hollow faces show only under the pointer or the
// finger, a face lying on the ground rises under the pointer.
let vis = mix(1.0, max(hov, press), look.z)
let stand = max(look.y, hov * 0.6) * en
let e = stand * (1.0 - 0.7 * flat + 0.6 * lift) + 0.15 * hov * look.y * en
let col_glass = step(0.5, tint.a)
// Under the glass: the shadow, and the pool of light in it.
let oy = (0.03 + 0.08 * e) * s
let sg = (0.05 + 0.05 * e) * s
let hs = vec2(max(h.x - 0.06 * s, 1.0), h.y)
let quad = smoothstep(0.0, 3.0, 20.0 - max(abs(pq.x - c.x) - h.x, abs(pq.y - c.y) - h.y))
let inside = Finish.cover(d, px)
let sh = Finish.drop(pq, c, hs, min(r, min(hs.x, hs.y)), vec2(0.0, oy), sg) * 0.10 * e * quad * mix(1.0, 0.5, inside) * look.x
let sink = mix(vec3(0.150, 0.170, 0.230), tint.rgb * 0.55, col_glass * 0.6)
var acc = vec4(sink * sh, sh)
let pc = vec2(c.x + 0.45 * h.x, c.y + h.y + 0.04 * s)
let pe = (pq - pc) / vec2(max(0.32 * h.x, 2.0), max(0.05 * s, 1.0))
let pa = exp(-dot(pe, pe)) * 0.12 * e * (1.0 - inside) * quad * look.x
let pool = mix(vec3(1.0, 0.975, 0.940), mix(tint.rgb, vec3(1.0, 1.0, 1.0), 0.35), col_glass)
acc = Finish.over(acc, vec4(pool * pa, pa))
// The face.
let w = clamp(0.11 * s, 2.0, 5.0)
// The two crescents where the rim is lit: the upper left end, where its
// edge faces the light, and the lower right, where the light leaves.
let L = normalize(vec2(-0.85, -0.53))
let cres_a = pow(max(dot(n, L), 0.0), 3.0)
let cres_b = pow(max(dot(n, -L), 0.0), 3.0) * 0.8
let cres = max(cres_a, cres_b)
let vy = clamp((pq.y - c.y) / max(h.y, 1.0) * 0.5 + 0.5, 0.0, 1.0)
let glow = exp(-t / max(0.14 * s, 0.5))
let lit = (1.0 + 0.15 * hov) * (1.0 - 0.3 * flat + 0.3 * lift)
// Clear glass: no base, the soft glow along its rim and a breath of
// white under the pointer; coloured glass: the colour, a little lighter
// toward its top and along the rim.
let veil = 0.03 * hov * look.x + 0.14 * glow * lit * look.x
var body = vec4(veil, veil, veil, veil)
let ct = mix(tint.rgb, vec3(1.0, 1.0, 1.0), 0.08 * (1.0 - vy) + 0.10 * glow) * (1.0 - 0.08 * flat)
let ca = tint.a * look.x
let cbody = vec4(ct * ca, ca)
body = mix(body, Finish.over(cbody, body * 0.25), step(0.001, tint.a))
// Thick coloured glass: its inner shadow, in a deeper shade of the
// colour, along the top inner edge and a little round the sides; it
// leaves the edge's first two device pixels to the rim's light, so it
// draws no dark outline.
let ist = Finish.inset(pq, c, h, r, vec2(0.0, 0.11 * s), 0.08 * s)
let iss = Finish.inset(pq, c, h, r, vec2(0.0, 0.0), 0.05 * s)
let isa = clamp(ist * 0.75 + iss * 0.30, 0.0, 1.0) * smoothstep(0.0, 2.0 * px, t) * col_glass * look.x * (1.0 - 0.3 * flat)
body = Finish.over(body, vec4(tint.rgb * 0.40 * isa, isa))
// The rim band: thin along the sides, swelling into the crescents, the
// ground seen through it lifted toward white.
let wr = w * (0.55 + 1.1 * cres)
let u = clamp(1.0 - t / wr, 0.0, 1.0)
let foot = smoothstep(0.3, 0.95, n.y)
let ra = smoothstep(0.0, 0.7, u) * (0.10 + 0.65 * cres + 0.22 * foot) * lit
body = Finish.over(body, vec4(ra, ra, ra, ra))
// The specular glint in the upper crescent, running on thinly along the
// top, and a softer one in the lower.
let ls = normalize(vec2(-0.75, -0.66))
let gd = 0.75 * w
let band = exp(-((t - gd) * (t - gd)) / max(0.18 * w * w, 0.01))
let xf = clamp((pq.x - c.x + h.x) / max(2.0 * h.x, 1.0), 0.0, 1.0)
let top = pow(max(-n.y, 0.0), 4.0) * (1.0 - 0.8 * xf) * 0.35
let spa = band * (pow(max(dot(n, ls), 0.0), 8.0) + top) * 0.80 * lit
let spb = band * pow(max(dot(n, -ls), 0.0), 8.0) * 0.45 * lit
// The glint: a small hot spot where the upper crescent turns.
let gl = exp(-((t - 0.6 * w) * (t - 0.6 * w)) / max(0.10 * w * w, 0.01)) * pow(max(dot(n, normalize(vec2(-0.55, -0.83))), 0.0), 16.0) * 1.0 * lit
let sp = clamp(spa + spb + gl, 0.0, 1.0)
body = Finish.over(body, vec4(sp, sp, sp, sp))
// The ring of the press, lit on the glass.
let crest = exp(-(delta * delta) / (0.15 * 0.15)) * life * life * strength * smoothstep(0.0, 0.10, age) * (1.0 - smoothstep(1.16, 1.44, dist)) * timed
let flank = (-delta / 0.15) * crest
let rdir = (pq - c) / max(length(pq - c), 0.001)
let tilt = dot(rdir, vec2(-0.26, -0.97)) * flank
let rwa = clamp(crest * 0.7 + max(tilt, 0.0) * 0.7, 0.0, 1.0)
body = Finish.over(body, vec4(rwa, rwa, rwa, rwa))
let rsa = clamp(-tilt * 0.4, 0.0, 1.0)
body = Finish.over(body, vec4(vec3(0.200, 0.235, 0.310) * rsa, rsa))
let split = clamp(-flank * 0.10, -0.12, 0.12)
body = body + vec4(0.9 * split, 0.1 * split, -0.9 * split, 0.0) * step(0.001, crest)
// Out of use: a pane laid on the ground, a faint edge, no light on it.
body = mix(body, vec4(0.03, 0.03, 0.03, 0.03), disabled)
body = body * inside
// The edge: a bright line on its inner pixel, no dark outline; out of
// use, the pane's faint edge.
let el = Finish.band(d, px, 2.0 * px, px) * 0.85 * (0.55 + (1.0 - 0.55) * cres) * lit * en
body = Finish.over(body, vec4(el, el, el, el))
let ed = Finish.band(d, 0.0, px, px) * 0.30 * disabled
body = Finish.over(body, vec4(vec3(0.420, 0.435, 0.475) * ed, ed))
acc = Finish.over(acc, body)
acc = acc * vis
acc = Finish.over(acc, lq_focus(d, px, focus * en))
let rr = max(r - 5.0, 2.0)
let q = pq - c
var ap = atan2(q.x, -q.y) - anim * 2.0 * PI
ap = ap - floor(ap / (2.0 * PI)) * 2.0 * PI
let arc = step(ap, 1.5 * PI) * Finish.cover(abs(length(q) - rr) - 0.75, px) * loading
return Finish.over(acc, vec4(ink.rgb * arc * ink.a, arc * ink.a))
}
// The room round every button for its shadow, its pool and its ring.
mod.widgets.ButtonFlat.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.ButtonFlat.clip_x = false
mod.widgets.ButtonFlat.clip_y = false
// A view lets what it holds draw past its edge; scroll views still clip.
mod.widgets.View.clip_x = false
mod.widgets.View.clip_y = false
// The flat button: a pill lying on the ground, lifted by the pointer.
mod.widgets.ButtonFlat.draw_bg.pixel = fn() {
let m = 12.0
let pq = 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)
return self.host_over(lq_pill(pq, self.rect_size, self.rect_pos, px, self.hover, self.down, self.cap_time, 0.0, self.focus, self.disabled, self.loading, self.anim_time, self.loading_color, vec4(0.0, 0.0, 0.0, 0.0), vec4(1.0, 0.0, 0.0, 0.0)), m)
}
// The standard, sized and compound buttons are clear pills standing off the
// ground (THE GLASS PILL).
mod.widgets.Button.draw_bg.pixel = fn() {
let m = 20.0
let pq = 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)
return self.host_over(lq_pill(pq, self.rect_size, self.rect_pos, px, self.hover, self.down, self.cap_time, 1.0, self.focus, self.disabled, self.loading, self.anim_time, self.loading_color, vec4(0.0, 0.0, 0.0, 0.0), vec4(1.0, 1.0, 0.0, 0.0)), m)
}
mod.widgets.ButtonXs.draw_bg.pixel = fn() {
let m = 20.0
let pq = 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)
return self.host_over(lq_pill(pq, self.rect_size, self.rect_pos, px, self.hover, self.down, self.cap_time, 1.0, self.focus, self.disabled, self.loading, self.anim_time, self.loading_color, vec4(0.0, 0.0, 0.0, 0.0), vec4(1.0, 1.0, 0.0, 0.0)), m)
}
mod.widgets.ButtonSm.draw_bg.pixel = fn() {
let m = 20.0
let pq = 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)
return self.host_over(lq_pill(pq, self.rect_size, self.rect_pos, px, self.hover, self.down, self.cap_time, 1.0, self.focus, self.disabled, self.loading, self.anim_time, self.loading_color, vec4(0.0, 0.0, 0.0, 0.0), vec4(1.0, 1.0, 0.0, 0.0)), m)
}
mod.widgets.ButtonLg.draw_bg.pixel = fn() {
let m = 20.0
let pq = 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)
return self.host_over(lq_pill(pq, self.rect_size, self.rect_pos, px, self.hover, self.down, self.cap_time, 1.0, self.focus, self.disabled, self.loading, self.anim_time, self.loading_color, vec4(0.0, 0.0, 0.0, 0.0), vec4(1.0, 1.0, 0.0, 0.0)), m)
}
mod.widgets.ButtonXl.draw_bg.pixel = fn() {
let m = 20.0
let pq = 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)
return self.host_over(lq_pill(pq, self.rect_size, self.rect_pos, px, self.hover, self.down, self.cap_time, 1.0, self.focus, self.disabled, self.loading, self.anim_time, self.loading_color, vec4(0.0, 0.0, 0.0, 0.0), vec4(1.0, 1.0, 0.0, 0.0)), m)
}
mod.widgets.ButtonCompound.draw_bg.pixel = fn() {
let m = 20.0
let pq = 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)
return self.host_over(lq_pill(pq, self.rect_size, self.rect_pos, px, self.hover, self.down, self.cap_time, 1.0, self.focus, self.disabled, self.loading, self.anim_time, self.loading_color, vec4(0.0, 0.0, 0.0, 0.0), vec4(1.0, 1.0, 0.0, 0.0)), m)
}
// The primary is blue glass under a white label.
mod.widgets.ButtonPrimary.draw_bg.pixel = fn() {
let m = 20.0
let pq = 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)
return self.host_over(lq_pill(pq, self.rect_size, self.rect_pos, px, self.hover, self.down, self.cap_time, 1.0, self.focus, self.disabled, self.loading, self.anim_time, self.loading_color, vec4(0.080, 0.270, 0.720, 0.82), vec4(1.0, 1.0, 0.0, 0.0)), m)
}
// Secondary is clear glass with a breath of cool blue; tertiary a breath
// of smoke: tints, not a base.
mod.widgets.ButtonSecondary.draw_bg.pixel = fn() {
let m = 20.0
let pq = 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)
return self.host_over(lq_pill(pq, self.rect_size, self.rect_pos, px, self.hover, self.down, self.cap_time, 1.0, self.focus, self.disabled, self.loading, self.anim_time, self.loading_color, vec4(0.700, 0.760, 0.860, 0.06), vec4(1.0, 1.0, 0.0, 0.0)), m)
}
mod.widgets.ButtonTertiary.draw_bg.pixel = fn() {
let m = 20.0
let pq = 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)
return self.host_over(lq_pill(pq, self.rect_size, self.rect_pos, px, self.hover, self.down, self.cap_time, 1.0, self.focus, self.disabled, self.loading, self.anim_time, self.loading_color, vec4(0.380, 0.400, 0.450, 0.07), vec4(1.0, 1.0, 0.0, 0.0)), m)
}
// The destructive action is red glass under a white label: the colour only
// where the meaning is.
mod.widgets.ButtonDanger.draw_bg.pixel = fn() {
let m = 20.0
let pq = 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)
return self.host_over(lq_pill(pq, self.rect_size, self.rect_pos, px, self.hover, self.down, self.cap_time, 1.0, self.focus, self.disabled, self.loading, self.anim_time, self.loading_color, vec4(0.700, 0.100, 0.090, 0.84), vec4(1.0, 1.0, 0.0, 0.0)), m)
}
// The outlined button is the rim alone: an empty frame of glass.
mod.widgets.ButtonOutline.draw_bg.pixel = fn() {
let m = 12.0
let pq = 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)
return self.host_over(lq_pill(pq, self.rect_size, self.rect_pos, px, self.hover, self.down, self.cap_time, 0.0, self.focus, self.disabled, self.loading, self.anim_time, self.loading_color, vec4(0.0, 0.0, 0.0, 0.0), vec4(0.0, 0.0, 0.0, 0.0)), m)
}
// The borderless button shows nothing until the pointer finds it.
mod.widgets.ButtonFlatter.draw_bg.pixel = fn() {
let m = 12.0
let pq = 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)
return self.host_over(lq_pill(pq, self.rect_size, self.rect_pos, px, self.hover, self.down, self.cap_time, 0.0, self.focus, self.disabled, self.loading, self.anim_time, self.loading_color, vec4(0.0, 0.0, 0.0, 0.0), vec4(1.0, 0.0, 1.0, 0.0)), m)
}
// Rows of 26: a 14 point line with 6 above and below, 10 either side
// (the label then clears the capsule end by a point at its cap height).
mod.widgets.ButtonFlat.padding = mod.turtle.Inset{left: 10.0 right: 10.0 top: 6.0 bottom: 6.0}
mod.widgets.ButtonFlat.margin = mod.turtle.Inset{left: 0.0 right: 0.0 top: 0.0 bottom: 0.0}
mod.widgets.ButtonFlat.spacing = 6.0
mod.widgets.ButtonFlat.icon_walk.width = 14.0
mod.widgets.ButtonIcon.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 6.0 bottom: 6.0}
mod.widgets.ButtonFlatIcon.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 6.0 bottom: 6.0}
mod.widgets.ButtonFlatterIcon.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 6.0 bottom: 6.0}
mod.widgets.ButtonFlat.draw_bg.loading_color = #1c1e22
mod.widgets.ButtonFlat.draw_text.color = #1c1e22
mod.widgets.ButtonFlat.draw_text.color_hover = #121417
mod.widgets.ButtonFlat.draw_text.color_down = #1c1e22
mod.widgets.ButtonFlat.draw_text.color_focus = #1c1e22
mod.widgets.ButtonFlat.draw_text.color_disabled = #a4a6ab
mod.widgets.ButtonFlat.draw_icon.color = #1c1e22
// On tinted glass the ink goes a step blacker, as on the references.
mod.widgets.ButtonPrimary.draw_text.color = #ffffff
mod.widgets.ButtonPrimary.draw_text.color_hover = #ffffff
mod.widgets.ButtonPrimary.draw_text.color_down = #ffffff
mod.widgets.ButtonPrimary.draw_text.color_focus = #ffffff
mod.widgets.ButtonPrimary.draw_text.color_disabled = #a4a6ab
mod.widgets.ButtonPrimary.draw_icon.color = #ffffff
mod.widgets.ButtonPrimary.draw_bg.loading_color = #ffffff
mod.widgets.ButtonPrimary.draw_text.material_glow_ink = #1a52c7
mod.widgets.ButtonDanger.draw_text.color = #ffffff
mod.widgets.ButtonDanger.draw_text.color_hover = #ffffff
mod.widgets.ButtonDanger.draw_text.color_down = #ffffff
mod.widgets.ButtonDanger.draw_text.color_focus = #ffffff
mod.widgets.ButtonDanger.draw_icon.color = #ffffff
mod.widgets.ButtonDanger.draw_text.material_glow_ink = #bd1f1a
mod.widgets.ButtonSecondary.draw_text.color = #1c1e22
mod.widgets.ButtonSecondary.draw_text.color_hover = #121417
mod.widgets.ButtonSecondary.draw_text.color_down = #1c1e22
mod.widgets.ButtonSecondary.draw_text.color_focus = #1c1e22
mod.widgets.ButtonSecondary.draw_icon.color = #1c1e22
mod.widgets.ButtonTertiary.draw_text.color = #1c1e22
mod.widgets.ButtonTertiary.draw_text.color_hover = #121417
mod.widgets.ButtonTertiary.draw_text.color_down = #1c1e22
mod.widgets.ButtonTertiary.draw_text.color_focus = #1c1e22
mod.widgets.ButtonTertiary.draw_icon.color = #1c1e22
mod.widgets.ButtonOutline.draw_text.color = #1c1e22
mod.widgets.ButtonOutline.draw_text.color_hover = #121417
mod.widgets.ButtonOutline.draw_text.color_down = #1c1e22
mod.widgets.ButtonOutline.draw_text.color_focus = #1c1e22
mod.widgets.ButtonOutline.draw_icon.color = #1c1e22
// CHECK BOX. A 16 point glass tile with a 5 point corner, sitting close to
// the ground: no pool under a part this small. Checked, the tile turns
// blue glass and carries a black mark, 1.75 points, round capped.
let lq_check = fn(pq: vec2, size: vec2, org: vec2, px: float, box: float, hover: float, down: float, focus: float, active: float, disabled: float, mixed: float, error: float, round: float, at_end: float) -> vec4 {
let x0 = (size.x - box) * at_end
let y0 = size.y * 0.5 - box * 0.5
let b = lq_snap(org, vec2(x0, y0), vec2(x0 + box, y0 + box), px)
let c = (b.xy + b.zw) * 0.5
let h = (b.zw - b.xy) * 0.5
let r = mix(5.0, h.x, round)
let d = Material.sd_box(pq, c, h, r)
let en = 1.0 - disabled
let press = down * en
let hov = hover * en * (1.0 - press)
let on = max(active, mixed)
let dis = mix(1.0, 0.3, disabled)
var acc = lq_cast(pq, c, h, r, px, box, 0.24 * dis * mix(1.0, 0.4, press) * (1.0 + 0.5 * hov), 1.0 - press, mix(vec3(0.165, 0.192, 0.251), vec3(0.204, 0.282, 0.431), on), vec3(1.0, 1.0, 1.0), 0.0)
let lift = 0.05 * hov - 0.07 * press
let rim = vec4(mix(mix(vec3(0.620, 0.631, 0.659), vec3(0.478, 0.561, 0.690), on * 0.6), vec3(0.753, 0.278, 0.243), error), mix(0.95, 0.5, disabled))
let hl = mix(0.95, 1.0, hov) * mix(1.0, 0.62, press) * mix(1.0, 0.5, disabled)
let body = lq_glass_body(pq, c, h, r, px, box, lq_blue(), on, lift, mix(1.0, 2.0, on), 0.9 * mix(1.0, 0.55, disabled), hl, 1.0 + 0.5 * press, mix(0.08 * box, 3.0 * px, hov) + px, 1.0, rim, vec3(0.965, 0.973, 0.988))
acc = Finish.over(acc, body)
let ink = mix(mix(vec3(0.051, 0.059, 0.078), vec3(0.620, 0.157, 0.125), error), vec3(0.643, 0.651, 0.671), disabled)
let k0 = vec2(c.x - box * 0.24, c.y + box * 0.01)
let k1 = vec2(c.x - box * 0.07, c.y + box * 0.18)
let k2 = vec2(c.x + box * 0.24, c.y - box * 0.17)
let ck = Finish.cover(min(lq_seg(pq, k0, k1), lq_seg(pq, k1, k2)) - 0.875, px) * active * (1.0 - mixed)
let dash = Finish.cover(lq_seg(pq, vec2(c.x - box * 0.2, c.y), vec2(c.x + box * 0.2, c.y)) - 0.875, px) * mixed
let mk = max(ck, dash)
acc = Finish.over(acc, vec4(ink * mk, mk))
return Finish.over(acc, lq_focus(d, px, focus * en))
}
mod.widgets.CheckBoxFlat.draw_bg.vertex = fn() {
let m = 8.0
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
mod.widgets.CheckBoxFlat.clip_x = false
mod.widgets.CheckBoxFlat.clip_y = false
mod.widgets.CheckBoxFlat.draw_bg.pixel = fn() {
let m = 8.0
let pq = 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)
return lq_check(pq, self.rect_size, self.rect_pos, px, 16.0, self.hover, self.down, self.focus, self.active, self.disabled, self.mixed, self.error, 0.0, self.mark_at_end)
}
mod.widgets.CheckBoxCircle.draw_bg.pixel = fn() {
let m = 8.0
let pq = 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)
return lq_check(pq, self.rect_size, self.rect_pos, px, 16.0, self.hover, self.down, self.focus, self.active, self.disabled, self.mixed, self.error, 1.0, self.mark_at_end)
}
mod.widgets.CheckBoxFlat.draw_bg.size = 16.0
// The box stands in the padding and the label's margin, so a box with no
// label still has the room it draws in.
mod.widgets.CheckBoxFlat.padding = mod.turtle.Inset{left: 8.0 right: 8.0 top: 6.0 bottom: 6.0}
mod.widgets.CheckBoxFlat.label_walk.margin.left = 16.0
// THE AIR SWITCH. The toggles and both switches are drawn as the glass
// family's switch (glass.GlassRadio, the "Air" switch on Glass > Controls):
// a short capsule track two thirds of the switch's height, grey off and
// green on (red in error), a white nub riding it with a soft shadow, and
// over the nub a larger lens of clear glass the switch's full height. The
// lens magnifies what lies under it by a fifth and bends the track in
// toward its rim, red and blue a little apart there, frosted a little more
// toward the top, with a bright crescent on the rim facing up and to the
// right and a faint seal round it. Crossing, it stretches like a drop,
// longest at the middle, round at the ends. Its numbers are the glass
// switch's (70 by 34), scaled to this switch's height.
// The switch as the lens sees it at `q` (the switch's points): the ground,
// the track and the nub, no shadow.
let lq_air_slot = fn(q: vec2, tcen: vec2, thalf: vec2, kc: vec2, khalf: vec2, kr: float, tcol: vec3, ta: float, ground: vec3, h: float) -> vec3 {
let dt = Material.sd_box(q, tcen, thalf, thalf.y)
var col = mix(ground, tcol, smoothstep(0.5, -0.5, dt) * ta)
let dk = Material.sd_box(q, kc, khalf, kr)
let kcol = mix(vec3(1.0, 1.0, 1.0), vec3(0.90, 0.91, 0.94), clamp(q.y / max(h, 1.0), 0.0, 1.0) * 0.28)
return mix(col, kcol, smoothstep(0.5, -0.5, dk))
}
// The switch in the box `b0`..`b1`, the knob at `t` (0 off, 1 on).
// Premultiplied.
let lq_air_switch = fn(pq: vec2, b0: vec2, b1: vec2, px: float, t: float, hover: float, disabled: float, error: float, ground: vec3) -> vec4 {
let w = b1.x - b0.x
let h = b1.y - b0.y
let p = pq - b0
let s = h / 34.0
let k = clamp(t, 0.0, 1.0)
let on_col = mix(mix(vec3(0.27, 0.80, 0.33), vec3(0.643, 0.651, 0.671), disabled), lq_red(), error)
let off_col = mix(vec3(0.46, 0.48, 0.51), vec3(0.70, 0.71, 0.73), disabled)
let tcol = mix(off_col, on_col, k)
// Off, the track is a breath of shade on the glass; on, its colour.
let ta = mix(0.3, 1.0, k)
let track_h = h * 0.66
let kpad = 3.0 * s
let knob_h = track_h - 4.0 * s
let travel_w = (h - kpad * 2.0) * 1.35
let knob_w = travel_w + 6.0 * s - h + knob_h
let track_xpad = kpad + (travel_w - knob_w) * 0.5
let tcen = vec2(w * 0.5, h * 0.5)
let thalf = vec2((w - track_xpad * 2.0) * 0.5, track_h * 0.5)
let kc = vec2(mix(kpad + travel_w * 0.5, w - kpad - travel_w * 0.5, k), h * 0.5)
let khalf = vec2(knob_w * 0.5, knob_h * 0.5)
let kr = min(knob_h * 0.44, khalf.x)
// The slot: the track, the nub's shadow, the nub.
let dt = Material.sd_box(p, tcen, thalf, thalf.y)
var acc = vec4(tcol * ta, ta) * Finish.cover(dt, px)
let trl = Finish.band(dt, 0.0, px, px) * 0.35 * (1.0 - k)
acc = Finish.over(acc, vec4(vec3(0.20, 0.22, 0.27) * trl, trl))
let ds = Material.sd_box(p, kc + vec2(-0.5 * s, 1.5 * s), khalf + vec2(0.5 * s, 0.5 * s), kr)
acc = Finish.over(acc, vec4(0.0, 0.0, 0.0, 0.18) * smoothstep(1.0, -1.0, ds))
let dk = Material.sd_box(p, kc, khalf, kr)
let kcol = mix(vec3(1.0, 1.0, 1.0), vec3(0.90, 0.91, 0.94), clamp(p.y / max(h, 1.0), 0.0, 1.0) * 0.28)
acc = Finish.over(acc, vec4(kcol, 1.0) * Finish.cover(dk, px))
// The lens over the nub, stretched as it crosses.
let klh = h - kpad * 2.0
let klw = klh * 1.35
let gloop = sin(k * PI)
let lh = klh + 6.0 * s - gloop * 2.0 * s
let lw = klw + 6.0 * s + gloop * klw * 0.55
let lc = vec2(mix(kpad + klw * 0.5, w - kpad - klw * 0.5, k), h * 0.5)
let lhalf = vec2(lw * 0.5, lh * 0.5)
let lr = lhalf.y
let shape = Material.sd_box(p, lc, lhalf, lr)
let la = Finish.cover(shape, px)
if la > 0.001 {
let e = 0.25
let gx = Material.sd_box(p + vec2(e, 0.0), lc, lhalf, lr) - Material.sd_box(p - vec2(e, 0.0), lc, lhalf, lr)
let gy = Material.sd_box(p + vec2(0.0, e), lc, lhalf, lr) - Material.sd_box(p - vec2(0.0, e), lc, lhalf, lr)
let n = vec2(gx, gy) / max(length(vec2(gx, gy)), 0.0001)
let rim = clamp(1.0 - abs(shape) / (9.0 * s), 0.0, 1.0)
let bend = rim * rim
let warped = p + (lc - p) * (0.20 * (1.0 - bend)) + n * (bend * 9.0 * s)
let chroma = n * (bend * 3.0 * s)
// What the lens shows, over a black ground and over a white one:
// where it shows the ground, it is clear glass (lq_unfill).
let cr0 = lq_air_slot(warped + chroma, tcen, thalf, kc, khalf, kr, tcol, ta, vec3(0.0, 0.0, 0.0), h)
let cg0 = lq_air_slot(warped, tcen, thalf, kc, khalf, kr, tcol, ta, vec3(0.0, 0.0, 0.0), h)
let cb0 = lq_air_slot(warped - chroma, tcen, thalf, kc, khalf, kr, tcol, ta, vec3(0.0, 0.0, 0.0), h)
let cr1 = lq_air_slot(warped + chroma, tcen, thalf, kc, khalf, kr, tcol, ta, vec3(1.0, 1.0, 1.0), h)
let cg1 = lq_air_slot(warped, tcen, thalf, kc, khalf, kr, tcol, ta, vec3(1.0, 1.0, 1.0), h)
let cb1 = lq_air_slot(warped - chroma, tcen, thalf, kc, khalf, kr, tcol, ta, vec3(1.0, 1.0, 1.0), h)
let seen0 = vec3(cr0.x, cg0.y, cb0.z)
let seen1 = vec3(cr1.x, cg1.y, cb1.z)
let kg = clamp((seen1.x + seen1.y + seen1.z - seen0.x - seen0.y - seen0.z) / 3.0, 0.0, 1.0)
var lens = vec4(seen0, 1.0 - kg)
let top = smoothstep(0.0, 1.0, 1.0 - p.y / max(h, 1.0))
let w1 = 0.12 + top * 0.10
lens = Finish.over(lens, vec4(w1, w1, w1, w1))
let facing = clamp(dot(n, normalize(vec2(0.50, -0.86))), 0.0, 1.0)
let edge = clamp(1.0 - abs(shape) / (2.6 * s), 0.0, 1.0)
let w2 = facing * edge * (0.45 + hover * 0.10)
lens = Finish.over(lens, vec4(w2, w2, w2, w2))
let w3 = 0.16 * Finish.cover(abs(shape) - 0.4 * s, px)
lens = Finish.over(lens, vec4(w3, w3, w3, w3))
acc = Finish.over(acc, lens * la)
}
return acc * mix(1.0, 0.55, disabled)
}
// TOGGLE. A 36 by 16 channel cut into the ground (the toggle's `size` by
// `pill_aspect`), filled with the accent when on. The knob is a flat disc
// of liquid glass, 22 points, his bench spec, with the widget's liquid
// motion; the channel runs on through it, bent. The face is drawn after
// the sliders, whose drop it borrows (THE TOGGLE'S DROP below).
let lq_toggle_track = fn(pq: vec2, tc: vec2, thh: vec2, px: float, th: float, hov: float, on: float, blue: vec3, disabled: float, dis: float, en: float) -> vec4 {
let tr = thh.y
var acc = lq_cast(pq, tc, thh, tr, px, th, 0.28 * dis * (1.0 + 0.3 * hov), 1.0, mix(vec3(0.165, 0.192, 0.251), vec3(0.204, 0.282, 0.431), on), mix(vec3(1.0, 1.0, 1.0), vec3(0.663, 0.769, 0.949), on), mix(0.3, 0.45, on) * mix(1.0, 1.3, hov) * en)
let lift = 0.05 * hov
let rim = vec4(mix(vec3(0.682, 0.690, 0.714), vec3(0.478, 0.561, 0.690), on * 0.6), mix(0.9, 0.5, disabled))
let body = lq_glass_body(pq, tc, thh, tr, px, th, blue, on, lift, mix(1.0, 2.0, on), 0.88 * mix(1.0, 0.55, disabled), mix(0.95, 1.0, hov) * mix(1.0, 0.5, disabled), 1.0, mix(0.07 * th, 3.0 * px, hov) + px, 1.5, rim, vec3(0.965, 0.973, 0.988))
return Finish.over(acc, body)
}
mod.widgets.ToggleFlat.draw_bg.size = uniform(20.0)
mod.widgets.ToggleFlat.draw_bg.pill_aspect = uniform(2.06)
// The toggle places a knob's marks and measures a drag from these two:
// with a point of border and a point of inset its knob centre is the one
// the face draws (half the track and a point in, travelling w - size).
mod.widgets.ToggleFlat.draw_bg.border_size = uniform(1.0)
mod.widgets.ToggleFlat.draw_bg.knob_inset = uniform(1.0)
mod.widgets.Toggle.draw_bg.border_size = 1.0
mod.widgets.Toggle.draw_bg.knob_inset = 1.0
// A mark on the knob: ten points on a 22 point lens, in the ink at 0.7,
// whatever colour the page hands it.
mod.widgets.ToggleFlat.knob_icon_size = 0.5
mod.widgets.Toggle.knob_icon_size = 0.5
mod.widgets.ToggleFlat.draw_icon_on.get_color = fn() {
let base = self.eval_gradient()
let a = base.a * 0.7 * self.opacity
return vec4(vec3(0.110, 0.118, 0.133) * a, a)
}
mod.widgets.ToggleFlat.draw_icon_off.get_color = fn() {
let base = self.eval_gradient()
let a = base.a * 0.7 * self.opacity
return vec4(vec3(0.110, 0.118, 0.133) * a, a)
}
mod.widgets.Toggle.draw_icon_off.get_color = fn() {
let base = self.eval_gradient()
let a = base.a * 0.7 * self.opacity
return vec4(vec3(0.110, 0.118, 0.133) * a, a)
}
mod.widgets.Toggle.draw_icon_on.get_color = fn() {
let base = self.eval_gradient()
let a = base.a * 0.7 * self.opacity
return vec4(vec3(0.110, 0.118, 0.133) * a, a)
}
mod.widgets.ToggleFlat.padding = mod.turtle.Inset{left: 20.0 right: 20.0 top: 6.0 bottom: 6.0}
mod.widgets.ToggleFlat.label_walk.margin.left = 26.0
// RADIO. A 16 point glass disc; chosen, a 6 point blue glass dot in it with
// its own hairline on top.
let lq_radio = fn(pq: vec2, size: vec2, org: vec2, px: float, hover: float, down: float, focus: float, active: float, disabled: float) -> vec4 {
let b = lq_snap(org, vec2(0.0, size.y * 0.5 - 8.0), vec2(16.0, size.y * 0.5 + 8.0), px)
let c = (b.xy + b.zw) * 0.5
let h = (b.zw - b.xy) * 0.5
let r = h.x
let d = length(pq - c) - r
let en = 1.0 - disabled
let press = down * en
let hov = hover * en * (1.0 - press)
let dis = mix(1.0, 0.3, disabled)
var acc = lq_cast(pq, c, h, r, px, 16.0, 0.24 * dis * mix(1.0, 0.4, press) * (1.0 + 0.5 * hov), 1.0 - press, vec3(0.165, 0.192, 0.251), vec3(1.0, 1.0, 1.0), 0.0)
let lift = 0.05 * hov - 0.07 * press
let rim = vec4(0.620, 0.631, 0.659, mix(0.95, 0.5, disabled))
let body = lq_glass_body(pq, c, h, r, px, 16.0, vec3(0.0, 0.0, 0.0), 0.0, lift, 1.0, 0.9 * mix(1.0, 0.55, disabled), mix(0.95, 1.0, hov) * mix(1.0, 0.62, press) * mix(1.0, 0.5, disabled), 1.0 + 0.5 * press, mix(1.3, 3.0 * px, hov) + px, 1.0, rim, vec3(0.965, 0.973, 0.988))
acc = Finish.over(acc, body)
// The dot: blue glass, darker at its foot, a pale hairline on its crown.
let dd = length(pq - c) - 3.0
let v = clamp((pq.y - c.y) / 3.0 * 0.5 + 0.5, 0.0, 1.0)
var dc = mix(mix(vec3(0.439, 0.588, 0.855), vec3(0.259, 0.431, 0.741), v), vec3(0.690, 0.710, 0.745), disabled)
dc = mix(dc, vec3(0.839, 0.886, 0.980), Finish.band(dd, 0.0, px, px) * smoothstep(0.2, -0.6, (pq.y - c.y) / 3.0))
let da = Finish.cover(dd, px) * active
acc = Finish.over(acc, vec4(dc * da, da))
return Finish.over(acc, lq_focus(d, px, focus * en))
}
mod.widgets.RadioButtonFlat.draw_bg.vertex = fn() {
let m = 8.0
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
mod.widgets.RadioButtonFlat.clip_x = false
mod.widgets.RadioButtonFlat.clip_y = false
mod.widgets.RadioButtonFlat.draw_bg.pixel = fn() {
let m = 8.0
let pq = 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)
return lq_radio(pq, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.active, self.disabled)
}
mod.widgets.RadioButtonFlat.draw_bg.size = 16.0
mod.widgets.RadioButtonFlat.padding = mod.turtle.Inset{left: 8.0 right: 8.0 top: 6.0 bottom: 6.0}
mod.widgets.RadioButtonFlat.label_walk.margin.left = 16.0
// An 8 by 4 point chevron, 1.5 point round-capped stroke, pointing down.
let lq_chevron = fn(p: vec2, cx: float, cy: float, px: float) -> float {
let d = min(lq_seg(p, vec2(cx - 4.0, cy - 2.0), vec2(cx, cy + 2.0)), lq_seg(p, vec2(cx, cy + 2.0), vec2(cx + 4.0, cy - 2.0)))
return Finish.cover(d - 0.75, px)
}
// TEXT FIELDS. The glass capsule lying flat: a field sits, it does not
// float, so no shadow and no pool, only the contact line under it. The
// face is the ground itself behind the glass; the hairline and the dark
// line under it along the top, a two pixel white exit band along the
// bottom. A field clips what it holds, so all of it stays in its box and
// the focus ring is drawn on the rim: the outline and the pixel inside it
// turn blue.
let lq_field = fn(pq: vec2, size: vec2, org: vec2, px: float, r: float, hover: float, down: float, focus: float, disabled: float, ground: vec3) -> vec4 {
let b = lq_snap(org, vec2(0.0, 0.0), vec2(size.x, size.y - 0.7), px)
let c = (b.xy + b.zw) * 0.5
let h = (b.zw - b.xy) * 0.5
let s = min(size.y, 32.0)
let rr = min(r, min(h.x, h.y))
let d = Material.sd_box(pq, c, h, rr)
let n = lq_norm(pq, c, h, rr)
let en = 1.0 - disabled
let g = 0.012 + 0.014 * hover * en * (1.0 - down) - 0.02 * down * en
let face = ground + vec3(g, g, g + 0.004)
let rim = vec4(mix(vec3(0.620, 0.631, 0.659), lq_accent(), focus * en), mix(0.95, 0.5, disabled))
var acc = lq_body(pq, c, h, rr, px, s, face, 0.45, 0.9 * mix(1.0, 0.55, disabled), mix(1.0, 0.5, disabled), 0.7, 0.05 * s + px, 2.0, rim, vec3(0.965, 0.973, 0.988))
let fr = Finish.band(d, px, 2.0 * px, px) * 0.45 * focus * en
acc = Finish.over(acc, vec4(lq_accent() * fr, fr))
let low = smoothstep(0.3, 0.9, n.y)
let ct = Finish.ring_out(d, 0.0, px, px) * low * 0.3 * mix(1.0, 0.4, disabled)
return Finish.over(acc, vec4(0.0, 0.0, 0.0, ct))
}
// The inputs a slider, a combo box, a number or a well carries inside
// themselves draw nothing: the control round them is the box.
mod.widgets.TextInputFlat.draw_bg.pixel = fn() {
if self.color.w < 0.01 || self.border_radius < 0.5 {
return vec4(0.0, 0.0, 0.0, 0.0)
}
let pq = self.pos * self.rect_size
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let lo = lq_field(pq, self.rect_size, self.rect_pos, px, 13.0, self.hover, self.down, self.focus, self.disabled, vec3(0.0, 0.0, 0.0))
let hi = lq_field(pq, self.rect_size, self.rect_pos, px, 13.0, self.hover, self.down, self.focus, self.disabled, vec3(1.0, 1.0, 1.0))
return lq_unfill(lo, hi, 0.08)
}
mod.widgets.TextInputFlat.padding.left = 12.0
mod.widgets.TextInputFlat.padding.right = 12.0
// The number field: the same capsule, with its stepper at the right end.
mod.widgets.NumberField.draw_bg.pixel = fn() {
let pq = self.pos * self.rect_size
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let lo = lq_field(pq, self.rect_size, self.rect_pos, px, 13.0, self.hover, 0.0, self.focus, self.disabled, vec3(0.0, 0.0, 0.0))
let hi = lq_field(pq, self.rect_size, self.rect_pos, px, 13.0, self.hover, 0.0, self.focus, self.disabled, vec3(1.0, 1.0, 1.0))
return lq_unfill(lo, hi, 0.08)
}
mod.widgets.NumberField.pad_left = 12.0
// The stepper stands clear of the capsule's round end: its column is the
// 12 point stepper plus the 13 point end, drawn in the left 12.
mod.widgets.NumberField.spin_width = 25.0
// The stepper: two small glass halves behind a hairline divider, a black
// chevron in each; the half under the pointer brightens, the held one
// settles darker.
mod.widgets.NumberSpin.draw_bg.pixel = fn() {
let p = self.pos * self.rect_size
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let w = 12.0
let h = self.rect_size.y
let mid = Finish.snap(self.rect_pos.y + h * 0.5, px) - self.rect_pos.y
let live = 1.0 - self.disabled
let upper = step(p.y, mid)
let hot = mix(max(0.0, -self.hot), max(0.0, self.hot), upper) * live
let dn = mix(max(0.0, -self.down), max(0.0, self.down), upper) * live
let hc = vec2(w * 0.5 + 0.5, mix(mid + (h - mid) * 0.5 - 1.0, mid * 0.5 + 1.0, upper))
let hh = vec2(w * 0.5 - 2.0, mid * 0.5 - 2.5)
let hd = Material.sd_box(p, hc, hh, 3.0)
let wash = Finish.cover(hd, px) * (0.35 * hot * (1.0 - dn))
var acc = vec4(wash, wash, wash, wash)
let dk = Finish.cover(hd, px) * 0.12 * dn
acc = Finish.over(acc, vec4(0.165 * dk, 0.192 * dk, 0.251 * dk, dk))
// the divider: a hairline down the left, one across the middle
let inx = step(3.0, p.x) * step(p.x, w - 2.0)
let iny = step(6.0, p.y) * step(p.y, h - 6.0)
let dv = max(step(mid - px, p.y) * step(p.y, mid) * inx, step(0.0, p.x) * step(p.x, px) * iny) * 0.5
acc = Finish.over(acc, vec4(0.620 * dv, 0.631 * dv, 0.659 * dv, dv))
let cx = w * 0.5 + 0.5
let yu = mid - 5.0
let yd = mid + 5.0
let cu = min(lq_seg(p, vec2(cx - 3.0, yu + 1.5), vec2(cx, yu - 1.5)), lq_seg(p, vec2(cx, yu - 1.5), vec2(cx + 3.0, yu + 1.5)))
let cd = min(lq_seg(p, vec2(cx - 3.0, yd - 1.5), vec2(cx, yd + 1.5)), lq_seg(p, vec2(cx, yd + 1.5), vec2(cx + 3.0, yd - 1.5)))
let mk = Finish.cover(min(cu, cd) - 0.75, px) * mix(0.85, 0.3, self.disabled)
return Finish.over(acc, vec4(vec3(0.110, 0.118, 0.133) * mk, mk))
}
// The printed arrow glyphs are hidden; the face draws the chevrons.
mod.widgets.NumberSpin.draw_text.color = #1c1e2200
mod.widgets.NumberSpin.draw_text.color_disabled = #1c1e2200
// The drag-number: the field, its value a faint blue wash over the glass.
mod.widgets.ValueInput.draw_bg.pixel = fn() {
let pq = self.pos * self.rect_size
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let lo = lq_field(pq, self.rect_size, self.rect_pos, px, 13.0, max(self.hover, self.drag), 0.0, self.focus, 0.0, vec3(0.0, 0.0, 0.0))
let hi = lq_field(pq, self.rect_size, self.rect_pos, px, 13.0, max(self.hover, self.drag), 0.0, self.focus, 0.0, vec3(1.0, 1.0, 1.0))
var acc = lq_unfill(lo, hi, 0.08)
let d = Material.sd_box(pq, self.rect_size * 0.5, self.rect_size * 0.5 - vec2(2.0, 2.0), 11.0)
let wash = step(0.0, self.fill) * step(pq.x, 2.0 + (self.rect_size.x - 4.0) * max(self.fill, 0.0)) * Finish.cover(d, px) * 0.18
return Finish.over(acc, vec4(lq_accent() * wash, wash))
}
mod.widgets.ValueInput.draw_text.color = #1c1e22
mod.widgets.ValueInput.arrow_color = #3d4048
mod.widgets.ValueInput.text_input.draw_bg.color = #00000000
// DROP-DOWN. A standing glass capsule with a chevron ten points in from the
// right; open, it settles like a pressed button.
mod.widgets.DropDownFlat.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.DropDownFlat.clip_x = false
mod.widgets.DropDownFlat.clip_y = false
mod.widgets.DropDownFlat.draw_bg.pixel = fn() {
let m = 12.0
let pq = 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 face = lq_button(pq, self.rect_size, self.rect_pos, px, self.hover, max(self.down, self.active), self.focus, self.disabled, 0.0, 0.0, vec4(0.0, 0.0, 0.0, 0.0), vec4(0.0, 0.0, 0.0, 0.0), 1.0, 0.0, 0.0)
let ch = lq_chevron(pq, self.rect_size.x - 14.0, self.rect_size.y * 0.5 + 0.5, px) * mix(0.75, 0.3, self.disabled)
return Finish.over(face, vec4(vec3(0.110, 0.118, 0.133) * ch, ch))
}
mod.widgets.DropDownFlat.padding.left = 14.0
mod.widgets.DropDownFlat.padding.right = 30.0
// The select wears a button of its own, built before this sheet, so it is
// given the drop-down's face, its grown quad and its chevron by name.
mod.widgets.Select.face.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.Select.face.clip_x = false
mod.widgets.Select.face.clip_y = false
mod.widgets.MultiSelect.face.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.MultiSelect.face.clip_x = false
mod.widgets.MultiSelect.face.clip_y = false
// The select round the face is a view of its own, and clips unless told.
mod.widgets.Select.clip_x = false
mod.widgets.Select.clip_y = false
mod.widgets.MultiSelect.clip_x = false
mod.widgets.MultiSelect.clip_y = false
mod.widgets.Select.face.draw_bg.pixel = fn() {
let m = 12.0
let pq = 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 face = lq_button(pq, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, self.loading_color, vec4(0.0, 0.0, 0.0, 0.0), 1.0, 0.0, 0.0)
let ch = lq_chevron(pq, self.rect_size.x - 14.0, self.rect_size.y * 0.5 + 0.5, px) * mix(0.75, 0.3, self.disabled)
return Finish.over(face, vec4(vec3(0.110, 0.118, 0.133) * ch, ch))
}
mod.widgets.Select.face.padding.right = 30.0
mod.widgets.MultiSelect.face.draw_bg.pixel = fn() {
let m = 12.0
let pq = 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 face = lq_button(pq, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, self.loading_color, vec4(0.0, 0.0, 0.0, 0.0), 1.0, 0.0, 0.0)
let ch = lq_chevron(pq, self.rect_size.x - 14.0, self.rect_size.y * 0.5 + 0.5, px) * mix(0.75, 0.3, self.disabled)
return Finish.over(face, vec4(vec3(0.110, 0.118, 0.133) * ch, ch))
}
mod.widgets.MultiSelect.face.padding.right = 30.0
// COMBO BOX. A field one types in, so the lying field, with the chevron.
mod.widgets.ComboBoxFlat.draw_bg.pixel = fn() {
let pq = self.pos * self.rect_size
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let face = lq_unfill(lq_field(pq, self.rect_size, self.rect_pos, px, 13.0, self.hover, 0.0, self.focus, self.disabled, vec3(0.0, 0.0, 0.0)), lq_field(pq, self.rect_size, self.rect_pos, px, 13.0, self.hover, 0.0, self.focus, self.disabled, vec3(1.0, 1.0, 1.0)), 0.08)
let ch = lq_chevron(pq, self.rect_size.x - 14.0, self.rect_size.y * 0.5 + 0.5, px) * mix(0.75, 0.3, self.disabled)
return Finish.over(face, vec4(vec3(0.110, 0.118, 0.133) * ch, ch))
}
mod.widgets.ComboBoxFlat.padding.left = 12.0
mod.widgets.ComboBoxFlat.padding.right = 30.0
mod.widgets.ComboBoxFlat.input.min_height = 0.0
// SLIDERS. Every slider face draws its handle and its shadows past its own
// box, so the family's root grows the quad and stops clipping. The handle
// on the three horizontal faces is the fader's liquid glass drop (FADER
// below) with the widget's liquid motion: a press spreads it, a drag pulls
// a tail behind it, a jump sends it travelling.
mod.widgets.SliderMinimal.clip_x = false
mod.widgets.SliderMinimal.clip_y = false
// A value fill laid in a well: the accent at three quarters over the well,
// with the well's own dark line kept along its top.
let lq_fill = fn(acc: vec4, p: vec2, c: vec2, h: vec2, px: float, x0: float, x1: float, col: vec3, k: float) -> vec4 {
let r = min(h.x, h.y)
let fc = vec2((x0 + x1) * 0.5, c.y)
let fh = vec2(max((x1 - x0) * 0.5, 0.0), h.y)
let fd = Material.sd_box(p, fc, fh, min(r, fh.x))
let wd = Material.sd_box(p, c, h, r)
let top = step(p.y, c.y - h.y + px) * 0.35
let fcol = mix(col, vec3(0.165, 0.192, 0.251), top)
let a = Finish.cover(fd, px) * Finish.cover(wd, px) * k * step(0.01, x1 - x0)
return Finish.over(acc, vec4(fcol * a, a))
}
// The bar slider's well and fill, drawn once where they lie and once more
// as the lens shows them, magnified.
let lq_bar_track = fn(p: vec2, gc: vec2, gh: vec2, px: float, f0: float, f1: float, fcol: vec3, dis: float) -> vec4 {
let acc = lq_well(p, gc, gh, gh.y, px, vec3(0.737, 0.749, 0.773), dis)
return lq_fill(acc, p, gc, gh, px, f0, f1, fcol, 0.8)
}
// The minimal slider's line and fill, drawn where they lie and as the lens
// shows them.
let lq_line_track = fn(pq: vec2, w: float, cy: float, px: float, fx0: float, fx1: float, fcol: vec3, dis: float) -> vec4 {
let inx = step(0.0, pq.x) * step(pq.x, w)
let dk = step(cy - px, pq.y) * step(pq.y, cy) * inx * 0.5 * dis
let lt = step(cy, pq.y) * step(pq.y, cy + px) * inx * 0.8 * dis
var acc = vec4(vec3(0.427, 0.443, 0.478) * dk, dk)
acc = Finish.over(acc, vec4(lt, lt, lt, lt))
let fd = Material.sd_box(pq, vec2((fx0 + fx1) * 0.5, cy), vec2((fx1 - fx0) * 0.5 + 1.0, 1.0), 1.0)
let fa = Finish.cover(fd, px) * 0.85
return Finish.over(acc, vec4(fcol * fa, fa))
}
// THE SETTLING DROP, the toggles' and the switches' knob: the engine's drop
// (round at rest, spread 8% by `press`, stretched by `s` toward `dir` with
// a tail merged on smoothly, squashed along by `sqa` and widened across by
// `sqx`) brought to rest without a swell. The engine merges its tail on
// above a stretch of 0.002, and two all but equal circles merged smoothly
// stand 0.175 of the radius proud of either, so its drop stands that much
// larger at the smallest stretch and falls back to its rest size in one
// frame as the tail catches up. Here the merged drop is blended in from the
// plain round drop at a stretch of 0.002 to the whole of it at 0.44, in
// step with the head narrowing as it stretches, so the drop keeps its rest
// size within a fiftieth of a point along and a fifteenth across while the
// tail catches up, and from 0.44 on is the engine's drop. The squash the
// motion hands over is read past its last 0.0065 (the 0.05 points of
// overshoot a motion may stop on, at the toggles' radius of 8), so a drop
// coming to rest does not squash and spring back once its motion stops.
let lq_smin = fn(a: float, b: float, k: float) -> vec2 {
let h = clamp(0.5 + 0.5 * (b - a) / max(k, 0.0001), 0.0, 1.0)
return vec2(mix(b, a, h) - k * h * (1.0 - h), h)
}
// A capsule from the origin to `pb`, radius `ra` at the origin and `rb` at
// `pb`: the drop's tail.
let lq_uneven = fn(p: vec2, pb: vec2, ra: float, rb: float) -> float {
let h = max(dot(pb, pb), 0.0001)
let q0 = vec2(dot(p, vec2(pb.y, -pb.x)), dot(p, pb)) / h
let q = vec2(abs(q0.x), q0.y)
let b = ra - rb
let c = vec2(sqrt(max(h - b * b, 0.0)), b)
let k = c.x * q.y - c.y * q.x
let m = dot(c, q)
let n = dot(q, q)
if k < 0.0 {
return sqrt(h * n) - ra
}
if k > c.x {
return sqrt(h * (n + 1.0 - 2.0 * q.y)) - rb
}
return m - ra
}
let lq_settle_drop = fn(q: vec2, radius: float, s: float, dir: vec2, press: float, sqa: float, sqx: float) -> vec3 {
let big = radius * (1.0 + 0.08 * press)
let m = clamp(s, 0.0, 1.0)
let v = vec2(-dir.y, dir.x)
let rh = big * (1.0 - 0.26 * m)
let sa = max(1.0 - sqa - 0.0065, 0.0)
let sx = max(sqx - 1.0 - 0.36 * 0.0065, 0.0)
let ax = vec2(rh * max(1.0 - sa, 0.3), rh * (1.0 + sx))
let qe = vec2(dot(q, dir), dot(q, v)) / ax
let le = max(length(qe), 0.0001)
var d = (le - 1.0) * min(ax.x, ax.y)
let ge = qe / (ax * le)
var g = normalize(dir * (ge.x + 0.000001) + v * ge.y)
if m > 0.002 {
let rt = big * (1.0 - 0.73 * m)
let ct = -dir * (1.69 * big * m)
let dt = lq_uneven(q, ct, 0.85 * rh, rt)
let cp = ct * clamp(dot(q, ct) / max(dot(ct, ct), 0.0001), 0.0, 1.0)
let gt = normalize(q - cp + vec2(0.0001, 0.0))
let sm = lq_smin(d, dt, 0.7 * big)
let w = clamp((m - 0.002) / 0.438, 0.0, 1.0)
d = mix(d, sm.x, w)
g = normalize(mix(g, normalize(mix(gt, g, sm.y)), w))
}
return vec3(d, g.x, g.y)
}
// The fader: the bench's liquid glass drop ("Liquid glass drop"). A round
// drop of clear glass as long as the cap, 9 points tall, its dome a quarter
// round in from the outline, rides a 5 point slot cut into the ground, the
// accent lit along it from the stop (or from the origin) to the value.
// Through the glass the slot and its fill are seen bent and magnified,
// split a little into their colours toward the rim; the glass takes the
// plain sky's Fresnel reflection outside and off its back, a highlight and
// a soft lens spot, and throws a cool shadow down and to the right with a
// caustic just inside it. The shape, its curve and the material numbers are
// the bench's; the ground, the slot, the accent and the shadow ink are this
// sheet's. Lying down or standing up (`is_vertical`); the light stays above.
mod.widgets.SliderFader.cap_size = 22.0
mod.widgets.SliderFader.track_inset = 8.0
// The cap is a liquid: its tail follows the head on a viscous spring, a press spreads it, a jump sends it travelling.
mod.widgets.SliderFader.cap_viscosity = 0.66
mod.widgets.SliderFader.draw_bg.track_size = uniform(5.0)
mod.widgets.SliderFader.clip_x = false
mod.widgets.SliderFader.clip_y = false
// The material, the bench's numbers, as uniforms so they tune live: the key
// light on the screen (x right, y down, z toward the eye) and its strength;
// the highlight, the roughness (which also frosts the glass), the broad
// sheen and the rim toward grazing; the occlusion in the slot; the plain
// sky's reflection and how close the eye sits over the drop; a glow, off;
// the exposure in stops and the highlights' roll-off; the cast shadow's
// strength (a glass cap lays it at 0.45 of this) and its softest edge, the
// contact shadow and the slot's inner shadow; the drop's height. Metal,
// turned grain and the occlusion's reach up the height are left out: the
// glass shade reads none of them, in the bench as here.
mod.widgets.SliderFader.draw_bg.cap_light = uniform(vec3(-0.3, -0.6, 0.74))
mod.widgets.SliderFader.draw_bg.cap_light_intensity = uniform(0.95)
mod.widgets.SliderFader.draw_bg.cap_specular = uniform(1.0)
mod.widgets.SliderFader.draw_bg.cap_roughness = uniform(0.02)
mod.widgets.SliderFader.draw_bg.cap_gloss = uniform(0.0)
mod.widgets.SliderFader.draw_bg.cap_rim = uniform(1.0)
mod.widgets.SliderFader.draw_bg.cap_ao = uniform(0.46)
mod.widgets.SliderFader.draw_bg.cap_reflection = uniform(0.46)
mod.widgets.SliderFader.draw_bg.cap_reflection_perspective = uniform(0.51)
mod.widgets.SliderFader.draw_bg.cap_glow = uniform(0.0)
mod.widgets.SliderFader.draw_bg.cap_exposure = uniform(0.05)
mod.widgets.SliderFader.draw_bg.cap_rolloff = uniform(0.88)
mod.widgets.SliderFader.draw_bg.cap_shadow = uniform(0.5)
mod.widgets.SliderFader.draw_bg.cap_shadow_blur = uniform(10.0)
mod.widgets.SliderFader.draw_bg.cap_contact = uniform(0.35)
mod.widgets.SliderFader.draw_bg.cap_inner = uniform(0.5)
mod.widgets.SliderFader.draw_bg.cap_height = uniform(9.0)
// The glass: how far (points) its surface bends the view of what lies
// under it and how much it magnifies, its white tint, the share of the bend
// split into the colours, the strength of the lens spot and the caustic,
// and the sky reflected off its outside and off its back. Clear glass, his
// bench spec: no tint, no sheen, no magnifying, a sharp highlight, the rim
// full toward the light, the sky at 0.46.
mod.widgets.SliderFader.draw_bg.cap_glass_depth = uniform(6.0)
mod.widgets.SliderFader.draw_bg.cap_glass_magnify = uniform(0.0)
mod.widgets.SliderFader.draw_bg.cap_glass_tint = uniform(0.0)
mod.widgets.SliderFader.draw_bg.cap_glass_chroma = uniform(0.05)
mod.widgets.SliderFader.draw_bg.cap_glass_caustic = uniform(0.6)
mod.widgets.SliderFader.draw_bg.cap_glass_outer = uniform(1.0)
mod.widgets.SliderFader.draw_bg.cap_glass_inner = uniform(0.6)
// The inks. The ground, the slot (the sheet's well, the ground seven
// hundredths down) and the accent are this sheet's, the fill grey when
// disabled as the sheet's fills turn; the glass is tinted and lit white, as
// the bench's; the shadow ink is the sheet's cool shadow, which darkens the
// ground under the drop about as the bench's ink darkens its own.
mod.widgets.SliderFader.draw_bg.cap_ground = uniform(theme.color_bg_app)
mod.widgets.SliderFader.draw_bg.cap_track_ink = uniform(theme.color_inset_1)
mod.widgets.SliderFader.draw_bg.cap_fill_ink = uniform(theme.color_val)
mod.widgets.SliderFader.draw_bg.cap_fill_disabled = uniform(theme.color_mark_disabled)
mod.widgets.SliderFader.draw_bg.cap_body = uniform(#ffffff)
mod.widgets.SliderFader.draw_bg.cap_light_ink = uniform(theme.color_material_light)
mod.widgets.SliderFader.draw_bg.cap_shadow_ink = uniform(#2a3140)
// The room the fader draws in outside its box, in points, worked out alike
// in `vertex` and `pixel`: the drop past the box (spread 8% under the
// finger; nothing on a fader wider than the drop), the throw of its cast
// shadow along the key light, the blur at the shadow's tip (the engine holds
// it between 0.35 and 1 times the height), a glow's reach when the glass
// emits, and three points for the focus ring and the contact shadow's tail.
let lq_fader_room = fn(across: float, cap: float, inset: float, lgt: vec3, height: float, blur: float, glo: float) -> float {
let ln = normalize(lgt)
let over = max(cap * 0.54 - min(across * 0.5, inset + cap * 0.5), 0.0)
let thrown = height * 0.7 * length(ln.xy) / max(ln.z, 0.25)
let tip = clamp(blur, max(height * 0.35, 1.0), max(height, 1.0))
return over + thrown + tip + cap * 1.6 * step(0.001, glo) + 3.0
}
// The fader laid over the sheet's ground without painting the ground:
// premultiplied, the share of the ground it darkens away (the most any
// channel loses) and the colour put back over what is left. Exact for any
// colour, so the accent keeps its blue where a light-or-dark split would
// turn it grey; the bare ground comes out clear.
let lq_on_ground = fn(col: vec3, gnd: vec3) -> vec4 {
let dk = max(gnd - col, vec3(0.0, 0.0, 0.0)) / max(gnd, vec3(0.01, 0.01, 0.01))
let k = clamp(max(dk.x, max(dk.y, dk.z)), 0.0, 1.0)
return vec4(max(col - gnd * (1.0 - k), vec3(0.0, 0.0, 0.0)), k)
}
// The ground under the fader at `ab`, in the fader's frame: the sheet's
// ground with the slot cut into it and the lit fill from `lo` to `hi`, its
// halo only beside the drop at `at` (half length `hx`). The glass draws it
// again through itself, bent. `dim` scales the slot's shade.
mod.widgets.SliderFader.draw_bg.fader_ground = fn(ab: vec2, tbox: vec4, lo: float, hi: float, at: float, hx: float, lt: vec3, px: float, fill_ink: vec3, dim: float) -> vec3 {
return Cap.slot(self.cap_ground.rgb, ab, tbox, tbox.w, 2.0, lo, hi, at, hx, lt, px, 0.0, 0.0, self.cap_track_ink.rgb, fill_ink, self.cap_light_ink.rgb, self.cap_shadow_ink.rgb, self.cap_inner * dim, self.cap_ao * dim)
}
mod.widgets.SliderFader.draw_bg.vertex = fn() {
let across = mix(self.rect_size.y, self.rect_size.x, self.is_vertical)
let m = lq_fader_room(across, self.cap_px, self.inset_px, self.cap_light, self.cap_height, self.cap_shadow_blur, self.cap_glow)
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 px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let sz = self.rect_size
let v = self.is_vertical
let along = mix(sz.x, sz.y, v)
let across = mix(sz.y, sz.x, v)
let m = lq_fader_room(across, self.cap_px, self.inset_px, self.cap_light, self.cap_height, self.cap_shadow_blur, self.cap_glow)
let p = self.pos * (sz + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
// The fader's frame: `ab.x` along the track from the value's zero (up
// the screen when the fader stands), `ab.y` across it. The track's
// middle lands on a device pixel.
let ab = vec2(mix(p.x, sz.y - p.y, v), mix(p.y, p.x, v))
let ob = mix(self.rect_pos.y, self.rect_pos.x, v)
let mid = Finish.snap(ob + across * 0.5, px) - ob
let inset = self.inset_px
let travel = max(along - inset * 2.0 - self.cap_px, 1.0)
let foot = inset + self.cap_px * 0.5
let at = foot + self.slide_pos * travel
let en = 1.0 - self.disabled
// Disabled, the shadows and the slot's shade drop to 0.35 and the
// glass's light to half, as this sheet dims its disabled parts.
let dim = mix(1.0, 0.35, self.disabled)
let hl = mix(1.0, 0.5, self.disabled)
// Held, the drop spreads 8% under the finger and settles 14% lower, as
// the bench presses it; the pointer over it lifts the glass a little.
let press = self.drag * en
let hov = self.hover * en * (1.0 - self.drag)
let ground = self.cap_ground.rgb
let light_ink = self.cap_light_ink.rgb
let body = self.cap_body.rgb
// The key light on the screen for the glass, and turned into the
// fader's frame for everything laid out along the track.
let l = normalize(self.cap_light)
let lt = Cap.light(self.cap_light, v)
let li = self.cap_light_intensity
// The lit fill draws its core at 1.2 times the ink it is handed; handed
// the accent over 1.2 it shows the accent itself. Disabled, the sheet's
// grey.
let fill_ink = mix(self.cap_fill_ink.rgb, self.cap_fill_disabled.rgb, self.disabled) / 1.2
// The slot, the track's whole length with round ends, and the fill from
// the stop (the track's end) or the origin to the value. Under the glass
// the fill runs on 0.42 of the cap past the drop's centre, so it is seen
// ending inside the drop rather than cut off at its middle, as the bench
// draws it.
let tw = min(self.track_size, across)
let tlen = max(along - inset * 2.0, 1.0)
let tbox = vec4(inset + tlen * 0.5, mid, tlen * 0.5, tw * 0.5)
var lo = foot + self.fill_lo * travel
var hi = foot + self.fill_hi * travel
if self.fill_lo < 0.0005 {
lo = inset
}
if self.fill_hi > 0.9995 {
hi = along - inset
}
var glo = lo
var ghi = hi
if abs(hi - at) < 0.5 {
ghi = hi + self.cap_px * 0.42
}
if abs(lo - at) < 0.5 {
glo = lo - self.cap_px * 0.42
}
// The drop: a circle the cap's length across, spread by the press. Its
// height is the edge curve's quarter round, from the outline in over the
// rest radius. The bench's along and across curves are flat at 1, so
// their reading is (1, 0) everywhere and goes in as it is.
let r = self.cap_px * 0.5
let hs = vec2(r, r)
let rp = r * (1.0 + 0.08 * press)
let height = self.cap_height * (1.0 - 0.14 * press)
let q = ab - vec2(at, mid)
let fp = Cap.sd_drop(q, r, self.cap_stretch, vec2(self.cap_dir, 0.0), press, self.cap_squash_along, self.cap_squash_across)
let cov = Cap.cover(fp.x, px)
var fg = vec2(0.0, 0.0)
var disp = vec2(0.0, 0.0)
if fp.x < px {
let edge_c = Cap.curve(Cap.edge_x(fp.x, hs, 1.0), 2.0, vec2(0.0, 0.0), vec2(0.0, 0.5523), vec2(-0.5523, 0.0), vec2(1.0, 1.0), vec2(0.5523, 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 flat_c = vec2(1.0, 0.0)
// Shape: corner and edge width the whole radius, blend 0.02, the
// height. No ribs, no mark, no cut.
let f = Cap.field(q, hs, fp, flat_c, flat_c, edge_c, flat_c, vec4(1.0, 1.0, 0.02, height), vec4(0.0, 0.0, 0.0, 1.0), vec4(0.0, 0.0, 0.0, 0.0), vec4(0.0, 0.5, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 0.0))
fg = f.yz
// What lies under the glass is seen where its surface bends the
// view, read in the fader's frame.
disp = Cap.glass_disp(q, Cap.normal(fg, height, 0.0), self.cap_glass_depth, self.cap_glass_magnify, cov)
}
var col = self.fader_ground(ab + disp, tbox, glo, ghi, at, r, lt, px, fill_ink, dim)
if cov > 0.001 {
// Its red and its blue are read a little apart along the bend, and
// frosted by the roughness.
let split = Cap.glass_split(disp, hs, self.cap_glass_chroma, self.cap_roughness, cov)
let red = self.fader_ground(ab + split.xy, tbox, glo, ghi, at, r, lt, px, fill_ink, dim)
let blue = self.fader_ground(ab + split.zw, tbox, glo, ghi, at, r, lt, px, fill_ink, dim)
col = Cap.glass_mix(red, col, blue, self.cap_glass_chroma, self.cap_roughness)
}
// The glass and what it throws, worked out only where the drop or its
// shadow can reach.
var cap = col
var shd = 0.0
var caus = vec3(0.0, 0.0, 0.0)
var halo = 0.0
if length(q) - rp < m {
let rise = height
let n = Cap.normal(fg, height, v)
let eye = Cap.view(q, hs, self.cap_reflection_perspective, v)
let spot = Cap.lens(q, hs, lt, rise, self.cap_glass_depth, li, self.cap_glass_caustic) * hl
cap = Cap.glass_shade(n, l, eye, col, body, self.cap_glass_tint, self.cap_specular * hl, self.cap_roughness, self.cap_gloss * hl, self.cap_rim * hl, li, self.cap_reflection, self.cap_glass_outer, self.cap_glass_inner, spot, 0.0, 0.0, light_ink, light_ink, ground)
// The lit fill still showing beside the drop lights the side that
// faces it; the pointer lifts the glass as it lifts every glass face
// on this sheet.
cap = cap + fill_ink * Cap.fill_light(q, hs, fg * height, lo, hi, at, tw) * en
cap = cap + vec3(0.03, 0.03, 0.03) * hov
// Under it: the cast shadow, thrown from the top outline the bench
// gives for this drop (a glass one at 0.45 of the shadow), the
// contact shadow round its foot, the caustic just inside the shadow
// on the side away from the light, and the glow if the glass emits.
// The cast shadow, the caustic and the lens keep the rest size, as
// the bench throws them: held, the spread drop covers the shadow's
// foot and the lower rise draws it in, while the contact shadow
// hugs the spread outline.
let top = vec3(0.6902, 0.6902, 1.0)
let thrown = Cap.shadow(q, hs, r, top, lt, rise, self.cap_shadow_blur, self.cap_shadow * 0.45 * dim, px)
let touch = Cap.contact(fp.x, max(rise * 0.3, 1.0), self.cap_contact * dim)
shd = clamp(thrown + touch, 0.0, 1.0)
caus = light_ink * Cap.caustic(q, hs, r, top, lt, rise, self.cap_shadow_blur, px, self.cap_glass_chroma, self.cap_glass_depth, self.cap_roughness, li, self.cap_glass_caustic) * dim
halo = Cap.glow(fp.x, hs, self.cap_glow)
}
let outside = 1.0 - cov
col = mix(col, col * self.cap_shadow_ink.rgb, shd * outside)
col = col + caus * outside + body * halo * 0.9 * outside
col = mix(col, cap, cov)
// Toned, then laid over the ground without painting it, with the
// sheet's focus ring a point and a half outside the drop.
let ev = pow(2.0, self.cap_exposure)
let res = lq_on_ground(Cap.tone(col, ev, self.cap_rolloff), Cap.tone(ground, ev, self.cap_rolloff))
return Finish.over(res, lq_focus(fp.x, px, self.focus * en))
}
// THE DROP on a horizontal face: the fader's liquid glass drop (FADER
// below), its material read from the face's own uniforms, laid over the
// face's track seen through the glass. `ab` is the point in the face's
// frame (x along the track, y down), `c` the drop's centre, `tbox` the
// track's box (centre, half size) for the slot and the fill's light, `cap`
// the face's own extra geometry handed on to `lq_under`, `f` the lit fill
// from `f.x` to `f.y` along the track, `m` the room past the box, `ring`
// 1 to put the sheet's focus ring round the drop and 0 to leave the focus
// to the face, `hot` the share of the press that spreads this drop (1 on a
// face with one drop), `settle` 1 for the settling drop the toggles and the
// switches carry (THE SETTLING DROP above) and 0 for the engine's drop the
// sliders carry. What lies under the glass is read through
// `self.lq_under`, which every face defines for its own track; the whole
// is laid over the ground without painting it.
mod.widgets.SliderMinimal.draw_bg.lq_drop = fn(ab: vec2, c: vec2, tbox: vec4, cap: vec4, f: vec2, px: float, m: float, ring: float, hot: float, settle: float) -> vec4 {
let en = 1.0 - self.disabled
let dim = mix(1.0, 0.35, self.disabled)
let hl = mix(1.0, 0.5, self.disabled)
let press = max(self.drag, self.cap_press) * en * hot
let hov = self.hover * en * (1.0 - self.drag)
let ground = self.cap_ground.rgb
let light_ink = self.cap_light_ink.rgb
let body = self.cap_body.rgb
let l = normalize(self.cap_light)
let lt = Cap.light(self.cap_light, 0.0)
let li = self.cap_light_intensity
let fill_ink = mix(self.cap_fill_ink.rgb, self.cap_fill_disabled.rgb, self.disabled) * 0.5
let at = c.x
let tw = tbox.w * 2.0
// Under the glass the fill runs on 0.42 of the cap past the drop's
// centre, so it is seen ending inside the drop and not cut at its middle.
var glo = f.x
var ghi = f.y
if abs(f.y - at) < 0.5 {
ghi = f.y + self.cap_px * 0.42
}
if abs(f.x - at) < 0.5 {
glo = f.x - self.cap_px * 0.42
}
let r = self.cap_px * 0.5
let hs = vec2(r, r)
let rp = r * (1.0 + 0.08 * press)
let height = self.cap_height * (1.0 - 0.14 * press)
let q = ab - c
var fp = Cap.sd_drop(q, r, self.cap_stretch, vec2(self.cap_dir, 0.0), press, self.cap_squash_along, self.cap_squash_across)
if settle > 0.5 {
fp = lq_settle_drop(q, r, self.cap_stretch, vec2(self.cap_dir, 0.0), press, self.cap_squash_along, self.cap_squash_across)
}
let cov = Cap.cover(fp.x, px)
var fg = vec2(0.0, 0.0)
var disp = vec2(0.0, 0.0)
if fp.x < px {
// the edge: its curve from the rim in over `cap_edge_width` of the
// radius (the three handles the bench exports), blended by
// `cap_blend`
let edge_c = Cap.curve(Cap.edge_x(fp.x, hs, self.cap_edge_width), 2.0, vec2(0.0, 0.0), self.cap_edge_out0, self.cap_edge_in1, vec2(1.0, 1.0), self.cap_edge_out1, 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 flat_c = vec2(1.0, 0.0)
let fld = Cap.field(q, hs, fp, flat_c, flat_c, edge_c, flat_c, vec4(1.0, self.cap_edge_width, self.cap_blend, height), vec4(0.0, 0.0, 0.0, 1.0), vec4(0.0, 0.0, 0.0, 0.0), vec4(0.0, 0.5, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 0.0))
fg = fld.yz
disp = Cap.glass_disp(q, Cap.normal(fg, height, 0.0), self.cap_glass_depth, self.cap_glass_magnify, cov)
}
var col = self.lq_under(ab + disp, tbox, cap, vec2(glo, ghi))
if cov > 0.001 {
let split = Cap.glass_split(disp, hs, self.cap_glass_chroma, self.cap_roughness, cov)
let red = self.lq_under(ab + split.xy, tbox, cap, vec2(glo, ghi))
let blue = self.lq_under(ab + split.zw, tbox, cap, vec2(glo, ghi))
col = Cap.glass_mix(red, col, blue, self.cap_glass_chroma, self.cap_roughness)
}
var capc = col
var shd = 0.0
var caus = vec3(0.0, 0.0, 0.0)
var halo = 0.0
if length(q) - rp < m {
let rise = height
let n = Cap.normal(fg, height, 0.0)
let eye = Cap.view(q, hs, self.cap_reflection_perspective, 0.0)
let spot = Cap.lens(q, hs, lt, rise, self.cap_glass_depth, li, self.cap_glass_caustic) * hl
capc = Cap.glass_shade(n, l, eye, col, body, self.cap_glass_tint, self.cap_specular * hl, self.cap_roughness, self.cap_gloss * hl, self.cap_rim * hl, li, self.cap_reflection, self.cap_glass_outer, self.cap_glass_inner, spot, 0.0, 0.0, light_ink, light_ink, ground)
capc = capc + fill_ink * Cap.fill_light(q, hs, fg * height, f.x, f.y, at, tw) * en
capc = capc + vec3(0.03, 0.03, 0.03) * hov
let top = vec3(0.6902, 0.6902, 1.0)
let thrown = Cap.shadow(q, hs, r, top, lt, rise, self.cap_shadow_blur, self.cap_shadow * 0.45 * dim, px)
let touch = Cap.contact(fp.x, max(rise * 0.3, 1.0), self.cap_contact * dim)
shd = clamp(thrown + touch, 0.0, 1.0)
caus = light_ink * Cap.caustic(q, hs, r, top, lt, rise, self.cap_shadow_blur, px, self.cap_glass_chroma, self.cap_glass_depth, self.cap_roughness, li, self.cap_glass_caustic) * dim
halo = Cap.glow(fp.x, hs, self.cap_glow)
}
let outside = 1.0 - cov
col = mix(col, col * self.cap_shadow_ink.rgb, shd * outside)
col = col + caus * outside + body * halo * 0.9 * outside
col = mix(col, capc, cov)
let ev = pow(2.0, self.cap_exposure)
let res = lq_on_ground(Cap.tone(col, ev, self.cap_rolloff), Cap.tone(ground, ev, self.cap_rolloff))
return Finish.over(res, lq_focus(fp.x, px, self.focus * en * ring))
}
// The material and the inks of the liquid glass drop on this face: the
// fader's numbers (FADER below says what each does), the drop's height
// and its size by the face.
mod.widgets.SliderMinimal.cap_size = 14.0
mod.widgets.SliderMinimal.track_inset = 0.0
mod.widgets.SliderMinimal.cap_viscosity = 0.66
mod.widgets.SliderMinimal.draw_bg.cap_light = uniform(vec3(-0.3, -0.6, 0.74))
mod.widgets.SliderMinimal.draw_bg.cap_light_intensity = uniform(0.95)
mod.widgets.SliderMinimal.draw_bg.cap_specular = uniform(1.0)
mod.widgets.SliderMinimal.draw_bg.cap_roughness = uniform(0.02)
mod.widgets.SliderMinimal.draw_bg.cap_gloss = uniform(0.0)
mod.widgets.SliderMinimal.draw_bg.cap_rim = uniform(1.0)
mod.widgets.SliderMinimal.draw_bg.cap_ao = uniform(0.46)
mod.widgets.SliderMinimal.draw_bg.cap_reflection = uniform(0.46)
mod.widgets.SliderMinimal.draw_bg.cap_reflection_perspective = uniform(0.51)
mod.widgets.SliderMinimal.draw_bg.cap_glow = uniform(0.0)
mod.widgets.SliderMinimal.draw_bg.cap_exposure = uniform(0.05)
mod.widgets.SliderMinimal.draw_bg.cap_rolloff = uniform(0.88)
mod.widgets.SliderMinimal.draw_bg.cap_shadow = uniform(0.5)
mod.widgets.SliderMinimal.draw_bg.cap_shadow_blur = uniform(10.0)
mod.widgets.SliderMinimal.draw_bg.cap_contact = uniform(0.35)
mod.widgets.SliderMinimal.draw_bg.cap_inner = uniform(0.5)
mod.widgets.SliderMinimal.draw_bg.cap_edge_width = uniform(1.0)
mod.widgets.SliderMinimal.draw_bg.cap_blend = uniform(0.02)
mod.widgets.SliderMinimal.draw_bg.cap_edge_out0 = uniform(vec2(0.0, 0.5523))
mod.widgets.SliderMinimal.draw_bg.cap_edge_in1 = uniform(vec2(-0.5523, 0.0))
mod.widgets.SliderMinimal.draw_bg.cap_edge_out1 = uniform(vec2(0.5523, 0.0))
mod.widgets.SliderMinimal.draw_bg.cap_height = uniform(5.7)
mod.widgets.SliderMinimal.draw_bg.cap_glass_depth = uniform(3.8)
mod.widgets.SliderMinimal.draw_bg.cap_glass_magnify = uniform(0.0)
mod.widgets.SliderMinimal.draw_bg.cap_glass_tint = uniform(0.0)
mod.widgets.SliderMinimal.draw_bg.cap_glass_chroma = uniform(0.05)
mod.widgets.SliderMinimal.draw_bg.cap_glass_caustic = uniform(0.6)
mod.widgets.SliderMinimal.draw_bg.cap_glass_outer = uniform(1.0)
mod.widgets.SliderMinimal.draw_bg.cap_glass_inner = uniform(0.6)
mod.widgets.SliderMinimal.draw_bg.cap_ground = uniform(theme.color_bg_app)
mod.widgets.SliderMinimal.draw_bg.cap_fill_ink = uniform(theme.color_val)
mod.widgets.SliderMinimal.draw_bg.cap_fill_disabled = uniform(theme.color_mark_disabled)
mod.widgets.SliderMinimal.draw_bg.cap_body = uniform(#ffffff)
mod.widgets.SliderMinimal.draw_bg.cap_light_ink = uniform(theme.color_material_light)
mod.widgets.SliderMinimal.draw_bg.cap_shadow_ink = uniform(#2a3140)
// The room the drop and its shadow need past the box, the same in vertex
// and pixel.
mod.widgets.SliderMinimal.draw_bg.vertex = fn() {
let m = lq_fader_room(self.rect_size.y, self.cap_px, self.inset_px, self.cap_light, self.cap_height, self.cap_shadow_blur, self.cap_glow)
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 material and the inks of the liquid glass drop on this face: the
// fader's numbers (FADER below says what each does), the drop's height
// and its size by the face.
mod.widgets.SliderFlat.cap_size = 18.0
mod.widgets.SliderFlat.track_inset = 0.0
mod.widgets.SliderFlat.cap_viscosity = 0.66
mod.widgets.SliderFlat.draw_bg.cap_light = uniform(vec3(-0.3, -0.6, 0.74))
mod.widgets.SliderFlat.draw_bg.cap_light_intensity = uniform(0.95)
mod.widgets.SliderFlat.draw_bg.cap_specular = uniform(1.0)
mod.widgets.SliderFlat.draw_bg.cap_roughness = uniform(0.02)
mod.widgets.SliderFlat.draw_bg.cap_gloss = uniform(0.0)
mod.widgets.SliderFlat.draw_bg.cap_rim = uniform(1.0)
mod.widgets.SliderFlat.draw_bg.cap_ao = uniform(0.46)
mod.widgets.SliderFlat.draw_bg.cap_reflection = uniform(0.46)
mod.widgets.SliderFlat.draw_bg.cap_reflection_perspective = uniform(0.51)
mod.widgets.SliderFlat.draw_bg.cap_glow = uniform(0.0)
mod.widgets.SliderFlat.draw_bg.cap_exposure = uniform(0.05)
mod.widgets.SliderFlat.draw_bg.cap_rolloff = uniform(0.88)
mod.widgets.SliderFlat.draw_bg.cap_shadow = uniform(0.5)
mod.widgets.SliderFlat.draw_bg.cap_shadow_blur = uniform(10.0)
mod.widgets.SliderFlat.draw_bg.cap_contact = uniform(0.35)
mod.widgets.SliderFlat.draw_bg.cap_inner = uniform(0.5)
mod.widgets.SliderFlat.draw_bg.cap_edge_width = uniform(1.0)
mod.widgets.SliderFlat.draw_bg.cap_blend = uniform(0.02)
mod.widgets.SliderFlat.draw_bg.cap_edge_out0 = uniform(vec2(0.0, 0.5523))
mod.widgets.SliderFlat.draw_bg.cap_edge_in1 = uniform(vec2(-0.5523, 0.0))
mod.widgets.SliderFlat.draw_bg.cap_edge_out1 = uniform(vec2(0.5523, 0.0))
mod.widgets.SliderFlat.draw_bg.cap_height = uniform(7.4)
mod.widgets.SliderFlat.draw_bg.cap_glass_depth = uniform(4.9)
mod.widgets.SliderFlat.draw_bg.cap_glass_magnify = uniform(0.0)
mod.widgets.SliderFlat.draw_bg.cap_glass_tint = uniform(0.0)
mod.widgets.SliderFlat.draw_bg.cap_glass_chroma = uniform(0.05)
mod.widgets.SliderFlat.draw_bg.cap_glass_caustic = uniform(0.6)
mod.widgets.SliderFlat.draw_bg.cap_glass_outer = uniform(1.0)
mod.widgets.SliderFlat.draw_bg.cap_glass_inner = uniform(0.6)
mod.widgets.SliderFlat.draw_bg.cap_ground = uniform(theme.color_bg_app)
mod.widgets.SliderFlat.draw_bg.cap_fill_ink = uniform(theme.color_val)
mod.widgets.SliderFlat.draw_bg.cap_fill_disabled = uniform(theme.color_mark_disabled)
mod.widgets.SliderFlat.draw_bg.cap_body = uniform(#ffffff)
mod.widgets.SliderFlat.draw_bg.cap_light_ink = uniform(theme.color_material_light)
mod.widgets.SliderFlat.draw_bg.cap_shadow_ink = uniform(#2a3140)
// The room the drop and its shadow need past the box, the same in vertex
// and pixel.
mod.widgets.SliderFlat.draw_bg.vertex = fn() {
let m = lq_fader_room(self.rect_size.y, self.cap_px, self.inset_px, self.cap_light, self.cap_height, self.cap_shadow_blur, self.cap_glow)
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 material and the inks of the liquid glass drop on this face: the
// fader's numbers (FADER below says what each does), the drop's height
// and its size by the face.
mod.widgets.SliderRoundFlat.cap_size = 16.0
mod.widgets.SliderRoundFlat.track_inset = 8.0
mod.widgets.SliderRoundFlat.cap_viscosity = 0.66
mod.widgets.SliderRoundFlat.draw_bg.cap_light = uniform(vec3(-0.3, -0.6, 0.74))
mod.widgets.SliderRoundFlat.draw_bg.cap_light_intensity = uniform(0.95)
mod.widgets.SliderRoundFlat.draw_bg.cap_specular = uniform(1.0)
mod.widgets.SliderRoundFlat.draw_bg.cap_roughness = uniform(0.02)
mod.widgets.SliderRoundFlat.draw_bg.cap_gloss = uniform(0.0)
mod.widgets.SliderRoundFlat.draw_bg.cap_rim = uniform(1.0)
mod.widgets.SliderRoundFlat.draw_bg.cap_ao = uniform(0.46)
mod.widgets.SliderRoundFlat.draw_bg.cap_reflection = uniform(0.46)
mod.widgets.SliderRoundFlat.draw_bg.cap_reflection_perspective = uniform(0.51)
mod.widgets.SliderRoundFlat.draw_bg.cap_glow = uniform(0.0)
mod.widgets.SliderRoundFlat.draw_bg.cap_exposure = uniform(0.05)
mod.widgets.SliderRoundFlat.draw_bg.cap_rolloff = uniform(0.88)
mod.widgets.SliderRoundFlat.draw_bg.cap_shadow = uniform(0.5)
mod.widgets.SliderRoundFlat.draw_bg.cap_shadow_blur = uniform(10.0)
mod.widgets.SliderRoundFlat.draw_bg.cap_contact = uniform(0.35)
mod.widgets.SliderRoundFlat.draw_bg.cap_inner = uniform(0.5)
mod.widgets.SliderRoundFlat.draw_bg.cap_edge_width = uniform(1.0)
mod.widgets.SliderRoundFlat.draw_bg.cap_blend = uniform(0.02)
mod.widgets.SliderRoundFlat.draw_bg.cap_edge_out0 = uniform(vec2(0.0, 0.5523))
mod.widgets.SliderRoundFlat.draw_bg.cap_edge_in1 = uniform(vec2(-0.5523, 0.0))
mod.widgets.SliderRoundFlat.draw_bg.cap_edge_out1 = uniform(vec2(0.5523, 0.0))
mod.widgets.SliderRoundFlat.draw_bg.cap_height = uniform(6.5)
mod.widgets.SliderRoundFlat.draw_bg.cap_glass_depth = uniform(4.4)
mod.widgets.SliderRoundFlat.draw_bg.cap_glass_magnify = uniform(0.0)
mod.widgets.SliderRoundFlat.draw_bg.cap_glass_tint = uniform(0.0)
mod.widgets.SliderRoundFlat.draw_bg.cap_glass_chroma = uniform(0.05)
mod.widgets.SliderRoundFlat.draw_bg.cap_glass_caustic = uniform(0.6)
mod.widgets.SliderRoundFlat.draw_bg.cap_glass_outer = uniform(1.0)
mod.widgets.SliderRoundFlat.draw_bg.cap_glass_inner = uniform(0.6)
mod.widgets.SliderRoundFlat.draw_bg.cap_ground = uniform(theme.color_bg_app)
mod.widgets.SliderRoundFlat.draw_bg.cap_fill_ink = uniform(theme.color_val)
mod.widgets.SliderRoundFlat.draw_bg.cap_fill_disabled = uniform(theme.color_mark_disabled)
mod.widgets.SliderRoundFlat.draw_bg.cap_body = uniform(#ffffff)
mod.widgets.SliderRoundFlat.draw_bg.cap_light_ink = uniform(theme.color_material_light)
mod.widgets.SliderRoundFlat.draw_bg.cap_shadow_ink = uniform(#2a3140)
// The room the drop and its shadow need past the box, the same in vertex
// and pixel.
mod.widgets.SliderRoundFlat.draw_bg.vertex = fn() {
let m = lq_fader_room(self.rect_size.y, self.cap_px, self.inset_px, self.cap_light, self.cap_height, self.cap_shadow_blur, self.cap_glow)
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 bar slider: a 6 point well under the label row, the accent fill from
// the stop (or from the default) to the handle, and the liquid glass drop
// (18 points, with the widget's liquid motion) through which the well and
// its fill show bent and magnified.
mod.widgets.SliderFlat.draw_bg.lq_under = fn(ab: vec2, tbox: vec4, cap: vec4, f: vec2) -> vec3 {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let dis = mix(1.0, 0.35, self.disabled)
let fcol = mix(lq_accent(), vec3(0.643, 0.651, 0.671), self.disabled)
let acc = lq_bar_track(ab, tbox.xy, tbox.zw, px, f.x, f.y, fcol, dis)
return Finish.over(vec4(self.cap_ground.rgb, 1.0), acc).rgb
}
mod.widgets.SliderFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = lq_fader_room(self.rect_size.y, self.cap_px, self.inset_px, self.cap_light, self.cap_height, self.cap_shadow_blur, self.cap_glow)
let pq = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let org = self.rect_pos
let top = 20.0
let cy = Finish.snap(org.y + top + (self.rect_size.y - top) * 0.5, px) - org.y
// The drop's centre runs from half a cap in to half a cap from the end,
// as the widget's travel has it.
let r = self.cap_px * 0.5
let x0 = self.inset_px + r
let x1 = self.rect_size.x - self.inset_px - r
let hx = mix(x0, x1, self.slide_pos)
let gc = vec2(self.rect_size.x * 0.5, cy)
let gh = vec2(self.rect_size.x * 0.5, 3.0)
let lo = min(self.origin_pos, self.slide_pos)
let hi = max(self.origin_pos, self.slide_pos)
let from = mix(0.0, lo, self.arc_origin)
let to = mix(self.slide_pos, hi, self.arc_origin)
let f0 = mix(gc.x - gh.x, mix(x0, x1, from), step(0.0001, from))
let f1 = mix(x0, x1, to)
let kc = vec2(Finish.snap(org.x + hx, px) - org.x, cy)
return self.lq_drop(pq, kc, vec4(gc, gh), vec4(0.0, 0.0, 0.0, 0.0), vec2(f0, f1), px, m, 1.0, 1.0, 0.0)
}
// The minimal slider: an engraved line, dark over light, a 2 point fill
// and the liquid glass drop (14 points, with the widget's liquid motion).
mod.widgets.SliderMinimal.draw_bg.lq_under = fn(ab: vec2, tbox: vec4, cap: vec4, f: vec2) -> vec3 {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let dis = mix(1.0, 0.35, self.disabled)
let fcol = mix(lq_accent(), vec3(0.643, 0.651, 0.671), self.disabled)
let acc = lq_line_track(ab, tbox.z * 2.0, tbox.y, px, f.x, f.y, fcol, dis)
return Finish.over(vec4(self.cap_ground.rgb, 1.0), acc).rgb
}
mod.widgets.SliderMinimal.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = lq_fader_room(self.rect_size.y, self.cap_px, self.inset_px, self.cap_light, self.cap_height, self.cap_shadow_blur, self.cap_glow)
let pq = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let org = self.rect_pos
let top = self.offset_y
let cy = Finish.snap(org.y + top + (self.rect_size.y - top) * 0.5, px) - org.y
let r = self.cap_px * 0.5
let x0 = self.inset_px + r
let x1 = self.rect_size.x - self.inset_px - r
let lo = min(self.origin_pos, self.slide_pos)
let hi = max(self.origin_pos, self.slide_pos)
let from = mix(0.0, lo, self.arc_origin)
let to = mix(self.slide_pos, hi, self.arc_origin)
let fx0 = mix(x0, x1, from)
let fx1 = mix(x0, x1, to)
let hx = mix(x0, x1, self.slide_pos)
let kc = vec2(Finish.snap(org.x + hx, px) - org.x, cy)
return self.lq_drop(pq, kc, vec4(self.rect_size.x * 0.5, cy, self.rect_size.x * 0.5, 1.0), vec4(0.0, 0.0, 0.0, 0.0), vec2(fx0, fx1), px, m, 1.0, 1.0, 0.0)
}
// The round slider is the readout slider: a glass capsule beside its label
// holding a 6 point well, the fill up to the liquid glass drop (16 points,
// with the widget's liquid motion) riding the capsule.
mod.widgets.SliderRoundFlat.draw_bg.lq_under = fn(ab: vec2, tbox: vec4, cap: vec4, f: vec2) -> vec3 {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let en = 1.0 - self.disabled
let dis = mix(1.0, 0.35, self.disabled)
let sc = cap.xy
let sh = cap.zw
let hgt = sh.y * 2.0
var acc = lq_cast(ab, sc, sh, sh.y, px, hgt, 0.26 * dis, 1.0, vec3(0.165, 0.192, 0.251), vec3(1.0, 1.0, 1.0), 0.3 * en)
acc = Finish.over(acc, lq_glass_body(ab, sc, sh, sh.y, px, hgt, vec3(0.0, 0.0, 0.0), 0.0, 0.02 * self.hover, 1.0, 0.88, mix(1.0, 0.5, self.disabled), 1.0, 0.06 * hgt + px, 1.5, vec4(0.682, 0.690, 0.714, 0.9), vec3(0.965, 0.973, 0.988)))
acc = Finish.over(acc, lq_well(ab, tbox.xy, tbox.zw, 3.0, px, vec3(0.737, 0.749, 0.773), dis))
acc = lq_fill(acc, ab, tbox.xy, tbox.zw, px, f.x, f.y, mix(lq_accent(), vec3(0.643, 0.651, 0.671), self.disabled), 0.8)
return Finish.over(vec4(self.cap_ground.rgb, 1.0), acc).rgb
}
mod.widgets.SliderRoundFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = lq_fader_room(self.rect_size.y, self.cap_px, self.inset_px, self.cap_light, self.cap_height, self.cap_shadow_blur, self.cap_glow)
let pq = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let org = self.rect_pos
let sx0 = self.label_size
let sx1 = self.rect_size.x - 40.0
let hgt = min(self.rect_size.y, 18.0)
let b = lq_snap(org, vec2(sx0, self.rect_size.y * 0.5 - hgt * 0.5), vec2(sx1, self.rect_size.y * 0.5 + hgt * 0.5), px)
let sc = (b.xy + b.zw) * 0.5
let sh = (b.zw - b.xy) * 0.5
let wc = vec2(sc.x, sc.y)
let wh = vec2(sh.x - 6.0, 3.0)
let x0 = wc.x - wh.x + 2.0
let x1 = wc.x + wh.x - 2.0
let hx = mix(x0, x1, self.slide_pos)
let kc = vec2(Finish.snap(org.x + hx, px) - org.x, sc.y)
return self.lq_drop(pq, kc, vec4(wc, wh), vec4(sc, sh), vec2(wc.x - wh.x, hx), px, m, 1.0, 1.0, 0.0)
}
// THE TOGGLE'S DROP. The knob is the sliders' liquid glass drop: the
// material under the same names as on the faders, the motion's five values
// as uniforms the widget writes, the knob's followed position beside them,
// and the drop pipeline itself handed over from the minimal face, drawn
// with the settling outline (THE SETTLING DROP) so the knob lands without
// a swell. The
// rocker and the slide switch carry the same drop (SWITCHES below), so the
// material is laid on each one's draw from here.
let lq_toggle_drop = fn(d) {
d.cap_px = uniform(22.0)
d.cap_stretch = uniform(0.0)
d.cap_dir = uniform(1.0)
d.cap_press = uniform(0.0)
d.cap_squash_along = uniform(1.0)
d.cap_squash_across = uniform(1.0)
d.knob_t = uniform(0.0)
d.knob_follow = uniform(0.0)
d.cap_light = uniform(vec3(-0.3, -0.6, 0.74))
d.cap_light_intensity = uniform(1.25)
d.cap_specular = uniform(1.0)
d.cap_roughness = uniform(0.02)
d.cap_gloss = uniform(0.0)
d.cap_rim = uniform(1.0)
d.cap_ao = uniform(0.46)
d.cap_reflection = uniform(0.94)
d.cap_reflection_perspective = uniform(0.61)
d.cap_glow = uniform(0.0)
d.cap_exposure = uniform(0.2)
d.cap_rolloff = uniform(0.88)
d.cap_shadow = uniform(0.5)
d.cap_shadow_blur = uniform(10.0)
d.cap_contact = uniform(0.35)
d.cap_inner = uniform(0.5)
d.cap_edge_width = uniform(0.74)
d.cap_blend = uniform(0.175)
d.cap_edge_out0 = uniform(vec2(0.0062, -0.0167))
d.cap_edge_in1 = uniform(vec2(-0.9938, 0.0))
d.cap_edge_out1 = uniform(vec2(0.9938, 0.0))
d.cap_height = uniform(4.8)
d.cap_glass_depth = uniform(2.75)
d.cap_glass_magnify = uniform(0.0)
d.cap_glass_tint = uniform(0.0)
d.cap_glass_chroma = uniform(0.32)
d.cap_glass_caustic = uniform(0.0)
d.cap_glass_outer = uniform(1.0)
d.cap_glass_inner = uniform(0.6)
d.cap_ground = uniform(theme.color_bg_app)
d.cap_fill_ink = uniform(theme.color_val)
d.cap_fill_disabled = uniform(theme.color_mark_disabled)
d.cap_body = uniform(#ffffff)
d.cap_light_ink = uniform(theme.color_material_light)
d.cap_shadow_ink = uniform(#2a3140)
}
mod.widgets.ToggleFlat.cap_viscosity = 0.66
lq_toggle_drop(mod.widgets.ToggleFlat.draw_bg)
mod.widgets.ToggleFlat.draw_bg.lq_drop = mod.widgets.SliderMinimal.draw_bg.lq_drop
// What lies under the knob: the track, a channel cut into the ground
// (the sheet's well), filled with the accent as far as it is on, red in
// error, lifted a little under the pointer. `cap` is the track's box
// (centre, half size).
mod.widgets.ToggleFlat.draw_bg.lq_under = fn(ab: vec2, tbox: vec4, cap: vec4, f: vec2) -> vec3 {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let en = 1.0 - self.disabled
let dis = mix(1.0, 0.35, self.disabled)
let hov = self.hover * en * 0.04
var acc = lq_well(ab, cap.xy, cap.zw, cap.w, px, vec3(0.737, 0.749, 0.773) + vec3(hov, hov, hov), dis)
let fcol = mix(mix(lq_accent(), vec3(0.643, 0.651, 0.671), self.disabled), lq_red(), self.error)
acc = lq_fill(acc, ab, cap.xy, cap.zw, px, cap.x - cap.z, cap.x + cap.z, fcol, 0.8 * self.active)
return Finish.over(vec4(self.cap_ground.rgb, 1.0), acc).rgb
}
mod.widgets.ToggleFlat.draw_bg.vertex = fn() {
let m = lq_fader_room(self.size, self.cap_px, 0.0, self.cap_light, self.cap_height, self.cap_shadow_blur, self.cap_glow)
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
mod.widgets.ToggleFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = lq_fader_room(self.size, self.cap_px, 0.0, self.cap_light, self.cap_height, self.cap_shadow_blur, self.cap_glow)
let pq = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let org = self.rect_pos
let th = self.size
let keep = 0.0 * (self.border_size + self.knob_inset)
let t = mix(mix(self.active, self.drag_pos, self.drag), self.knob_t, self.knob_follow) + keep
let w = th * self.pill_aspect
let x0 = (self.rect_size.x - w) * self.mark_at_end
let b = lq_snap(org, vec2(x0, self.rect_size.y * 0.5 - th * 0.5), vec2(x0 + w, self.rect_size.y * 0.5 + th * 0.5), px)
let out = lq_air_switch(pq, b.xy, b.zw, px, t, self.hover * (1.0 - self.disabled), self.disabled, self.error, self.cap_ground.rgb)
let tc = (b.xy + b.zw) * 0.5
let thh = (b.zw - b.xy) * 0.5
let td = Material.sd_box(pq, tc, thh, thh.y)
return Finish.over(out, lq_focus(td, px, self.focus * (1.0 - self.disabled)))
}
// SWITCHES. The rocker and the slide switch are the toggle with its liquid
// glass drop for their moving part, riding the toggle's channel (the
// sheet's well, as long and as tall as the toggle's, filled with the
// accent as far as they are on, red in error): the rocker's drop runs
// between an "O" in the off end and an "I" in the on end, the slide's
// leaves its lamp in the end it has left, a white dot while on and an open
// ring while off. What lies under the drop is seen through it, bent, as on
// the toggle; the legends are dark on the bare channel and white on the
// fill. The rocker flips on a click and does not drag; the slide follows a
// drag. Both move with the toggle's viscosity.
mod.widgets.ToggleRocker.cap_viscosity = 0.66
mod.widgets.ToggleSlide.cap_viscosity = 0.66
mod.widgets.ToggleRocker.clip_x = false
mod.widgets.ToggleRocker.clip_y = false
mod.widgets.ToggleSlide.clip_x = false
mod.widgets.ToggleSlide.clip_y = false
lq_toggle_drop(mod.widgets.ToggleRocker.draw_bg)
lq_toggle_drop(mod.widgets.ToggleSlide.draw_bg)
mod.widgets.ToggleRocker.draw_bg.lq_drop = mod.widgets.SliderMinimal.draw_bg.lq_drop
mod.widgets.ToggleSlide.draw_bg.lq_drop = mod.widgets.SliderMinimal.draw_bg.lq_drop
// The toggle's channel and the toggle's room round it, so the label
// stands as far from the drop; the rocker's border and knob inset the
// toggle's, so the widget's travel is the drawn one.
mod.widgets.ToggleRocker.draw_bg.size = uniform(20.0)
mod.widgets.ToggleRocker.draw_bg.pill_aspect = uniform(2.06)
mod.widgets.ToggleRocker.draw_bg.border_size = uniform(1.0)
mod.widgets.ToggleRocker.draw_bg.knob_inset = uniform(1.0)
mod.widgets.ToggleSlide.draw_bg.size = uniform(20.0)
mod.widgets.ToggleSlide.draw_bg.pill_aspect = uniform(2.06)
mod.widgets.ToggleRocker.padding = mod.turtle.Inset{left: 20.0 right: 20.0 top: 6.0 bottom: 6.0}
mod.widgets.ToggleSlide.padding = mod.turtle.Inset{left: 20.0 right: 20.0 top: 6.0 bottom: 6.0}
mod.widgets.ToggleRocker.label_walk.margin.left = 26.0
mod.widgets.ToggleSlide.label_walk.margin.left = 26.0
// The rocker's channel: the toggle's, and in it the legends, each in the
// middle of the place the drop rests at that end: an "O" ring in the off
// end, an "I" bar in the on end. `cap` is the channel's box (centre, half
// size).
mod.widgets.ToggleRocker.draw_bg.lq_under = fn(ab: vec2, tbox: vec4, cap: vec4, f: vec2) -> vec3 {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let en = 1.0 - self.disabled
let dis = mix(1.0, 0.35, self.disabled)
let hov = self.hover * en * 0.04
var acc = lq_well(ab, cap.xy, cap.zw, cap.w, px, vec3(0.737, 0.749, 0.773) + vec3(hov, hov, hov), dis)
let fcol = mix(mix(lq_accent(), vec3(0.643, 0.651, 0.671), self.disabled), lq_red(), self.error)
acc = lq_fill(acc, ab, cap.xy, cap.zw, px, cap.x - cap.z, cap.x + cap.z, fcol, 0.8 * self.active)
let at_o = vec2(cap.x - cap.z + cap.w + 1.0, cap.y)
let at_i = vec2(cap.x + cap.z - cap.w - 1.0, cap.y)
let ring = Finish.cover(abs(length(ab - at_o) - cap.w * 0.34) - 0.55, px)
let bar = Finish.cover(max(abs(ab.x - at_i.x) - 0.55, abs(ab.y - at_i.y) - cap.w * 0.4), px)
let la = clamp(ring + bar, 0.0, 1.0) * mix(0.8, 0.95, self.active) * dis
let ink = mix(vec3(0.290, 0.306, 0.337), vec3(0.965, 0.973, 0.988), self.active)
acc = Finish.over(acc, vec4(ink * la, la))
return Finish.over(vec4(self.cap_ground.rgb, 1.0), acc).rgb
}
mod.widgets.ToggleRocker.draw_bg.vertex = fn() {
let m = lq_fader_room(self.size, self.cap_px, 0.0, self.cap_light, self.cap_height, self.cap_shadow_blur, self.cap_glow)
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 rocker drawn as the toggle is: the drop at its side or where the
// liquid motion has it, half the channel plus a point in, travelling the
// channel less its height and two points (the widget's own travel, from
// the border and the knob inset it reads back).
mod.widgets.ToggleRocker.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = lq_fader_room(self.size, self.cap_px, 0.0, self.cap_light, self.cap_height, self.cap_shadow_blur, self.cap_glow)
let pq = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let org = self.rect_pos
let th = self.size
let keep = 0.0 * (self.border_size + self.knob_inset)
let t = mix(mix(self.active, self.drag_pos, self.drag), self.knob_t, self.knob_follow) + keep
let w = th * self.pill_aspect
let x0 = (self.rect_size.x - w) * self.mark_at_end
let b = lq_snap(org, vec2(x0, self.rect_size.y * 0.5 - th * 0.5), vec2(x0 + w, self.rect_size.y * 0.5 + th * 0.5), px)
let out = lq_air_switch(pq, b.xy, b.zw, px, t, self.hover * (1.0 - self.disabled), self.disabled, self.error, self.cap_ground.rgb)
let tc = (b.xy + b.zw) * 0.5
let thh = (b.zw - b.xy) * 0.5
let td = Material.sd_box(pq, tc, thh, thh.y)
return Finish.over(out, lq_focus(td, px, self.focus * (1.0 - self.disabled)))
}
// The slide's channel: the toggle's, and in it the lamp in the end the
// drop has left, moving with the drop as the library's mark does.
mod.widgets.ToggleSlide.draw_bg.lq_under = fn(ab: vec2, tbox: vec4, cap: vec4, f: vec2) -> vec3 {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let en = 1.0 - self.disabled
let dis = mix(1.0, 0.35, self.disabled)
let hov = self.hover * en * 0.04
var acc = lq_well(ab, cap.xy, cap.zw, cap.w, px, vec3(0.737, 0.749, 0.773) + vec3(hov, hov, hov), dis)
let fcol = mix(mix(lq_accent(), vec3(0.643, 0.651, 0.671), self.disabled), lq_red(), self.error)
acc = lq_fill(acc, ab, cap.xy, cap.zw, px, cap.x - cap.z, cap.x + cap.z, fcol, 0.8 * self.active)
let t = mix(mix(self.active, self.drag_pos, self.drag), self.knob_t, self.knob_follow)
let free = vec2(mix(cap.x + cap.z - cap.w, cap.x - cap.z + cap.w, t), cap.y)
let mr = cap.w * 0.3
let dot = Finish.cover(length(ab - free) - mr, px)
let ring = Finish.cover(abs(length(ab - free) - mr) - 0.5, px)
let la = mix(ring, dot, self.active) * mix(0.8, 0.95, self.active) * dis
let ink = mix(vec3(0.290, 0.306, 0.337), vec3(0.965, 0.973, 0.988), self.active)
acc = Finish.over(acc, vec4(ink * la, la))
return Finish.over(vec4(self.cap_ground.rgb, 1.0), acc).rgb
}
mod.widgets.ToggleSlide.draw_bg.vertex = fn() {
let m = lq_fader_room(self.size, self.cap_px, 0.0, self.cap_light, self.cap_height, self.cap_shadow_blur, self.cap_glow)
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 slide drawn as the toggle is, its drop travelling the channel less
// its height (the widget's `knob_centred` travel, which its drag reads).
mod.widgets.ToggleSlide.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = lq_fader_room(self.size, self.cap_px, 0.0, self.cap_light, self.cap_height, self.cap_shadow_blur, self.cap_glow)
let pq = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let org = self.rect_pos
let th = self.size
let keep = 0.0 * (self.border_size + self.knob_inset + self.knob_centred)
let t = mix(mix(self.active, self.drag_pos, self.drag), self.knob_t, self.knob_follow) + keep
let w = th * self.pill_aspect
let x0 = (self.rect_size.x - w) * self.mark_at_end
let b = lq_snap(org, vec2(x0, self.rect_size.y * 0.5 - th * 0.5), vec2(x0 + w, self.rect_size.y * 0.5 + th * 0.5), px)
let out = lq_air_switch(pq, b.xy, b.zw, px, t, self.hover * (1.0 - self.disabled), self.disabled, self.error, self.cap_ground.rgb)
let tc = (b.xy + b.zw) * 0.5
let thh = (b.zw - b.xy) * 0.5
let td = Material.sd_box(pq, tc, thh, thh.y)
return Finish.over(out, lq_focus(td, px, self.focus * (1.0 - self.disabled)))
}
// RANGE SLIDER. A 6 point well the track's length, the accent span between
// the two ends, and a liquid glass drop (16 points) on each end, the well
// and the span seen bent through them. The two drops are the sliders'
// drop drawn twice; every pixel takes the nearer drop's rendering, so each
// drop refracts the well and the span but not the other drop.
mod.widgets.RangeSliderFlat.handle_size = 16.0
mod.widgets.RangeSliderFlat.clip_x = false
mod.widgets.RangeSliderFlat.clip_y = false
mod.widgets.RangeSliderFlat.draw_bg.cap_px = uniform(16.0)
mod.widgets.RangeSliderFlat.draw_bg.cap_stretch = uniform(0.0)
mod.widgets.RangeSliderFlat.draw_bg.cap_dir = uniform(1.0)
mod.widgets.RangeSliderFlat.draw_bg.cap_press = uniform(0.0)
mod.widgets.RangeSliderFlat.draw_bg.cap_squash_along = uniform(1.0)
mod.widgets.RangeSliderFlat.draw_bg.cap_squash_across = uniform(1.0)
mod.widgets.RangeSliderFlat.draw_bg.cap_light = uniform(vec3(-0.3, -0.6, 0.74))
mod.widgets.RangeSliderFlat.draw_bg.cap_light_intensity = uniform(0.95)
mod.widgets.RangeSliderFlat.draw_bg.cap_specular = uniform(1.0)
mod.widgets.RangeSliderFlat.draw_bg.cap_roughness = uniform(0.02)
mod.widgets.RangeSliderFlat.draw_bg.cap_gloss = uniform(0.0)
mod.widgets.RangeSliderFlat.draw_bg.cap_rim = uniform(1.0)
mod.widgets.RangeSliderFlat.draw_bg.cap_ao = uniform(0.46)
mod.widgets.RangeSliderFlat.draw_bg.cap_reflection = uniform(0.46)
mod.widgets.RangeSliderFlat.draw_bg.cap_reflection_perspective = uniform(0.51)
mod.widgets.RangeSliderFlat.draw_bg.cap_glow = uniform(0.0)
mod.widgets.RangeSliderFlat.draw_bg.cap_exposure = uniform(0.05)
mod.widgets.RangeSliderFlat.draw_bg.cap_rolloff = uniform(0.88)
mod.widgets.RangeSliderFlat.draw_bg.cap_shadow = uniform(0.5)
mod.widgets.RangeSliderFlat.draw_bg.cap_shadow_blur = uniform(10.0)
mod.widgets.RangeSliderFlat.draw_bg.cap_contact = uniform(0.35)
mod.widgets.RangeSliderFlat.draw_bg.cap_inner = uniform(0.5)
mod.widgets.RangeSliderFlat.draw_bg.cap_edge_width = uniform(1.0)
mod.widgets.RangeSliderFlat.draw_bg.cap_blend = uniform(0.02)
mod.widgets.RangeSliderFlat.draw_bg.cap_edge_out0 = uniform(vec2(0.0, 0.5523))
mod.widgets.RangeSliderFlat.draw_bg.cap_edge_in1 = uniform(vec2(-0.5523, 0.0))
mod.widgets.RangeSliderFlat.draw_bg.cap_edge_out1 = uniform(vec2(0.5523, 0.0))
mod.widgets.RangeSliderFlat.draw_bg.cap_height = uniform(6.5)
mod.widgets.RangeSliderFlat.draw_bg.cap_glass_depth = uniform(4.4)
mod.widgets.RangeSliderFlat.draw_bg.cap_glass_magnify = uniform(0.0)
mod.widgets.RangeSliderFlat.draw_bg.cap_glass_tint = uniform(0.0)
mod.widgets.RangeSliderFlat.draw_bg.cap_glass_chroma = uniform(0.05)
mod.widgets.RangeSliderFlat.draw_bg.cap_glass_caustic = uniform(0.6)
mod.widgets.RangeSliderFlat.draw_bg.cap_glass_outer = uniform(1.0)
mod.widgets.RangeSliderFlat.draw_bg.cap_glass_inner = uniform(0.6)
mod.widgets.RangeSliderFlat.draw_bg.cap_ground = uniform(theme.color_bg_app)
mod.widgets.RangeSliderFlat.draw_bg.cap_fill_ink = uniform(theme.color_val)
mod.widgets.RangeSliderFlat.draw_bg.cap_fill_disabled = uniform(theme.color_mark_disabled)
mod.widgets.RangeSliderFlat.draw_bg.cap_body = uniform(#ffffff)
mod.widgets.RangeSliderFlat.draw_bg.cap_light_ink = uniform(theme.color_material_light)
mod.widgets.RangeSliderFlat.draw_bg.cap_shadow_ink = uniform(#2a3140)
mod.widgets.RangeSliderFlat.draw_bg.lq_drop = mod.widgets.SliderMinimal.draw_bg.lq_drop
// What lies under the drops: the well and the span from `f.x` to `f.y`,
// over the ground.
mod.widgets.RangeSliderFlat.draw_bg.lq_under = fn(ab: vec2, tbox: vec4, cap: vec4, f: vec2) -> vec3 {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let dis = mix(1.0, 0.35, self.disabled)
let fcol = mix(lq_accent(), vec3(0.643, 0.651, 0.671), self.disabled)
var acc = lq_well(ab, tbox.xy, tbox.zw, tbox.w, px, vec3(0.737, 0.749, 0.773), dis)
acc = lq_fill(acc, ab, tbox.xy, tbox.zw, px, f.x, f.y, fcol, 0.8 + 0.1 * self.hot_band)
return Finish.over(vec4(self.cap_ground.rgb, 1.0), acc).rgb
}
mod.widgets.RangeSliderFlat.draw_bg.vertex = fn() {
let m = lq_fader_room(self.rect_size.y - self.label_px, self.cap_px, self.inset_px, self.cap_light, self.cap_height, self.cap_shadow_blur, self.cap_glow)
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 px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = lq_fader_room(self.rect_size.y - self.label_px, self.cap_px, self.inset_px, self.cap_light, self.cap_height, self.cap_shadow_blur, self.cap_glow)
let pq = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let org = self.rect_pos
let top = self.label_px
let track_h = self.rect_size.y - top
let cy = Finish.snap(org.y + top + track_h * 0.5, px) - org.y
// Both centres come out of the travel the hit test uses, so what is
// drawn is what is grabbable.
let span_x = self.rect_size.x - self.inset_px * 2.0 - self.cap_px
let sc = self.inset_px + self.cap_px * 0.5 + self.start_pos * span_x
let ec = self.inset_px + self.cap_px * 0.5 + self.end_pos * span_x
let gc = vec2(self.rect_size.x * 0.5, cy)
let gh = vec2(self.rect_size.x * 0.5, 3.0)
let kc0 = vec2(Finish.snap(org.x + sc, px) - org.x, cy)
let kc1 = vec2(Finish.snap(org.x + ec, px) - org.x, cy)
let a = self.lq_drop(pq, kc0, vec4(gc, gh), vec4(0.0, 0.0, 0.0, 0.0), vec2(sc, ec), px, m, 1.0, self.hot_start, 0.0)
let b = self.lq_drop(pq, kc1, vec4(gc, gh), vec4(0.0, 0.0, 0.0, 0.0), vec2(sc, ec), px, m, 1.0, self.hot_end, 0.0)
return mix(a, b, step(length(pq - kc1), length(pq - kc0)))
}
// KNOBS. A clear glass disc with the rim of every other part, a black
// pointer line, and round it a dark ring with the value laid into it as an
// accent core, the glass family's value arc: ink, not light.
let lq_arc = fn(p: vec2, c: vec2, rr: float, a0: float, a1: float, w: float) -> float {
let q = p - c
var ap = atan2(-q.x, q.y)
if ap < 0.0 {
ap = ap + 2.0 * PI
}
let rel = ap - a0 - floor((ap - a0) / (2.0 * PI)) * 2.0 * PI
let on = step(rel, a1 - a0)
let band = abs(length(q) - rr) - 0.5 * w
let e0 = length(q - vec2(-sin(a0), cos(a0)) * rr) - 0.5 * w
let e1 = length(q - vec2(-sin(a1), cos(a1)) * rr) - 0.5 * w
return mix(min(e0, e1), band, on)
}
// The ring, a little outside the cap: a pale hairline, then the dark band
// with the value's accent core laid into it. Premultiplied.
let lq_knob_ring = fn(p: vec2, c: vec2, r: float, px: float, start: float, sweep: float, a0: float, a1: float, disabled: float) -> vec4 {
let dis = mix(1.0, 0.35, disabled)
let gap = clamp(0.1 * r, 1.5, 3.0)
let rr = r + gap
let pale = Finish.cover(lq_arc(p, c, rr + gap * 0.5 + 1.0, start, start + sweep, px), px) * 0.45 * dis
var acc = vec4(pale, pale, pale, pale)
let ta = Finish.cover(lq_arc(p, c, rr, start, start + sweep, 2.0), px) * 0.85 * dis
acc = Finish.over(acc, vec4(vec3(0.196, 0.271, 0.400) * ta, ta))
let va = Finish.cover(lq_arc(p, c, rr, a0, max(a1, a0 + 0.001), 1.1), px) * step(0.0005, a1 - a0) * dis
return Finish.over(acc, vec4(mix(vec3(0.439, 0.620, 0.949), vec3(0.643, 0.651, 0.671), disabled) * va, va))
}
// How far the glass reaches: its rest radius `r`, and pressed (`grow` 1, a
// little past it as the press springs) three points past the ring's pale
// hairline, so the whole ring lies under it and clear of the solid lines
// along the glass's rim.
let lq_knob_reach = fn(r: float, grow: float) -> float {
let gap = clamp(0.1 * r, 1.5, 3.0)
let out = r + gap * 1.5 + 4.0
return r + (out - r) * grow
}
// THE DROP IN MOTION. The knob is a flat drop of water lying on its ring,
// and it moves as water does, not as jelly: slowly, damped, flowing.
// Pressed, it spreads out over the ring like poured water, fast at first
// and arriving softly (1 - e^(-t / 0.11)), while soft lobes rise on its
// outline and travel round it, each at its own speed and some the other
// way, so its shape melts from one blob into the next; they die away over
// about a second and it rests spread and round. Let go, it eases in (a
// damped return, 0.8 of critical: it starts slowly, overshoots once by a
// point or two and settles), drawing in a little past rest into a smaller,
// taller dome that eases back, while new lobes rise and circle round it and
// melt away; it is round again within 1.2 seconds. The lobes it had while
// held do not stop at the release but carry on fading, so nothing snaps.
// The lobes are the drop's two-, three- and four-lobed modes (the
// three-lobed strongest) with their angles moving: convex lobes joined by
// soft waists, never a point, and never a lobe bouncing in place. They keep
// the drop's mean radius and its scale is taken down by what they add, so
// its area holds; drawn in smaller it reads taller: lit harder, its lines
// tighter, a small glint high on its left. While it moves the glass reads
// thick: a bright band inside its rim, strongest on its lit side, a glint
// where the rim faces the light, and a soft colour fringe along the rim,
// blue and violet on the left and pink and peach on the right. At rest it
// is drawn as it always was; a hover moves nothing.
//
// No two need be the same. Each set of lobes takes its own angles, mix of
// the modes (the three-lobed's weight 0.85 to 1.15, the others' 0.65 to
// 1.35 and 0.6 to 1.4), speeds and way round, and its own decay (within
// 0.15), and the dome a size within a fifth either way. A release draws
// them from a hash of how long the knob was held (the press clock, to the
// frame), its value and its place on the window (`seat`); a press from the
// place alone, since the hold is not known yet and the value moves under a
// turn: turned, the pointer turns and the drop does not.
//
// The clocks: the drag (1 held, falling to 0 over 1.2 seconds when let
// go, so the time since the release is (1 - drag) * 1.2) and the press
// clock (0 to 1 over 8 seconds from the press, held, and falling with the
// drag when let go, so the clock over the drag is how long it was held,
// over 8, all through the release; their animator, below). From these the
// face knows the time since the press throughout, and the spread it had
// when let go.
// A share of the drop's radius in the direction `u`: two sets of lobes,
// the press's and the release's, each `a` (.xyz) the swings of its two-,
// three- and four-lobed modes and `f` (.xyz) their angles; `a.w` the
// drop's scale.
let lq_drop_form = fn(u: vec2, a: vec4, f: vec4, b: vec4, g: vec4) -> float {
let th = atan2(u.y, u.x)
let pl = a.x * cos(2.0 * (th - f.x)) + a.y * cos(3.0 * (th - f.y)) + a.z * cos(4.0 * (th - f.z))
let rl = b.x * cos(2.0 * (th - g.x)) + b.y * cos(3.0 * (th - g.y)) + b.z * cos(4.0 * (th - g.z))
return a.w * max(1.0 + pl + rl, 0.3)
}
// The pointer: a black line 1.5 points wide, a fifth of the disc long, set
// in from the rim. Premultiplied.
let lq_knob_pointer = fn(p: vec2, c: vec2, r: float, px: float, val: float, disabled: float) -> vec4 {
let dir = vec2(-sin(val), cos(val))
let pd = lq_seg(p, c + dir * (r - 2.5 - 0.4 * r), c + dir * (r - 2.5)) - 0.75
let pa = Finish.cover(pd, px) * mix(1.0, 0.35, disabled)
return vec4(vec3(0.110, 0.118, 0.133) * pa, pa)
}
// The knob: the drop's glass in its shape over the ring, and the pointer
// carried by the drop's shape as the glass is (THE DROP IN MOTION). Where
// the drop lies past its rest disc, over the ring, the ring is seen through
// it as it is, under a face thinned to a third, nothing bent; the glass's
// rim lines are solid, and where they pass over the ring the ring still
// shows through them at seven tenths.
let lq_knob = fn(p: vec2, c: vec2, r: float, px: float, start: float, sweep: float, a0: float, a1: float, val: float, hover: float, drag: float, clock: float, focus: float, disabled: float, seat: float) -> vec4 {
let en = 1.0 - disabled
let dis = mix(1.0, 0.35, disabled)
let dd = 2.0 * r
let tw = 2.0 * PI
// The motion.
let held = step(0.9995, drag)
let tr = (1.0 - clamp(drag, 0.0, 1.0)) * 1.2
// How long it was held (and held, how long since the press), seconds.
let hold = clamp(clock / max(drag, 0.0001), 0.0, 1.0) * 8.0
let tp = hold + tr
let fade = 1.0 - smoothstep(0.85, 1.2, tr) * (1.0 - held)
// The press's lobes: from the place alone, never the value, so turning
// the knob turns its pointer and leaves the drop be.
let sv = seat * tw
let kp1 = sin(sv * 1.7 + 0.4)
let kp2 = sin(sv * 1.3 + 2.1)
let kp3 = sin(sv * 2.3 + 4.4)
let kp4 = sin(sv * 1.1 + 1.2)
let kp5 = sin(sv * 1.9 + 3.3)
let kp6 = sin(sv * 2.9 + 5.1)
let pe = smoothstep(0.0, 0.12, tp) * exp(-tp / (0.32 * (1.0 + 0.15 * kp5))) * fade
let pw2 = mix(-1.0, 1.0, step(0.0, kp4))
let pa = vec4(0.10 * (1.0 + 0.35 * kp1) * pe, 0.18 * (1.0 + 0.15 * kp2) * pe, 0.06 * (1.0 + 0.40 * kp3) * pe, 0.0)
let pf = vec4(PI * kp4 + pw2 * 1.9 * (1.0 + 0.2 * kp6) * tp, 0.5 + PI * kp5 - pw2 * 1.3 * (1.0 + 0.2 * kp1) * tp, 1.9 + PI * kp6 + pw2 * 1.0 * (1.0 + 0.2 * kp2) * tp, 0.0)
// The release's lobes: hashed from the hold, the value and the place.
// The hold is read back as the clock over the drag, which wanders in
// its last bits from frame to frame; the hash turns any change into
// other lobes, so it is taken to the whole frame first (else the drop
// jumps between shapes as it settles).
let hs = vec2(floor(hold * 60.0 + 0.5) * 4.9979 + seat * 97.0, val * 517.0 + seat * 311.0)
let kr1 = Finish.hash(hs) * 2.0 - 1.0
let kr2 = Finish.hash(hs + vec2(13.7, 71.3)) * 2.0 - 1.0
let kr3 = Finish.hash(hs + vec2(41.9, 7.1)) * 2.0 - 1.0
let kr4 = Finish.hash(hs + vec2(5.3, 29.9)) * 2.0 - 1.0
let kr5 = Finish.hash(hs + vec2(88.1, 52.7)) * 2.0 - 1.0
let kr6 = Finish.hash(hs + vec2(23.3, 91.1)) * 2.0 - 1.0
let re = smoothstep(0.0, 0.18, tr) * exp(-tr / (0.38 * (1.0 + 0.15 * kr5))) * (1.0 - held) * fade
let rw2 = mix(-1.0, 1.0, step(0.0, kr4))
let ra = vec4(0.10 * (1.0 + 0.35 * kr1) * re, 0.17 * (1.0 + 0.15 * kr2) * re, 0.05 * (1.0 + 0.40 * kr3) * re, 0.0)
let rf = vec4(val + PI * kr4 + rw2 * 1.9 * (1.0 + 0.2 * kr6) * tr, val + 0.5 + PI * kr5 - rw2 * 1.3 * (1.0 + 0.2 * kr1) * tr, val + 1.9 + PI * kr6 + rw2 * 1.0 * (1.0 + 0.2 * kr2) * tr, 0.0)
// The dome: drawn in a little past rest after the release, once,
// easing back.
let du = tr / 0.28
let dome = -0.11 * (1.0 + 0.2 * kr6) * du * exp(1.0 - du) * (1.0 - held) * fade
let ss = pa.x * pa.x + pa.y * pa.y + pa.z * pa.z + ra.x * ra.x + ra.y * ra.y + ra.z * ra.z
let a = vec4(pa.xyz * en, mix(1.0, (1.0 + dome) / sqrt(1.0 + 0.5 * ss), en))
let ar = vec4(ra.xyz * en, 0.0)
let defo = clamp((pa.x + pa.y + pa.z + ra.x + ra.y + ra.z) * 5.0 + abs(dome) * 4.0, 0.0, 1.0) * en
// The spread: poured out while held; let go, a damped return from the
// spread it had.
let spread = 1.0 - exp(-tp / 0.11)
let g0 = 1.0 - exp(-hold / 0.11)
let zw = 0.8 * 7.5
let wd = 7.5 * sqrt(1.0 - 0.8 * 0.8)
let back = exp(-zw * tr) * (cos(wd * tr) + (zw / wd) * sin(wd * tr))
let grow = mix(g0 * back * fade, spread, held)
// The glass.
let hov = hover * en * (1.0 - held)
let gw = grow * en
let rg = lq_knob_reach(r, gw)
var acc = lq_knob_ring(p, c, r, px, start, sweep, a0, a1, disabled)
let q = p - c
let rho = length(q)
let e = lq_drop_form(q / max(rho, 0.0001), a, pf, ar, rf)
let pw = c + q / e
let rw = length(pw - c)
var seen = vec4(0.0, 0.0, 0.0, 0.0)
var under = 0.0
if rho > r - 1.0 && rw < rg + px {
under = Finish.cover(rw - rg, px)
seen = acc
}
let h = vec2(rg, rg)
let kap = clamp(r * r / max(rg * rg * a.w * a.w, 1.0), 0.6, 1.8)
acc = Finish.over(acc, lq_cast(pw, c, h, rg, px, dd, 0.3 * dis * mix(1.0, kap, 0.5), 1.0 - 0.4 * held, vec3(0.165, 0.192, 0.251), vec3(1.0, 1.0, 1.0), 0.32 * en * (1.0 - 0.5 * held)))
// Clear glass, no grey base (lq_glass_body): the pane shows through it
// under a breath of white, a little more under the pointer, a little
// less held.
let lift = 0.015 * hov - 0.01 * held
let thin = smoothstep(r + 0.25, r + 1.25, max(rw, rho))
let hl = mix(0.95, 1.0, hov) * mix(1.0, 0.5, disabled) * clamp(mix(1.0, kap, 0.8), 0.7, 1.5)
let fo = (0.06 * dd + px) / pow(kap, 1.2)
acc = Finish.over(acc, lq_glass_body(pw, c, h, rg, px, dd, vec3(0.0, 0.0, 0.0), 0.0, lift, 1.0, mix(0.9, 0.3, thin), hl, 1.0, fo, 1.5, vec4(0.620, 0.631, 0.659, 0.9), vec3(0.965, 0.973, 0.988)))
// Thick glass in motion: the band inside the rim, its glint and its
// fringe, in the drop's own frame so they follow the outline.
let inside = Finish.cover(rw - rg, px)
let n = (pw - c) / max(rw, 0.0001)
let lt = max(dot(n, normalize(vec2(-0.45, -0.89))), 0.0)
let din = rg - rw
let bw = 0.30 * r
let band = smoothstep(0.0, 0.35 * bw, din) * (1.0 - smoothstep(0.45 * bw, bw, din))
let ba = band * (0.25 + 0.75 * lt) * 0.50 * defo * inside
acc = Finish.over(acc, vec4(ba, ba, ba, ba))
let gx = (din - 0.12 * r) / max(0.07 * r, 0.5)
let ga = exp(-gx * gx) * pow(lt, 10.0) * 1.0 * defo * inside
acc = Finish.over(acc, vec4(ga, ga, ga, ga))
let cool = mix(vec3(0.560, 0.640, 1.000), vec3(0.720, 0.600, 0.980), smoothstep(-1.0, 1.0, n.y))
let warm = mix(vec3(1.000, 0.800, 0.660), vec3(1.000, 0.660, 0.790), smoothstep(-1.0, 1.0, n.y))
let fc = mix(cool, warm, smoothstep(-0.6, 0.6, n.x))
let fa = smoothstep(0.0, 1.5 * px, din) * (1.0 - smoothstep(0.04 * r, 0.11 * r, din)) * 0.55 * defo * inside
acc = Finish.over(acc, vec4(fc * fa, fa))
// The glint of a taller dome.
let gc = c + vec2(-0.30, -0.36) * rg
let gq = (pw - gc) / max(0.20 * r / kap, 0.5)
let da = exp(-dot(gq, gq)) * clamp((kap - 1.0) * 2.5, 0.0, 0.85) * inside * en
acc = Finish.over(acc, vec4(da, da, da, da))
acc = Finish.over(acc, seen * (Finish.band(rw - rg, 0.0, 3.0 * px, px) * under * 0.7))
acc = Finish.over(acc, lq_focus(rho - rg, px, focus * en))
return Finish.over(acc, lq_knob_pointer(pw, c, r, px, val, disabled))
}
// The knobs' press spring at all but the engine's most viscous (at exactly
// 1 the knobs stopped drawing), though the drop's spread now runs on the
// clocks below. The drag state still cuts to 1 on the press; let go, it
// falls in a straight line over 1.2 seconds, the release's clock. The press clock runs 0 to 1 over
// 8 seconds from the press and falls with the drag; it rides on
// `label_size`, which a vertical slider does not read (a further value of
// its own would take the face past the 32 inputs a vertex shader may
// have). The animator only animates a value it has set once, and the
// pointer comes over the knob before it presses, so the hover sets it.
mod.widgets.RotaryKnob.cap_viscosity = 0.99
mod.widgets.RotaryFlat.cap_viscosity = 0.99
mod.widgets.RotaryKnob.animator.drag.on.from = {all: Forward {duration: 8.0}}
mod.widgets.RotaryKnob.animator.drag.on.apply = {draw_bg: {drag: snap(1.0) label_size: 1.0} draw_text: {drag: snap(1.0)}}
mod.widgets.RotaryKnob.animator.drag.off.from = {all: Forward {duration: 1.2}}
mod.widgets.RotaryKnob.animator.drag.off.apply = {draw_bg: {drag: 0.0 label_size: 0.0} draw_text: {drag: 0.0}}
mod.widgets.RotaryKnob.animator.hover.on.apply = {draw_bg: {hover: 1.0 label_size: 0.0} draw_text: {hover: 1.0}}
mod.widgets.RotaryFlat.animator.drag.on.from = {all: Forward {duration: 8.0}}
mod.widgets.RotaryFlat.animator.drag.on.apply = {draw_bg: {drag: snap(1.0) label_size: 1.0} draw_text: {drag: snap(1.0)}}
mod.widgets.RotaryFlat.animator.drag.off.from = {all: Forward {duration: 1.2}}
mod.widgets.RotaryFlat.animator.drag.off.apply = {draw_bg: {drag: 0.0 label_size: 0.0} draw_text: {drag: 0.0}}
mod.widgets.RotaryFlat.animator.hover.on.apply = {draw_bg: {hover: 1.0 label_size: 0.0} draw_text: {hover: 1.0}}
// The panel knob: the disc in the biggest circle the box holds, pinned to
// the bottom under its legend, its ring round it. Held, the glass spreads
// over the ring on the press.
mod.widgets.RotaryKnob.draw_bg.pixel = fn() {
let m = 10.0
let pq = 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 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)
let r = dia * 0.5 - clamp(0.15 * dia, 3.5, 6.5)
let one = PI / 180.0
let sweep = 2.0 * PI - self.gap * one
let start = self.gap * one * 0.5
let val = start + sweep * self.slide_pos
let org_a = start + sweep * self.origin_pos
let a0 = mix(start, min(org_a, val), self.arc_origin)
let a1 = mix(val, max(org_a, val), self.arc_origin)
let seat = Finish.hash(floor(self.rect_pos) * 0.731 + vec2(3.7, 1.3))
return lq_knob(pq, c, r, px, start, sweep, a0, a1, val, self.hover, self.drag, self.label_size, self.focus, self.disabled, seat)
}
mod.widgets.RotaryKnob.width = 44.0
mod.widgets.RotaryKnob.height = 60.0
// The legend over a knob is a small caption in the dim ink.
mod.widgets.RotaryKnob.draw_text.color = #4a4e56
mod.widgets.RotaryKnob.draw_text.text_style = mod.theme.font_regular{font_size: 7.5}
mod.widgets.RotaryFlat.text_input.draw_bg.color = #00000000
mod.widgets.RotaryFlat.text_input.min_height = 0.0
mod.widgets.RotaryFlat.text_input.padding.top = 0.0
mod.widgets.RotaryFlat.text_input.padding.bottom = 0.0
mod.widgets.Rotary.text_input.min_height = 0.0
mod.widgets.Rotary.text_input.padding.top = 0.0
mod.widgets.Rotary.text_input.padding.bottom = 0.0
mod.widgets.RotaryFlat.label_walk.margin.top = 4.0
// The value a bar slider carries beside its label sits on the label's
// line: its field takes no row height of its own (its padding is nil).
mod.widgets.SliderFlat.text_input.min_height = 0.0
mod.widgets.Slider.text_input.min_height = 0.0
mod.widgets.SliderMinimal.text_input.min_height = 0.0
mod.widgets.SliderRoundFlat.text_input.min_height = 0.0
mod.widgets.Rotary.label_walk.margin.top = 4.0
// The dial: the same knob, larger, under its legend and readout; held,
// its glass spreads over the ring as the panel knob's does.
mod.widgets.RotaryFlat.draw_bg.pixel = fn() {
let m = 10.0
let pq = 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 top = 24.0
let dia = min(self.rect_size.x, self.rect_size.y - top)
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 + top + dia * 0.5, px) - self.rect_pos.y)
let r = min(dia * 0.5 - 7.0, 28.0)
let one = PI / 180.0
let sweep = 2.0 * PI - self.gap * one
let start = self.gap * one * 0.5
let val = start + sweep * self.slide_pos
let org_a = start + sweep * self.origin_pos
let a0 = mix(start, min(org_a, val), self.arc_origin)
let a1 = mix(val, max(org_a, val), self.arc_origin)
let seat = Finish.hash(floor(self.rect_pos) * 0.731 + vec2(3.7, 1.3))
return lq_knob(pq, c, r, px, start, sweep, a0, a1, val, self.hover, self.drag, self.label_size, self.focus, self.disabled, seat)
}
// PROGRESS. A 6 point well; the fill is blue glass, paler along its top
// with a pale hairline and a bright exit pixel along its foot. Without a
// value the bar holds one still segment in its middle: a face may not read
// the clock, so the sweep cannot move.
mod.widgets.ProgressBarFlat.draw_bg.pixel = fn() {
let p = self.pos * self.rect_size
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let org = self.rect_pos
let w = self.rect_size.x
let t = min(6.0, self.rect_size.y)
let cy = Finish.snap(org.y + self.rect_size.y * 0.5, px) - org.y
let x0 = w * self.start
let x1 = w * self.end
let gc = vec2((x0 + x1) * 0.5, cy)
let gh = vec2((x1 - x0) * 0.5, t * 0.5)
var acc = lq_well(p, gc, gh, t * 0.5, px, vec3(0.737, 0.749, 0.773), 1.0) * step(0.5, self.track)
let xv = w * clamp(self.value, self.start, self.end)
let det = step(0.0, self.value)
let fa0 = mix(x0 + (x1 - x0) * 0.35, x0, det)
let fa1 = mix(x0 + (x1 - x0) * 0.65, xv, det)
let fr = t * 0.5
let fd = Material.sd_box(p, vec2((fa0 + fa1) * 0.5, cy), vec2(max((fa1 - fa0) * 0.5, 0.0), fr), min(fr, max((fa1 - fa0) * 0.5, 0.0)))
let v = clamp((p.y - cy + fr) / (2.0 * fr), 0.0, 1.0)
let glass = mix(vec3(0.498, 0.608, 0.769), lq_accent(), smoothstep(0.1, 0.9, v))
var fcol = mix(glass, self.fill_color().rgb, step(0.5, self.intent))
fcol = mix(fcol, vec3(0.839, 0.886, 0.980), Finish.band(fd, 0.0, px, px) * step(p.y, cy - fr + px) * 0.8)
fcol = mix(fcol, vec3(0.839, 0.886, 0.980), Finish.band(fd, 0.0, px, px) * step(cy + fr - px, p.y) * 0.5)
let show = mix(1.0, step(x0 + 0.5, xv), det)
let fA = Finish.cover(fd, px) * show
acc = Finish.over(acc, vec4(fcol * fA, fA))
return acc * self.opacity
}
mod.widgets.ProgressBarFlat.height = 8.0
mod.widgets.ProgressBarFlat.draw_bg.thickness = 6.0
// LEVEL METER. A readout window of dark glass cut into the ground: a black
// rim, a pale lip on its lower edge, and cells three points long with a
// point between, flat colour, accent to seven tenths, amber to nine,
// red above; unlit cells show a tenth of their colour. The held peak is the
// one lit mark allowed a short halo. The clip lamp is a bead of dark glass that lights red, no glow.
mod.widgets.LevelMeter.draw_bg.pixel = fn() {
let p = self.pos * self.rect_size
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let org = self.rect_pos
let v = self.vertical
let along = mix(self.rect_size.x, self.rect_size.y, v)
let across = mix(self.rect_size.y, self.rect_size.x, v)
let q = vec2(mix(p.x, self.rect_size.y - p.y, v), mix(p.y, p.x, v))
let lamp_room = self.lamp * (6.0 + self.lamp_gap)
let bar = max(4.0, along - lamp_room)
let r = min(3.0, across * 0.5)
let gc = vec2(bar * 0.5, across * 0.5)
let gh = vec2(bar * 0.5, across * 0.5)
let gd = Material.sd_box(q, gc, gh, r)
let pad = 1.5
let span = max(1.0, bar - pad * 2.0)
let share = clamp((q.x - pad) / span, 0.0, 1.0)
var zone = vec3(0.310, 0.498, 0.820)
zone = mix(zone, vec3(0.851, 0.643, 0.255), step(0.7, share))
zone = mix(zone, vec3(0.816, 0.337, 0.310), step(0.9, share))
let cell = step(0.25, fract((q.x - pad) / 4.0))
let inside = step(pad, q.x) * step(q.x, bar - pad) * step(abs(q.y - across * 0.5), across * 0.5 - pad)
let lit = step(q.x, pad + span * self.level)
// No dark window: the cells lie on the glass, the unlit ones a fifth of
// their colour, the lit ones full.
var acc = vec4(zone * cell * inside * 0.2 * (1.0 - lit), cell * inside * 0.2 * (1.0 - lit))
let la0 = cell * inside * lit
acc = Finish.over(acc, vec4(zone * la0, la0))
let hx = pad + span * self.hold
let hd = abs(q.x - hx) - 0.75
let hm = Finish.cover(hd, px) * inside * step(0.002, self.hold)
let hz = mix(vec3(0.310, 0.498, 0.820), vec3(0.816, 0.337, 0.310), step(0.9, self.hold))
let halo = (1.0 - smoothstep(0.0, 2.0, hd)) * 0.3 * inside * step(0.002, self.hold) * (1.0 - hm)
acc = Finish.over(acc, vec4(hz * halo, halo))
acc = Finish.over(acc, vec4(hz * hm, hm))
let rim = Finish.band(gd, 0.0, px, px) * 0.55
acc = Finish.over(acc, vec4(vec3(0.051, 0.067, 0.110) * rim, rim))
acc = acc * Finish.cover(gd, px)
let lip = Finish.ring_out(gd, 0.0, px, px) * step(across * 0.5, q.y) * 0.45
acc = Finish.over(acc, vec4(lip, lip, lip, lip))
// The clip lamp: a 5 point bead of dark glass (not a hole), a white
// hairline on its crown and a dark rim; lit, the same bead in red.
let lc = vec2(along - 3.0, across * 0.5)
let lr = min(2.5, across * 0.5)
let ld = length(q - lc) - lr
var lcol = mix(vec3(0.278, 0.294, 0.333), vec3(0.816, 0.337, 0.310), self.over)
lcol = mix(lcol, vec3(0.051, 0.067, 0.110), Finish.band(ld, 0.0, px, px) * 0.6)
lcol = mix(lcol, vec3(1.0, 1.0, 1.0), Finish.band(ld, px, 2.0 * px, px) * smoothstep(0.1, -0.6, (q.y - lc.y) / lr) * 0.45)
let la = Finish.cover(ld, px) * self.lamp
acc = Finish.over(acc, vec4(lcol * la, la))
return acc * self.opacity
}
mod.widgets.LevelMeter.height = 6.0
mod.widgets.LevelMeterColumn.width = 6.0
// SCROLL BAR. No track: a 6 point glass capsule thumb, its outline and a
// hairline on its upper end, no shadow; the pointer darkens it.
mod.widgets.ScrollBar.draw_bg.pixel = fn() {
let p = self.pos * self.rect_size
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let v = self.is_vertical
let along = mix(self.rect_size.x, self.rect_size.y, v)
let across = mix(self.rect_size.y, self.rect_size.x, v)
let q = vec2(mix(p.x, p.y, v), mix(p.y, p.x, v))
let mb = across * 0.5
let s0 = along * self.norm_scroll + 1.0
let s1 = along * (self.norm_scroll + self.norm_handle) - 1.0
let hc = vec2((s0 + s1) * 0.5, mb)
let hh = vec2(max((s1 - s0) * 0.5, 3.0), 3.0)
let hd = Material.sd_box(q, hc, hh, 3.0)
let k = max(self.hover, self.drag)
// No grey: a breath of shade on the glass, deeper under the pointer,
// a bright hairline on its upper end and its rim.
let ha = Finish.cover(hd, px)
let sh = mix(0.14, 0.24, k)
var acc = vec4(vec3(0.165, 0.192, 0.251) * sh, sh)
let hl = Finish.band(hd, px, 2.0 * px, px) * (1.0 - smoothstep(hc.x - hh.x + 3.0, hc.x - hh.x + 8.0, q.x)) * 0.9
acc = Finish.over(acc, vec4(vec3(0.965, 0.973, 0.988) * hl, hl))
let rl = Finish.band(hd, 0.0, px, px) * mix(0.7, 1.0, k)
acc = Finish.over(acc, vec4(vec3(0.600, 0.612, 0.643) * rl, rl))
return acc * ha * self.opacity
}
mod.widgets.ScrollBar.bar_size = 8.0
// SEGMENTED CONTROL. One standing glass capsule round every segment; the
// chosen one is blue glass inside it, with its hairline and no outline.
// The blue glass moves as the glass family's segmented row does (Containers
// > Glass > Controls): it travels on the glide's own clock eased as that
// row eases (1 - e^(-4.7 t), over a glide of 0.45 seconds, most of the way in
// 0.3), and stretches along its way like a drop pulled across, up to 0.44
// wider at the middle of each step, centred on its path, gathering again
// on each answer it passes: a jump over two answers passes the one between
// with a second stretch. Where it is going and where it set out from come
// with the pill (`glide_from`, `glide_to`, `glide`); the answers it crosses
// are counted from how far it goes against the width of an answer. The
// words do not change weight; the chosen one is a step darker.
mod.widgets.SegmentedControlFlat.segment_height = 26.0
mod.widgets.SegmentedControlFlat.segment_padding = 14.0
mod.widgets.SegmentedControlFlat.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.SegmentedControlFlat.draw_bg.pixel = fn() {
let m = 12.0
let pq = 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 b = lq_snap(self.rect_pos, vec2(0.0, 0.0), self.rect_size, px)
let c = (b.xy + b.zw) * 0.5
let h = (b.zw - b.xy) * 0.5
// The capsule's round is a row's: half its 26 point segment and the 2
// round it, never more than the short side's half. A row lying down is
// the capsule it was; one standing up is a tall rounded box whose
// corners hold the blue glass as the row's ends do, not a leaf.
let s = min(min(self.rect_size.x, self.rect_size.y), 32.0)
let r = min(min(h.x, h.y), 15.0)
var acc = lq_cast(pq, c, h, r, px, s, 0.36, 1.0, vec3(0.165, 0.192, 0.251), vec3(1.0, 1.0, 1.0), 0.32)
let lo = lq_body(pq, c, h, r, px, s, vec3(0.0, 0.0, 0.0), 1.0, 0.88, 0.95, 1.0, 0.05 * s + px, 1.5, vec4(0.682, 0.690, 0.714, 0.9), vec3(0.965, 0.973, 0.988))
let hi = lq_body(pq, c, h, r, px, s, vec3(1.0, 1.0, 1.0), 1.0, 0.88, 0.95, 1.0, 0.05 * s + px, 1.5, vec4(0.682, 0.690, 0.714, 0.9), vec3(0.965, 0.973, 0.988))
return Finish.over(acc, lq_unfill(lo, hi, 0.06))
}
// The room the moving pill is drawn in: both ends of its glide and the
// rect it has reached, and a little over for its edge.
let lq_seg_room = fn(rect_pos: vec2, rect_size: vec2, a: vec4, b: vec4) -> vec4 {
let lo = min(min(a.xy, b.xy), rect_pos) - vec2(6.0, 6.0)
let hi = max(max(a.xy + a.zw, b.xy + b.zw), rect_pos + rect_size) + vec2(6.0, 6.0)
return vec4(lo, hi - lo)
}
mod.widgets.SegmentedControlFlat.glide_secs = 0.45
mod.widgets.SegmentedControlFlat.draw_pill.vertex = fn() {
let room = lq_seg_room(self.rect_pos, self.rect_size, self.glide_from, self.glide_to)
self.vertex_pos = self.clip_and_transform_vertex(room.xy, room.zw)
}
mod.widgets.SegmentedControlFlat.draw_pill.pixel = fn() {
let room = lq_seg_room(self.rect_pos, self.rect_size, self.glide_from, self.glide_to)
let p = room.xy + self.pos * room.zw
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let a = self.glide_from
let g = self.glide_to
let t = clamp(self.glide, 0.0, 1.0)
let e = (1.0 - exp(-4.7 * t)) / (1.0 - exp(-4.7))
let r = mix(a, g, e)
// The stretch: a hump for every answer crossed, none at rest.
let span = abs(g.x - a.x)
let n = max(floor(span / max((a.z + g.z) * 0.5, 1.0) + 0.5), 1.0)
let f = e * n
let gloop = (1.0 - abs(2.0 * fract(f) - 1.0)) * step(0.5, span) * step(t, 0.9999)
let x0 = r.x - gloop * r.z * 0.22
let w = r.z * (1.0 + gloop * 0.44)
let b = lq_snap(vec2(0.0, 0.0), vec2(x0 + 1.0, r.y + 1.0), vec2(x0 + w - 1.0, r.y + r.w - 1.0), px)
let c = (b.xy + b.zw) * 0.5
let h = (b.zw - b.xy) * 0.5
return lq_body(p, c, h, min(h.x, h.y), px, min(r.z, r.w), lq_blue(), 2.2, 0.8, 1.0, 0.7, 0.05 * r.w + px, 1.0, vec4(0.478, 0.561, 0.690, 0.0), vec3(0.957, 0.969, 1.0))
}
mod.widgets.SegmentedControlFlat.draw_text.color = #1c1e22
mod.widgets.SegmentedControlFlat.draw_text.text_style.font_size = 8.75
mod.widgets.SegmentedControlFlat.draw_text_selected.text_style = mod.theme.font_regular{font_size: 8.75}
mod.widgets.SegmentedControlFlat.intent = mod.widgets.BadgeIntent.Primary
mod.widgets.SegmentedControlFlat.palette.on_primary_container = #0d0f14
mod.widgets.SegmentedControl.palette.on_primary_container = #0d0f14
mod.widgets.SegmentedControlFlat.draw_text_selected.color = #0d0f14
// TABS. The strip is one glass capsule lying on the ground; the tab in use
// is marked the way a tab bar is here, by a 1.5 point accent line under
// its word, with a faint lift of the glass; a hairline divider stands
// between the quiet ones.
mod.widgets.Tabs.height = 26.0
mod.widgets.Tabs.draw_bg.pixel = fn() {
let p = self.pos * self.rect_size
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let b = lq_snap(self.rect_pos, vec2(0.0, 0.0), vec2(self.rect_size.x, self.rect_size.y - 0.7), px)
let c = (b.xy + b.zw) * 0.5
let h = (b.zw - b.xy) * 0.5
let d = Material.sd_box(p, c, h, h.y)
let lo = lq_body(p, c, h, h.y, px, self.rect_size.y, vec3(0.0, 0.0, 0.0), 0.6, 0.85, 0.95, 0.8, 0.05 * self.rect_size.y + px, 1.5, vec4(0.682, 0.690, 0.714, 0.9), vec3(0.965, 0.973, 0.988))
let hi = lq_body(p, c, h, h.y, px, self.rect_size.y, vec3(1.0, 1.0, 1.0), 0.6, 0.85, 0.95, 0.8, 0.05 * self.rect_size.y + px, 1.5, vec4(0.682, 0.690, 0.714, 0.9), vec3(0.965, 0.973, 0.988))
var acc = lq_unfill(lo, hi, 0.06)
let ct = Finish.ring_out(d, 0.0, px, px) * smoothstep(0.0, h.y, p.y - c.y) * 0.25
return Finish.over(acc, vec4(0.0, 0.0, 0.0, ct))
}
mod.widgets.Tabs.draw_tab.pixel = fn() {
return vec4(0.0, 0.0, 0.0, 0.0)
}
mod.widgets.Tabs.draw_tab_hover.pixel = fn() {
let p = self.pos * self.rect_size
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let c = self.rect_size * 0.5
let h = self.rect_size * 0.5 - vec2(3.0, 3.0)
let d = Material.sd_box(p, c, h, h.y)
let a = Finish.cover(d, px) * 0.22
return vec4(a, a, a, a)
}
mod.widgets.Tabs.draw_tab_active.pixel = fn() {
let p = self.pos * self.rect_size
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let c = self.rect_size * 0.5
let h = self.rect_size * 0.5 - vec2(3.0, 3.0)
let d = Material.sd_box(p, c, h, h.y)
let lift = Finish.cover(d, px) * 0.3
var acc = vec4(lift, lift, lift, lift)
let yb = Finish.snap(self.rect_pos.y + self.rect_size.y - 5.0, px) - self.rect_pos.y
let ld = Material.sd_box(p, vec2(c.x, yb), vec2(max(h.x - 8.0, 4.0), 0.75), 0.75)
let la = Finish.cover(ld, px)
return Finish.over(acc, vec4(vec3(0.278, 0.463, 0.804) * la, la))
}
mod.widgets.Tabs.draw_sep.pixel = fn() {
let p = self.pos * self.rect_size
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let x = Finish.snap(self.rect_pos.x + self.rect_size.x * 0.5, px) - self.rect_pos.x
let inset = self.rect_size.y * 0.25
let a = step(x - px, p.x) * step(p.x, x) * step(inset, p.y) * step(p.y, self.rect_size.y - inset) * 0.6
return vec4(0.682 * a, 0.690 * a, 0.714 * a, a)
}
// Close and add: 7 point marks at the family's 1.5 point icon stroke.
mod.widgets.Tabs.draw_mark.color = #3d4048
mod.widgets.Tabs.draw_mark.pixel = fn() {
let p = self.pos * self.rect_size
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let c = self.rect_size * 0.5
let e = 2.75
let a = mix(0.7853981634, 0.0, self.plus)
let r = e * mix(1.4142, 1.0, self.plus)
let ax = cos(a) * r
let ay = sin(a) * r
let d = min(lq_seg(p, c - vec2(ax, ay), c + vec2(ax, ay)), lq_seg(p, c + vec2(ay, -ax), c + vec2(-ay, ax)))
let k = Finish.cover(d - 0.75, px) * self.color.w
return vec4(self.color.rgb * k, k)
}
mod.widgets.Tabs.draw_text.color = #1c1e22
mod.widgets.Tabs.draw_text_active.color = #0d0f14
// RADIO TABS. Glass capsules lying on the ground, lifted by the pointer;
// the one in use is blue glass.
mod.widgets.RadioButtonTabFlat.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.RadioButtonTabFlat.clip_x = false
mod.widgets.RadioButtonTabFlat.clip_y = false
mod.widgets.RadioButtonTabFlat.draw_bg.pixel = fn() {
let m = 12.0
let pq = 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)
return lq_button(pq, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, 0.0, 0.0, vec4(0.0, 0.0, 0.0, 0.0), vec4(lq_blue(), self.active), self.active, 0.0, 0.0)
}
mod.widgets.RadioButtonTabFlat.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 6.0 bottom: 6.0}
mod.widgets.RadioButtonTabFlat.draw_text.color = #1c1e22
mod.widgets.RadioButtonTabFlat.draw_text.color_active = #0d0f14
// The dock's tabs: the tab in use is a pane of glass rising from the bar
// with its hairline on top and the accent line along its foot; the bar
// ends in a hairline pair.
mod.widgets.Tab.draw_bg.pixel = fn() {
let p = self.pos * self.rect_size
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let b = lq_snap(self.rect_pos, vec2(0.0, 3.0), vec2(self.rect_size.x, self.rect_size.y + 6.0), px)
let c = (b.xy + b.zw) * 0.5
let h = (b.zw - b.xy) * 0.5
let d = Material.sd_box(p, c, h, 6.0)
let body = lq_glass_body(p, c, h, 6.0, px, 26.0, vec3(0.0, 0.0, 0.0), 0.0, 0.02, 0.5, 0.9, 1.0, 0.7, 1.4, 1.0, vec4(0.682, 0.690, 0.714, 0.9), vec3(0.965, 0.973, 0.988)) * self.active
let lift = Finish.cover(d, px) * 0.18 * self.hover * (1.0 - self.active)
var acc = Finish.over(vec4(lift, lift, lift, lift), body)
let yb = Finish.snap(self.rect_pos.y + self.rect_size.y - 1.0, px) - self.rect_pos.y
let ld = Material.sd_box(p, vec2(c.x, yb), vec2(max(h.x - 6.0, 4.0), 0.75), 0.75)
let la = Finish.cover(ld, px) * self.active
return Finish.over(acc, vec4(vec3(0.278, 0.463, 0.804) * la, la))
}
mod.widgets.Tab.padding = mod.turtle.Inset{left: 10.0 right: 10.0 top: 4.0 bottom: 4.0}
mod.widgets.TabBar.draw_fill.pixel = fn() {
let p = self.pos * self.rect_size
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let yb = Finish.snap(self.rect_pos.y + self.rect_size.y, px) - self.rect_pos.y
let dk = step(yb - 2.0 * px, p.y) * step(p.y, yb - px) * 0.18
let lt = step(yb - px, p.y) * step(p.y, yb) * 0.5
let acc = vec4(0.165 * dk, 0.192 * dk, 0.251 * dk, dk)
return Finish.over(acc, vec4(lt, lt, lt, lt))
}
// CHIPS. A small glass capsule standing on the ground; pressed, it settles;
// a keyed chip wears the focus ring.
mod.widgets.ChipFlat.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.ChipFlat.draw_bg.pixel = fn() {
let m = 10.0
let pq = 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 ch = min(22.0, self.rect_size.y)
let dy = (self.rect_size.y - ch) * 0.5
let b = lq_snap(self.rect_pos, vec2(0.0, dy), vec2(self.rect_size.x, dy + ch), px)
let c = (b.xy + b.zw) * 0.5
let h = (b.zw - b.xy) * 0.5
let d = Material.sd_box(pq, c, h, h.y)
let press = self.down
let hov = self.hover * (1.0 - press)
var acc = lq_cast(pq, c, h, h.y, px, ch, 0.3, 1.0 - press, vec3(0.165, 0.192, 0.251), vec3(1.0, 1.0, 1.0), mix(0.3, 0.45, hov) * (1.0 - 0.6 * press))
let lift = 0.03 * hov - 0.04 * press
acc = Finish.over(acc, lq_glass_body(pq, c, h, h.y, px, ch, vec3(0.0, 0.0, 0.0), 0.0, lift, 1.0, 0.88, mix(1.0, 0.62, press), 1.0 + 0.5 * press, 0.06 * ch + px, 1.0, vec4(0.682, 0.690, 0.714, 0.9), vec3(0.965, 0.973, 0.988)))
return Finish.over(acc, lq_focus(d, px, self.keyed))
}
mod.widgets.ChipFlat.draw_text.color = #1c1e22
mod.widgets.ChipFlat.draw_text.text_style.font_size = 8.25
// SURFACES. A card is a thick slab of glass on the ground: its outline, a
// bright-dark-bright triple line along its top edge leaning a little cyan
// as a thick slab's edge does, an exit line along its foot, a face just
// lighter than the ground falling gently to its foot, and a soft cool
// shadow; `beads` puts a small clear bead in each corner. The sunken panel
// is a tray cut into the ground: its top edge in shade, a white lip along
// its foot. Both carry the ground's own grain. A panel is a pane of light
// glass over the blobs (see THE PANES, after the ground).
let lq_panel = fn(pq: vec2, size: vec2, org: vec2, px: float, r: float, raised: float, sunken: float, lift: float, beads: float, ground: vec3) -> vec4 {
let b = lq_snap(org, vec2(0.0, 0.0), size, px)
let c = (b.xy + b.zw) * 0.5
let h = (b.zw - b.xy) * 0.5
let rr = min(r, min(h.x, h.y))
let d = Material.sd_box(pq, c, h, rr)
let n = lq_norm(pq, c, h, rr)
let t = -d
let up = smoothstep(0.15, 0.85, -n.y)
let low = smoothstep(0.3, 0.9, n.y)
let right = clamp(n.x, 0.0, 1.0)
// The raised slab's shadow: 5 points down, a 7 point blur, and a tight
// contact line under the foot.
let sh = Finish.drop(pq, c + vec2(0.5, 0.0), h - vec2(2.0, 2.0), max(rr - 2.0, 0.0), vec2(0.0, 5.0 + lift), 6.0) * (0.13 + 0.03 * lift) * raised
var acc = vec4(vec3(0.165, 0.192, 0.251) * sh, sh)
let ct = Finish.drop(pq, c, h, rr, vec2(0.0, 0.8), 0.7) * 0.3 * smoothstep(c.y, c.y + h.y, pq.y) * raised
acc = Finish.over(acc, vec4(0.0, 0.0, 0.0, ct))
acc = acc * (1.0 - Finish.cover(d, px))
// The slab: the ground two hundredths up, three up at the top and three
// down at the foot over its first and last 60 points.
let ft = smoothstep(0.0, 60.0, pq.y - b.y)
let fb = smoothstep(0.0, 60.0, b.w - pq.y)
let g = 0.02 + 0.03 * (1.0 - ft) - 0.03 * (1.0 - fb) + 0.006 * lift
var col = ground + vec3(g, g, g + 0.006)
col = col + vec3(lq_grain(org + pq, px))
// A cool shade just inside the rim, heavier to the lower right.
col = mix(col, vec3(0.165, 0.192, 0.251), (1.0 - smoothstep(0.0, 3.0, t)) * 0.06 * mix(0.6, 1.4, max(low, right)))
let edge = vec3(0.729, 0.839, 0.882)
let hi = mix(vec3(0.965, 0.973, 0.988), edge, 0.3)
col = mix(col, hi, Finish.band(d, px, 2.0 * px, px) * max(up, 0.35) * 0.95)
col = mix(col, mix(vec3(0.600, 0.624, 0.671), edge * 0.7, 0.3), Finish.band(d, 2.0 * px, 3.0 * px, px) * up * 0.8)
col = mix(col, vec3(1.0, 1.0, 1.0), Finish.band(d, 3.0 * px, 4.0 * px, px) * up * 0.4)
col = mix(col, mix(vec3(0.945, 0.957, 0.980), edge, 0.3), Finish.band(d, px, 2.5 * px, px) * low * 0.9)
col = mix(col, vec3(0.620, 0.631, 0.659), Finish.band(d, 0.0, px, px) * 0.9)
// The beads: 4 point clear glass, 7 points in from each corner, a white
// point on the upper left, a faint shade to the lower right.
let bx = min(pq.x - b.x, b.z - pq.x)
let by = min(pq.y - b.y, b.w - pq.y)
let bq = vec2(bx, by) - vec2(7.0, 7.0)
let bsx = step(c.x, pq.x) * 2.0 - 1.0
let bsy = step(c.y, pq.y) * 2.0 - 1.0
let bp = vec2(bq.x * -bsx, bq.y * -bsy)
let bd = length(bq) - 2.0
let bsh = (1.0 - smoothstep(-0.5, 1.2, length(bp - vec2(0.6, 0.8)) - 2.0)) * 0.18 * beads * (1.0 - Finish.cover(bd, px))
col = mix(col, vec3(0.165, 0.192, 0.251), bsh)
var bc = mix(lq_ground() + vec3(0.06, 0.06, 0.066), lq_ground() - vec3(0.02, 0.02, 0.02), clamp(bp.y / 4.0 + 0.5, 0.0, 1.0))
bc = mix(bc, vec3(1.0, 1.0, 1.0), Finish.cover(length(bp - vec2(-0.7, -0.7)) - 0.5, px))
bc = mix(bc, vec3(0.620, 0.631, 0.659), Finish.band(bd, 0.0, px, px) * 0.6)
col = mix(col, bc, Finish.cover(bd, px) * beads)
// The tray: the ground three hundredths down, the ground's shade
// falling in over its top edge, a white lip under its foot.
var tc = ground - vec3(0.025, 0.025, 0.02)
tc = tc + vec3(lq_grain(org + pq, px))
let ins = Finish.inset(pq, c, h, rr, vec2(0.0, 1.5), 2.0)
tc = mix(tc, vec3(0.165, 0.192, 0.251), ins * 0.22)
tc = mix(tc, vec3(0.427, 0.443, 0.478), Finish.band(d, 0.0, px, px) * up * 0.45)
tc = mix(tc, vec3(0.580, 0.600, 0.635), Finish.band(d, 0.0, px, px) * (1.0 - up) * 0.35)
col = mix(col, tc, sunken)
let a = Finish.cover(d, px)
acc = Finish.over(acc, vec4(col * a, a))
let lip = Finish.ring_out(d, 0.0, px, px) * low * 0.8 * sunken
return Finish.over(acc, vec4(lip, lip, lip, lip))
}
mod.widgets.RoundedView.clip_x = false
mod.widgets.RoundedView.clip_y = false
mod.widgets.PanelView.draw_bg.vertex = fn() {
let m = 16.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))
}
// A card is the slab too; a pressable one lifts a little under the pointer.
mod.widgets.Card.radius = 12.0
mod.widgets.Card.clip_x = false
mod.widgets.Card.clip_y = false
mod.widgets.Card.draw_bg.vertex = fn() {
let m = 16.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.Card.draw_bg.pixel = fn() {
let m = 16.0
let pq = 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 lift = self.hover * (1.0 - self.down)
let outlined = step(0.01, self.border_size)
let raised = max(clamp(self.shadow_radius / 4.0, 0.0, 1.0), 0.6 * lift) * (1.0 - self.down)
let sunk = (1.0 - clamp(self.shadow_radius / 4.0, 0.0, 1.0)) * (1.0 - outlined) * 0.7 * (1.0 - lift)
let lo = lq_panel(pq, self.rect_size, self.rect_pos, px, self.border_radius, max(raised, 0.35 * outlined), sunk, lift, 0.0, vec3(0.0, 0.0, 0.0))
let hi = lq_panel(pq, self.rect_size, self.rect_pos, px, self.border_radius, max(raised, 0.35 * outlined), sunk, lift, 0.0, vec3(1.0, 1.0, 1.0))
return lq_unfill(lo, hi, 0.08)
}
// The rounded view keeps whatever colour it is given, with a true corner,
// the glass hairline along its top and its border if it has one.
mod.widgets.RoundedView.draw_bg.pixel = fn() {
let p = self.pos * self.rect_size
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
var fill = self.color
if self.color_2.x > -0.5 {
let dir = if self.gradient_fill_horizontal > 0.5 self.pos.x else self.pos.y
fill = mix(self.color, self.color_2, dir)
}
let lower = vec2(self.border_inset.x, self.border_inset.y)
let upper = self.rect_size - vec2(self.border_inset.z, self.border_inset.w)
let c = (lower + upper) * 0.5
let h = max((upper - lower) * 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)
// A neutral light fill is a surface: it turns to glass, a breath of
// white with its edge; a coloured fill keeps its colour.
let chroma = max(fill.x, max(fill.y, fill.z)) - min(fill.x, min(fill.y, fill.z))
let glassy = step(chroma, 0.06) * step(0.6, Finish.lum(fill.rgb))
let a = Finish.cover(d, px) * fill.w * mix(1.0, 0.1, glassy)
var col = mix(fill.rgb, vec3(1.0, 1.0, 1.0), glassy)
let top = 1.0 - smoothstep(0.0, r + 2.0, p.y - lower.y)
col = mix(col, vec3(1.0, 1.0, 1.0), Finish.band(d, 0.0, px, px) * top * 0.45 * step(0.5, Finish.lum(fill.rgb)))
var acc = vec4(col * a, a)
let edge = Finish.band(d, 0.0, px, px) * glassy * fill.w
acc = Finish.over(acc, vec4(vec3(1.0, 1.0, 1.0) * edge * mix(0.35, 0.9, top), edge * mix(0.35, 0.9, top)))
let line = Finish.ring_out(d, 0.0, px, px) * glassy * fill.w * 0.14
acc = Finish.over(acc, vec4(vec3(0.20, 0.22, 0.30) * line, line))
let bd = Finish.band(d, 0.0, max(self.border_size, 0.0), px) * step(0.001, self.border_size) * self.border_color.w
return Finish.over(acc, vec4(self.border_color.rgb * bd, bd))
}
// LIST ROWS. Rows of 24, no separators. Under the pointer the row brightens
// a little; the chosen row is a pane of blue glass at a fifth, with its
// own hairline on top.
mod.widgets.ListItemOne.height = 24.0
mod.widgets.ListItem.padding = mod.turtle.Inset{left: 12.0 right: 8.0 top: 0.0 bottom: 0.0}
mod.widgets.ListItem.draw_bg.pixel = fn() {
let p = self.pos * self.rect_size
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let b = lq_snap(self.rect_pos, vec2(3.0, 1.0), self.rect_size - vec2(12.0, 1.0), px)
let c = (b.xy + b.zw) * 0.5
let h = (b.zw - b.xy) * 0.5
let d = Material.sd_box(p, c, h, 6.0)
let en = 1.0 - self.disabled
let a = Finish.cover(d, px)
let lift = a * (0.2 * self.hover - 0.06 * self.down) * en * (1.0 - self.selected)
var acc = vec4(max(lift, 0.0), max(lift, 0.0), max(lift, 0.0), abs(lift))
let sa = a * 0.24 * self.selected
acc = Finish.over(acc, vec4(lq_accent() * sa, sa))
let top = Finish.band(d, 0.0, px, px) * smoothstep(0.1, -0.5, (p.y - c.y) / max(h.y, 1.0)) * 0.55 * self.selected
acc = Finish.over(acc, vec4(top, top, top, top))
let bot = Finish.band(d, 0.0, px, px) * smoothstep(0.1, 0.8, (p.y - c.y) / max(h.y, 1.0)) * 0.3 * self.selected
return Finish.over(acc, vec4(0.204 * bot, 0.282 * bot, 0.431 * bot, bot))
}
mod.widgets.ListItem.draw_text.color = #1c1e22
// BADGE. A small pill of clear glass lying on the ground, dark digits; a
// coloured badge keeps its colour behind the same rim.
let lq_badge = fn(pq: vec2, size: vec2, org: vec2, px: float, fill: vec4, radius: float) -> vec4 {
let b = lq_snap(org, vec2(0.0, 0.0), size, px)
let c = (b.xy + b.zw) * 0.5
let h = (b.zw - b.xy) * 0.5
let r = min(radius, min(h.x, h.y))
let light = step(0.6, Finish.lum(fill.rgb))
return lq_glass_body(pq, c, h, r, px, size.y, fill.rgb, 1.0 - light, 0.02 * light, 1.0, mix(1.0, 0.9, light) * fill.w, 1.0, 1.0, 0.06 * size.y + px, 1.0, vec4(0.620, 0.631, 0.659, 0.9), vec3(0.965, 0.973, 0.988))
}
mod.widgets.BadgeFlat.draw_bg.pixel = fn() {
let pq = self.pos * self.rect_size
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return lq_badge(pq, self.rect_size, self.rect_pos, px, self.color, self.radius)
}
mod.widgets.Badge.draw_bg.pixel = fn() {
let pq = self.pos * self.rect_size
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return lq_badge(pq, self.rect_size, self.rect_pos, px, self.color, self.radius)
}
mod.widgets.BadgePalette.neutral = #d6d8dd
mod.widgets.BadgePalette.on_neutral = #1c1e22
mod.widgets.BadgeFlat.draw_text.text_style.font_size = 7.5
mod.widgets.Badge.draw_text.text_style.font_size = 7.5
// THE OPEN LIST. A slab of glass, radius 10; the row under the pointer is
// a pane of blue glass at a quarter with its hairline; the chosen row
// carries a black check at its right end.
mod.widgets.PopupMenuFlat.draw_bg.pixel = fn() {
let pq = self.pos * self.rect_size
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return lq_panel(pq, self.rect_size, self.rect_pos, px, 10.0, 0.0, 0.0, 1.0, 0.0, lq_ground())
}
mod.widgets.PopupMenuItem.draw_bg.pixel = fn() {
let p = self.pos * self.rect_size
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let c = self.rect_size * 0.5
let h = self.rect_size * 0.5 - vec2(2.0, 1.0)
let d = Material.sd_box(p, c, h, 6.0)
let k = self.hover * (1.0 - self.disabled)
let a = Finish.cover(d, px) * 0.25 * k
var acc = vec4(lq_accent() * a, a)
let top = Finish.band(d, 0.0, px, px) * smoothstep(0.1, -0.5, (p.y - c.y) / max(h.y, 1.0)) * 0.5 * k
acc = Finish.over(acc, vec4(top, top, top, top))
let cx = self.rect_size.x - 12.0
let cy = c.y
let ck = Finish.cover(min(lq_seg(p, vec2(cx - 3.5, cy), vec2(cx - 1.0, cy + 2.5)), lq_seg(p, vec2(cx - 1.0, cy + 2.5), vec2(cx + 3.5, cy - 2.5))) - 0.75, px) * self.active
return Finish.over(acc, vec4(vec3(0.110, 0.118, 0.133) * ck, ck))
}
mod.widgets.PopupMenuItem.draw_text.color_active = #0d0f14
// Rules between groups: the seam of the family, one device pixel of shade
// over one of light, never a grey line.
mod.widgets.Hr.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * self.rect_size
let y = Finish.snap(self.rect_pos.y + self.rect_size.y * 0.5, px) - self.rect_pos.y
let dk = step(y - px, p.y) * (1.0 - step(y, p.y)) * 0.12
let lt = step(y, p.y) * (1.0 - step(y + px, p.y)) * 0.4
return Finish.over(vec4(0.165 * dk, 0.192 * dk, 0.251 * dk, dk), vec4(lt, lt, lt, lt))
}
mod.widgets.Vr.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * self.rect_size
let x = Finish.snap(self.rect_pos.x + self.rect_size.x * 0.5, px) - self.rect_pos.x
let dk = step(x - px, p.x) * (1.0 - step(x, p.x)) * 0.12
let lt = step(x, p.x) * (1.0 - step(x + px, p.x)) * 0.4
return Finish.over(vec4(0.165 * dk, 0.192 * dk, 0.251 * dk, dk), vec4(lt, lt, lt, lt))
}
// The spinner turns on the pass clock of its own; it keeps the stock face
// and takes the accent.
mod.widgets.SpinnerFlat.draw_bg.color = #4f7fd1
// Templates that draw a face of their own on top of these roots keep the
// plain quad: the grown one is only for the faces written here.
mod.widgets.CloseButton.draw_bg.vertex = fn() {
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size)
}
mod.widgets.CopyButton.draw_bg.vertex = fn() {
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size)
}
mod.widgets.BurgerButton.draw_bg.vertex = fn() {
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size)
}
mod.widgets.ButtonFloating.draw_bg.vertex = fn() {
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size)
}
mod.widgets.ButtonFloatingSm.draw_bg.vertex = fn() {
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size)
}
mod.widgets.CheckBoxCustom.draw_bg.vertex = fn() {
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size)
}
mod.widgets.glass.CutButton.draw_bg.vertex = fn() {
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size)
}
mod.widgets.glass.RadioButton.draw_bg.vertex = fn() {
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size)
}
// THE GROUND, on his word ('take the blobgenerator ... and use different
// seeds to generate a new picture every time as background image for liquid
// glass'): soft coloured blobs on a white backdrop, in his "blobs"
// settings, laid out anew from a seed each time the sheet is applied (at
// start and at every switch to it; the same seed is the same picture). `mod.blobs.compose` (the sheets crate's port of the composer)
// lays the balls out; the ground's shader reads them as uniforms, no texture:
// per depth layer, back to front, the soft shadow the layer throws on what is
// behind it, then the layer itself, a metaball surface shaded matte as a
// sphere, a cool shade on its dark side and the backdrop's light bounced into
// its rim, a soft glow inside its edge, faded toward the backdrop with depth;
// then a still grain. A layer is skipped wherever it cannot reach (a box round
// its balls), so most of the window reads a few balls, not sixteen. The
// composition is made for 16:9 and stretched to the window as the composer
// stretches it (places with the width, sizes with the root of the aspect).
let lq_blobs = mod.blobs.compose(mod.std.random_u32())
// OKLab to sRGB.
let lq_blob_srgb = fn(c: vec3) -> vec3 {
let l = c.x + 0.3963377774 * c.y + 0.2158037573 * c.z
let m = c.x - 0.1055613458 * c.y - 0.0638541728 * c.z
let s = c.x - 0.0894841775 * c.y - 1.2914855480 * c.z
let l3 = l * l * l
let m3 = m * m * m
let s3 = s * s * s
let lin = vec3(4.0767416621 * l3 - 3.3077115913 * m3 + 0.2309699292 * s3, -1.2684380046 * l3 + 2.6097574011 * m3 - 0.3413193965 * s3, -0.0041960863 * l3 - 0.7034186147 * m3 + 1.7076147010 * s3)
return pow(max(lin, vec3(0.0, 0.0, 0.0)), vec3(0.4545454, 0.4545454, 0.4545454))
}
// The white backdrop: a soft fall from the upper left, darkening a touch
// toward the corners.
let lq_blob_bg = fn(uv: vec2, top: vec3, bottom: vec3) -> vec3 {
let c = mix(bottom, top, smoothstep(-0.7, 0.7, uv.y - uv.x * 0.25))
return c * (1.0 - 0.05 * dot(uv, uv))
}
// One ball's share of the field at `p` (and its gradient): `bp` the ball
// (place and radius, for the design frame), `sx` the stretch of its place to
// the window, `kr` of its radius, `k2` the reach squared. Round alone, liquid
// beside its neighbours.
let lq_blob_f = fn(p: vec2, bp: vec4, sx: float, kr: float, k2: float) -> vec3 {
let d = p - vec2(bp.x * sx, bp.y)
let r = bp.z * kr
let r2 = max(k2 * r * r, 0.0000001)
let q = max(1.0 - dot(d, d) / r2, 0.0) * step(0.0001, r)
let q2 = q * q
let gs = 6.0 * q2 / r2
return vec3(q2 * q, 0.0 - d.x * gs, 0.0 - d.y * gs)
}
// The same ball's colour, weighted toward the nearest ball: its two OKLab
// colours along its gradient (`ba.w`, `bb.w` the gradient's direction) times
// the weight, and the weight.
let lq_blob_c = fn(p: vec2, bp: vec4, ba: vec4, bb: vec4, sx: float, kr: float, k2: float) -> vec4 {
let d = p - vec2(bp.x * sx, bp.y)
let r = bp.z * kr
let r2 = max(k2 * r * r, 0.0000001)
let q = max(1.0 - dot(d, d) / r2, 0.0) * step(0.0001, r)
let v = q * q * q
let wt = v * v * v * v
let t = dot(d / max(r, 0.0001), vec2(ba.w, bb.w)) * 0.5 + 0.5
return vec4(mix(ba.xyz, bb.xyz, smoothstep(0.0, 1.0, t)) * wt, wt)
}
// The same weight times the ball's radius, for the local radius.
let lq_blob_rw = fn(p: vec2, bp: vec4, sx: float, kr: float, k2: float) -> float {
let d = p - vec2(bp.x * sx, bp.y)
let r = bp.z * kr
let r2 = max(k2 * r * r, 0.0000001)
let q = max(1.0 - dot(d, d) / r2, 0.0) * step(0.0001, r)
let v = q * q * q
return v * v * v * v * r
}
// 1 where a layer can reach `p`: inside the box round its balls' centres
// (`bd`: x min, x max, y min, y max), grown by its largest ball's reach and
// its shadow's throw.
let lq_blob_near = fn(p: vec2, bd: vec4, reach: float, throw: float, sx: float, kr: float) -> float {
let m = reach * kr + 0.035 * throw * kr + 0.01
let inx = step(bd.x * sx - m, p.x) * step(p.x, bd.y * sx + m)
let iny = step(bd.z - m, p.y) * step(p.y, bd.w + m)
return inx * iny * step(0.00001, reach)
}
// The surface's colour: matte, lit from the upper left, its dark side
// leaning cool and violet, the backdrop's light bounced into its rim; the
// hue turned a little by a slow swirl.
let lq_blob_shade = fn(lab_in: vec3, n: vec3, uv: vec2, cs: float, swirl: float, seed_s: float) -> vec3 {
let p = uv / cs
let sw = swirl * 0.85 * (sin(p.x * 3.7 + 1.4 * sin(p.y * 2.9) + seed_s) + sin(p.y * 5.3 - p.x * 2.1 + seed_s * 1.7))
let ca = cos(sw)
let sa = sin(sw)
let lab = vec3(lab_in.x, ca * lab_in.y - sa * lab_in.z, sa * lab_in.y + ca * lab_in.z)
let dif = clamp(dot(n, vec3(-0.4975, 0.6169, 0.6965)) * 0.5 + 0.5, 0.0, 1.0)
let lit = vec3(min(1.0, lab.x + 0.08), lab.y, lab.z)
let chroma = length(lab.yz)
let dyz = mix(lab.yz, vec2(0.45, -0.89) * max(chroma, 0.06), 0.32)
let drk = vec3(lab.x - 0.16, dyz.x, dyz.y)
let c = mix(drk, lit, smoothstep(0.05, 0.95, dif))
let fres = pow(1.0 - clamp(n.z, 0.0, 1.0), 2.5)
let bounce = fres * clamp(dot(n.xy, vec2(0.55, -0.83)) * 0.5 + 0.5, 0.0, 1.0)
let cl = mix(c.x, 0.97, bounce * 0.35)
let cab = c.yz * (1.0 - bounce * 0.35)
return lq_blob_srgb(vec3(cl, cab.x, cab.y))
}
// One layer laid over what is behind it: `fs` its field where its shadow is
// thrown from, `fc` its field here, `ac` its weighted colour and `rw` its
// weighted radius; `la` blur, fade, shadow strength, shadow distance; `lb`
// the shadow's tint.
let lq_blob_layer = fn(col: vec3, bg: vec3, uv: vec2, aa: float, iso: float, fs: vec3, fc: vec3, ac: vec4, rw: float, la: vec4, lb: vec4, kr: float, cs: float, swirl: float, seed_s: float, refl: vec4) -> vec3 {
let sds = (fs.x - iso) / max(length(fs.yz), 0.0001)
let sb = 0.05 * la.w * kr + la.x
let sa = smoothstep(0.0 - sb, sb * 0.4, sds) * la.z
let under = col * mix(vec3(1.0, 1.0, 1.0), lb.xyz, sa)
if fc.x < 0.0000001 {
return under
}
let inv = 1.0 / max(ac.w, 0.0000000001)
let lab = ac.xyz * inv
let rb = max(rw * inv, 0.0001)
let gn = max(length(fc.yz), 0.00001)
let gd = fc.yz / gn
let sd = (fc.x - iso) / gn
let a = smoothstep(0.0 - la.x - aa, la.x + aa, sd)
let dc = clamp(rb - sd * 0.85, 0.0, rb)
let n = normalize(vec3(0.0 - gd.x * dc, 0.0 - gd.y * dc, sqrt(max(rb * rb - dc * dc, 0.0)) + 0.0001))
var c = lq_blob_shade(lab, n, uv, cs, swirl, seed_s)
// The glow: a Fresnel falloff inside the edge, brighter facing the light.
let facing = dot(vec2(0.0 - gd.x, 0.0 - gd.y), vec2(-0.63, 0.78))
let e = clamp(sd * 0.85 / (rb * (0.12 + refl.y * 3.0)), 0.0, 1.0)
let glow = pow(1.0 - sqrt(e * (2.0 - e)), mix(7.0, 1.2, clamp(refl.z * 5.0, 0.0, 1.0))) * (0.7 + 0.3 * facing)
let r = clamp(glow * refl.x, 0.0, 1.0)
let gc = mix(vec3(0.97, 0.97, 0.97), lq_blob_srgb(vec3(0.93, lab.y * 1.4, lab.z * 1.4)), refl.w)
c = vec3(1.0, 1.0, 1.0) - (vec3(1.0, 1.0, 1.0) - c) * (vec3(1.0, 1.0, 1.0) - gc * r)
c = mix(c, bg, la.y)
return mix(under, c, a)
}
// The composition's uniforms on a draw: the balls, the layers, the look.
let lq_blob_uniforms = fn(d) {
d.blob_p0 = uniform(lq_blobs.p0)
d.blob_a0 = uniform(lq_blobs.a0)
d.blob_b0 = uniform(lq_blobs.b0)
d.blob_p1 = uniform(lq_blobs.p1)
d.blob_a1 = uniform(lq_blobs.a1)
d.blob_b1 = uniform(lq_blobs.b1)
d.blob_p2 = uniform(lq_blobs.p2)
d.blob_a2 = uniform(lq_blobs.a2)
d.blob_b2 = uniform(lq_blobs.b2)
d.blob_p3 = uniform(lq_blobs.p3)
d.blob_a3 = uniform(lq_blobs.a3)
d.blob_b3 = uniform(lq_blobs.b3)
d.blob_p4 = uniform(lq_blobs.p4)
d.blob_a4 = uniform(lq_blobs.a4)
d.blob_b4 = uniform(lq_blobs.b4)
d.blob_p5 = uniform(lq_blobs.p5)
d.blob_a5 = uniform(lq_blobs.a5)
d.blob_b5 = uniform(lq_blobs.b5)
d.blob_p6 = uniform(lq_blobs.p6)
d.blob_a6 = uniform(lq_blobs.a6)
d.blob_b6 = uniform(lq_blobs.b6)
d.blob_p7 = uniform(lq_blobs.p7)
d.blob_a7 = uniform(lq_blobs.a7)
d.blob_b7 = uniform(lq_blobs.b7)
d.blob_p8 = uniform(lq_blobs.p8)
d.blob_a8 = uniform(lq_blobs.a8)
d.blob_b8 = uniform(lq_blobs.b8)
d.blob_p9 = uniform(lq_blobs.p9)
d.blob_a9 = uniform(lq_blobs.a9)
d.blob_b9 = uniform(lq_blobs.b9)
d.blob_p10 = uniform(lq_blobs.p10)
d.blob_a10 = uniform(lq_blobs.a10)
d.blob_b10 = uniform(lq_blobs.b10)
d.blob_p11 = uniform(lq_blobs.p11)
d.blob_a11 = uniform(lq_blobs.a11)
d.blob_b11 = uniform(lq_blobs.b11)
d.blob_p12 = uniform(lq_blobs.p12)
d.blob_a12 = uniform(lq_blobs.a12)
d.blob_b12 = uniform(lq_blobs.b12)
d.blob_p13 = uniform(lq_blobs.p13)
d.blob_a13 = uniform(lq_blobs.a13)
d.blob_b13 = uniform(lq_blobs.b13)
d.blob_p14 = uniform(lq_blobs.p14)
d.blob_a14 = uniform(lq_blobs.a14)
d.blob_b14 = uniform(lq_blobs.b14)
d.blob_p15 = uniform(lq_blobs.p15)
d.blob_a15 = uniform(lq_blobs.a15)
d.blob_b15 = uniform(lq_blobs.b15)
d.blob_la0 = uniform(lq_blobs.la0)
d.blob_lb0 = uniform(lq_blobs.lb0)
d.blob_bd0 = uniform(lq_blobs.bd0)
d.blob_la1 = uniform(lq_blobs.la1)
d.blob_lb1 = uniform(lq_blobs.lb1)
d.blob_bd1 = uniform(lq_blobs.bd1)
d.blob_la2 = uniform(lq_blobs.la2)
d.blob_lb2 = uniform(lq_blobs.lb2)
d.blob_bd2 = uniform(lq_blobs.bd2)
d.blob_la3 = uniform(lq_blobs.la3)
d.blob_lb3 = uniform(lq_blobs.lb3)
d.blob_bd3 = uniform(lq_blobs.bd3)
d.blob_la4 = uniform(lq_blobs.la4)
d.blob_lb4 = uniform(lq_blobs.lb4)
d.blob_bd4 = uniform(lq_blobs.bd4)
d.blob_glob = uniform(lq_blobs.glob)
d.blob_look = uniform(lq_blobs.look)
d.blob_refl = uniform(lq_blobs.refl)
d.blob_bg_top = uniform(lq_blobs.bg_top)
d.blob_bg_bottom = uniform(lq_blobs.bg_bottom)
}
// What a pane of glass needs of it: the balls' places and softened colours.
let lq_blob_soft_uniforms = fn(d) {
d.blob_p0 = uniform(lq_blobs.p0)
d.blob_s0 = uniform(lq_blobs.s0)
d.blob_p1 = uniform(lq_blobs.p1)
d.blob_s1 = uniform(lq_blobs.s1)
d.blob_p2 = uniform(lq_blobs.p2)
d.blob_s2 = uniform(lq_blobs.s2)
d.blob_p3 = uniform(lq_blobs.p3)
d.blob_s3 = uniform(lq_blobs.s3)
d.blob_p4 = uniform(lq_blobs.p4)
d.blob_s4 = uniform(lq_blobs.s4)
d.blob_p5 = uniform(lq_blobs.p5)
d.blob_s5 = uniform(lq_blobs.s5)
d.blob_p6 = uniform(lq_blobs.p6)
d.blob_s6 = uniform(lq_blobs.s6)
d.blob_p7 = uniform(lq_blobs.p7)
d.blob_s7 = uniform(lq_blobs.s7)
d.blob_p8 = uniform(lq_blobs.p8)
d.blob_s8 = uniform(lq_blobs.s8)
d.blob_p9 = uniform(lq_blobs.p9)
d.blob_s9 = uniform(lq_blobs.s9)
d.blob_p10 = uniform(lq_blobs.p10)
d.blob_s10 = uniform(lq_blobs.s10)
d.blob_p11 = uniform(lq_blobs.p11)
d.blob_s11 = uniform(lq_blobs.s11)
d.blob_p12 = uniform(lq_blobs.p12)
d.blob_s12 = uniform(lq_blobs.s12)
d.blob_p13 = uniform(lq_blobs.p13)
d.blob_s13 = uniform(lq_blobs.s13)
d.blob_p14 = uniform(lq_blobs.p14)
d.blob_s14 = uniform(lq_blobs.s14)
d.blob_p15 = uniform(lq_blobs.p15)
d.blob_s15 = uniform(lq_blobs.s15)
d.blob_glob = uniform(lq_blobs.glob)
d.blob_bg_top = uniform(lq_blobs.bg_top)
d.blob_bg_bottom = uniform(lq_blobs.bg_bottom)
}
mod.widgets.Window.show_bg = true
lq_blob_uniforms(mod.widgets.Window.draw_bg)
mod.widgets.Window.draw_bg.pixel = fn() {
let size = max(self.rect_size, vec2(1.0, 1.0))
let pq = self.pos * size
let uv = vec2(pq.x - 0.5 * size.x, 0.5 * size.y - pq.y) / size.y
let asp = size.x / size.y
let cs = clamp(sqrt(asp), 0.6, 1.6)
let sx = asp / 1.7777778
let kr = cs / 1.3333333
let aa = 1.2 / size.y
let iso = self.blob_glob.y
let k2 = self.blob_glob.x * self.blob_glob.x
let bg = lq_blob_bg(uv, self.blob_bg_top.xyz, self.blob_bg_bottom.xyz)
var col = bg
// the back layer's edge blobs
if lq_blob_near(uv, self.blob_bd0, self.blob_lb0.w, self.blob_la0.w, sx, kr) > 0.5 {
let so = uv - vec2(0.016, -0.028) * (self.blob_la0.w * kr)
let fs = lq_blob_f(so, self.blob_p0, sx, kr, k2) + lq_blob_f(so, self.blob_p1, sx, kr, k2) + lq_blob_f(so, self.blob_p2, sx, kr, k2) + lq_blob_f(so, self.blob_p3, sx, kr, k2)
let fc = lq_blob_f(uv, self.blob_p0, sx, kr, k2) + lq_blob_f(uv, self.blob_p1, sx, kr, k2) + lq_blob_f(uv, self.blob_p2, sx, kr, k2) + lq_blob_f(uv, self.blob_p3, sx, kr, k2)
let ac = lq_blob_c(uv, self.blob_p0, self.blob_a0, self.blob_b0, sx, kr, k2) + lq_blob_c(uv, self.blob_p1, self.blob_a1, self.blob_b1, sx, kr, k2) + lq_blob_c(uv, self.blob_p2, self.blob_a2, self.blob_b2, sx, kr, k2) + lq_blob_c(uv, self.blob_p3, self.blob_a3, self.blob_b3, sx, kr, k2)
let rw = lq_blob_rw(uv, self.blob_p0, sx, kr, k2) + lq_blob_rw(uv, self.blob_p1, sx, kr, k2) + lq_blob_rw(uv, self.blob_p2, sx, kr, k2) + lq_blob_rw(uv, self.blob_p3, sx, kr, k2)
col = lq_blob_layer(col, bg, uv, aa, iso, fs, fc, ac, rw, self.blob_la0, self.blob_lb0, kr, cs, self.blob_glob.z, self.blob_look.x, self.blob_refl)
}
// the middle layer's edge blobs
if lq_blob_near(uv, self.blob_bd1, self.blob_lb1.w, self.blob_la1.w, sx, kr) > 0.5 {
let so = uv - vec2(0.016, -0.028) * (self.blob_la1.w * kr)
let fs = lq_blob_f(so, self.blob_p4, sx, kr, k2) + lq_blob_f(so, self.blob_p5, sx, kr, k2)
let fc = lq_blob_f(uv, self.blob_p4, sx, kr, k2) + lq_blob_f(uv, self.blob_p5, sx, kr, k2)
let ac = lq_blob_c(uv, self.blob_p4, self.blob_a4, self.blob_b4, sx, kr, k2) + lq_blob_c(uv, self.blob_p5, self.blob_a5, self.blob_b5, sx, kr, k2)
let rw = lq_blob_rw(uv, self.blob_p4, sx, kr, k2) + lq_blob_rw(uv, self.blob_p5, sx, kr, k2)
col = lq_blob_layer(col, bg, uv, aa, iso, fs, fc, ac, rw, self.blob_la1, self.blob_lb1, kr, cs, self.blob_glob.z, self.blob_look.x, self.blob_refl)
}
// the second mass
if lq_blob_near(uv, self.blob_bd2, self.blob_lb2.w, self.blob_la2.w, sx, kr) > 0.5 {
let so = uv - vec2(0.016, -0.028) * (self.blob_la2.w * kr)
let fs = lq_blob_f(so, self.blob_p6, sx, kr, k2) + lq_blob_f(so, self.blob_p7, sx, kr, k2) + lq_blob_f(so, self.blob_p8, sx, kr, k2)
let fc = lq_blob_f(uv, self.blob_p6, sx, kr, k2) + lq_blob_f(uv, self.blob_p7, sx, kr, k2) + lq_blob_f(uv, self.blob_p8, sx, kr, k2)
let ac = lq_blob_c(uv, self.blob_p6, self.blob_a6, self.blob_b6, sx, kr, k2) + lq_blob_c(uv, self.blob_p7, self.blob_a7, self.blob_b7, sx, kr, k2) + lq_blob_c(uv, self.blob_p8, self.blob_a8, self.blob_b8, sx, kr, k2)
let rw = lq_blob_rw(uv, self.blob_p6, sx, kr, k2) + lq_blob_rw(uv, self.blob_p7, sx, kr, k2) + lq_blob_rw(uv, self.blob_p8, sx, kr, k2)
col = lq_blob_layer(col, bg, uv, aa, iso, fs, fc, ac, rw, self.blob_la2, self.blob_lb2, kr, cs, self.blob_glob.z, self.blob_look.x, self.blob_refl)
}
// the first mass and the floating spheres
if lq_blob_near(uv, self.blob_bd3, self.blob_lb3.w, self.blob_la3.w, sx, kr) > 0.5 {
let so = uv - vec2(0.016, -0.028) * (self.blob_la3.w * kr)
let fs = lq_blob_f(so, self.blob_p9, sx, kr, k2) + lq_blob_f(so, self.blob_p10, sx, kr, k2) + lq_blob_f(so, self.blob_p11, sx, kr, k2) + lq_blob_f(so, self.blob_p12, sx, kr, k2) + lq_blob_f(so, self.blob_p13, sx, kr, k2)
let fc = lq_blob_f(uv, self.blob_p9, sx, kr, k2) + lq_blob_f(uv, self.blob_p10, sx, kr, k2) + lq_blob_f(uv, self.blob_p11, sx, kr, k2) + lq_blob_f(uv, self.blob_p12, sx, kr, k2) + lq_blob_f(uv, self.blob_p13, sx, kr, k2)
let ac = lq_blob_c(uv, self.blob_p9, self.blob_a9, self.blob_b9, sx, kr, k2) + lq_blob_c(uv, self.blob_p10, self.blob_a10, self.blob_b10, sx, kr, k2) + lq_blob_c(uv, self.blob_p11, self.blob_a11, self.blob_b11, sx, kr, k2) + lq_blob_c(uv, self.blob_p12, self.blob_a12, self.blob_b12, sx, kr, k2) + lq_blob_c(uv, self.blob_p13, self.blob_a13, self.blob_b13, sx, kr, k2)
let rw = lq_blob_rw(uv, self.blob_p9, sx, kr, k2) + lq_blob_rw(uv, self.blob_p10, sx, kr, k2) + lq_blob_rw(uv, self.blob_p11, sx, kr, k2) + lq_blob_rw(uv, self.blob_p12, sx, kr, k2) + lq_blob_rw(uv, self.blob_p13, sx, kr, k2)
col = lq_blob_layer(col, bg, uv, aa, iso, fs, fc, ac, rw, self.blob_la3, self.blob_lb3, kr, cs, self.blob_glob.z, self.blob_look.x, self.blob_refl)
}
// the corner blobs in front
if lq_blob_near(uv, self.blob_bd4, self.blob_lb4.w, self.blob_la4.w, sx, kr) > 0.5 {
let so = uv - vec2(0.016, -0.028) * (self.blob_la4.w * kr)
let fs = lq_blob_f(so, self.blob_p14, sx, kr, k2) + lq_blob_f(so, self.blob_p15, sx, kr, k2)
let fc = lq_blob_f(uv, self.blob_p14, sx, kr, k2) + lq_blob_f(uv, self.blob_p15, sx, kr, k2)
let ac = lq_blob_c(uv, self.blob_p14, self.blob_a14, self.blob_b14, sx, kr, k2) + lq_blob_c(uv, self.blob_p15, self.blob_a15, self.blob_b15, sx, kr, k2)
let rw = lq_blob_rw(uv, self.blob_p14, sx, kr, k2) + lq_blob_rw(uv, self.blob_p15, sx, kr, k2)
col = lq_blob_layer(col, bg, uv, aa, iso, fs, fc, ac, rw, self.blob_la4, self.blob_lb4, kr, cs, self.blob_glob.z, self.blob_look.x, self.blob_refl)
}
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let gq = fract((self.rect_pos + pq) / px * vec2(0.1234, 0.4567))
let gh = gq + vec2(dot(gq, gq + vec2(45.32, 45.32)), dot(gq, gq + vec2(45.32, 45.32)))
let grain = fract(gh.x * gh.y) - 0.5
col = col + vec3(grain, grain, grain) * self.blob_glob.w
return vec4(clamp(col, vec3(0.0, 0.0, 0.0), vec3(1.0, 1.0, 1.0)), 1.0)
}
mod.widgets.Window.pass.clear_color = #ececf1
// THE PANES, on his word ('the ui will be on transparent panels on top of
// the background just like frost glass, only the glass is not dark this
// time'): a panel is a pane of light glass. Through it the ground's colours
// show softly (the pools above, not a capture of the window: the pane stays
// in the window's body, so what is drawn on it is in the window's scene for
// the screens' water to bend), lightened without greying (lq_glass_tint)
// and lifted further only where it would still be too dark for the ink
// (lq_ink_floor); its edge bends the view
// outward over the last ten points; a soft white glow inside its edge; one
// device pixel of bright rim, whitest
// at the top left, a faint cool line outside it and a soft cool shadow under
// it so it holds its edge over the white backdrop; radius 12. Between the
// panes the ground shows sharp. An inset panel is the tray's lines alone,
// the glass showing through it.
//
// The glass keeps its colour and lightens it only as far as the ink needs: a
// colour below `floor` (relative luminance) is first brightened as it is,
// its hue kept, until a channel is full, and only the rest is milk. At 0.35
// the ink reads on the darkest glass as it did under the old thick milk.
let lq_ink_floor = fn(c: vec3, floor: float) -> vec3 {
let lin = pow(max(c, vec3(0.0001, 0.0001, 0.0001)), vec3(2.2, 2.2, 2.2))
let y = max(dot(lin, vec3(0.2126, 0.7152, 0.0722)), 0.0001)
let k = clamp(floor / y, 1.0, 1.0 / max(max(lin.x, max(lin.y, lin.z)), 0.0001))
let t = max(floor - y * k, 0.0) / max(1.0 - y * k, 0.0001)
return pow(mix(lin * k, vec3(1.0, 1.0, 1.0), t), vec3(0.4545454, 0.4545454, 0.4545454))
}
// sRGB to OKLab (the inverse of lq_blob_srgb).
let lq_blob_oklab = fn(c: vec3) -> vec3 {
let lin = pow(max(c, vec3(0.0, 0.0, 0.0)), vec3(2.2, 2.2, 2.2))
let l = pow(max(0.4122214708 * lin.x + 0.5363325363 * lin.y + 0.0514459929 * lin.z, 0.0), 0.3333333)
let m = pow(max(0.2119034982 * lin.x + 0.6806995451 * lin.y + 0.1073969566 * lin.z, 0.0), 0.3333333)
let s = pow(max(0.0883024619 * lin.x + 0.2817188376 * lin.y + 0.6299787005 * lin.z, 0.0), 0.3333333)
return vec3(0.2104542553 * l + 0.7936177850 * m - 0.0040720468 * s, 1.9779984951 * l - 2.4285922050 * m + 0.4505937099 * s, 0.0259040371 * l + 0.7827717662 * m - 0.8086757660 * s)
}
// What the glass makes of the colour behind it: lighter, as a milk would
// make it, but in OKLab, so it lightens without greying: its lightness goes
// 0.45 of the way to white and its colour keeps 0.85 of its strength.
let lq_glass_tint = fn(c: vec3) -> vec3 {
let lab = lq_blob_oklab(c)
return clamp(lq_blob_srgb(vec3(mix(lab.x, 1.0, 0.45), lab.y * 0.85, lab.z * 0.85)), vec3(0.0, 0.0, 0.0), vec3(1.0, 1.0, 1.0))
}
lq_blob_soft_uniforms(mod.widgets.PanelView.draw_bg)
mod.widgets.PanelView.draw_bg.pixel = fn() {
let m = 16.0
let pq = 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)
if self.sunken > 0.5 {
return lq_unfill(lq_panel(pq, self.rect_size, self.rect_pos, px, 10.0, 0.0, 1.0, 0.0, 0.0, vec3(0.0, 0.0, 0.0)), lq_panel(pq, self.rect_size, self.rect_pos, px, 10.0, 0.0, 1.0, 0.0, 0.0, vec3(1.0, 1.0, 1.0)), 0.06)
}
let b = lq_snap(self.rect_pos, vec2(0.0, 0.0), self.rect_size, px)
let c = (b.xy + b.zw) * 0.5
let h = (b.zw - b.xy) * 0.5
let rr = min(12.0, min(h.x, h.y))
let d = Material.sd_box(pq, c, h, rr)
let n = lq_norm(pq, c, h, rr)
// The window's size, from the pass's projection (two over each side).
let pr = self.draw_pass.camera_projection * vec4(1.0, -1.0, 0.0, 0.0)
let ws = vec2(2.0 / max(abs(pr.x), 0.000001), 2.0 / max(abs(pr.y), 0.000001))
let bevel = 1.0 - clamp(max(0.0 - d, 0.0) / 10.0, 0.0, 1.0)
let wp = self.rect_pos + pq + n * (5.0 * bevel * bevel)
// The ground as the glass shows it: the backdrop and every layer's
// blobs, their shapes kept and their edges softened over about five
// hundredths of the window's height either side, flat in each ball's
// mean colour, layer over layer, each paler with depth, at the point of
// the window behind the pane.
let wsz = max(ws, vec2(1.0, 1.0))
let uv = vec2(wp.x - 0.5 * wsz.x, 0.5 * wsz.y - wp.y) / wsz.y
let asp = wsz.x / wsz.y
let cs = clamp(sqrt(asp), 0.6, 1.6)
let sx = asp / 1.7777778
let kr = cs / 1.3333333
let k2 = self.blob_glob.x * self.blob_glob.x
let iso = self.blob_glob.y
var soft = lq_blob_bg(uv, self.blob_bg_top.xyz, self.blob_bg_bottom.xyz)
let g0 = lq_blob_f(uv, self.blob_p0, sx, kr, k2)
let g1 = lq_blob_f(uv, self.blob_p1, sx, kr, k2)
let g2 = lq_blob_f(uv, self.blob_p2, sx, kr, k2)
let g3 = lq_blob_f(uv, self.blob_p3, sx, kr, k2)
let f0 = g0 + g1 + g2 + g3
let e0 = (f0.x - iso) / max(length(f0.yz), 0.0001)
soft = mix(soft, (self.blob_s0.xyz * g0.x + self.blob_s1.xyz * g1.x + self.blob_s2.xyz * g2.x + self.blob_s3.xyz * g3.x) / max(f0.x, 0.0000001), smoothstep(-0.05, 0.05, e0) * step(0.0000001, f0.x) * (1.0 - 0.8 * self.blob_s0.w))
let g4 = lq_blob_f(uv, self.blob_p4, sx, kr, k2)
let g5 = lq_blob_f(uv, self.blob_p5, sx, kr, k2)
let f1 = g4 + g5
let e1 = (f1.x - iso) / max(length(f1.yz), 0.0001)
soft = mix(soft, (self.blob_s4.xyz * g4.x + self.blob_s5.xyz * g5.x) / max(f1.x, 0.0000001), smoothstep(-0.05, 0.05, e1) * step(0.0000001, f1.x) * (1.0 - 0.8 * self.blob_s4.w))
let g6 = lq_blob_f(uv, self.blob_p6, sx, kr, k2)
let g7 = lq_blob_f(uv, self.blob_p7, sx, kr, k2)
let g8 = lq_blob_f(uv, self.blob_p8, sx, kr, k2)
let f2 = g6 + g7 + g8
let e2 = (f2.x - iso) / max(length(f2.yz), 0.0001)
soft = mix(soft, (self.blob_s6.xyz * g6.x + self.blob_s7.xyz * g7.x + self.blob_s8.xyz * g8.x) / max(f2.x, 0.0000001), smoothstep(-0.05, 0.05, e2) * step(0.0000001, f2.x) * (1.0 - 0.8 * self.blob_s6.w))
let g9 = lq_blob_f(uv, self.blob_p9, sx, kr, k2)
let g10 = lq_blob_f(uv, self.blob_p10, sx, kr, k2)
let g11 = lq_blob_f(uv, self.blob_p11, sx, kr, k2)
let g12 = lq_blob_f(uv, self.blob_p12, sx, kr, k2)
let g13 = lq_blob_f(uv, self.blob_p13, sx, kr, k2)
let f3 = g9 + g10 + g11 + g12 + g13
let e3 = (f3.x - iso) / max(length(f3.yz), 0.0001)
soft = mix(soft, (self.blob_s9.xyz * g9.x + self.blob_s10.xyz * g10.x + self.blob_s11.xyz * g11.x + self.blob_s12.xyz * g12.x + self.blob_s13.xyz * g13.x) / max(f3.x, 0.0000001), smoothstep(-0.05, 0.05, e3) * step(0.0000001, f3.x) * (1.0 - 0.8 * self.blob_s9.w))
let g14 = lq_blob_f(uv, self.blob_p14, sx, kr, k2)
let g15 = lq_blob_f(uv, self.blob_p15, sx, kr, k2)
let f4 = g14 + g15
let e4 = (f4.x - iso) / max(length(f4.yz), 0.0001)
soft = mix(soft, (self.blob_s14.xyz * g14.x + self.blob_s15.xyz * g15.x) / max(f4.x, 0.0000001), smoothstep(-0.05, 0.05, e4) * step(0.0000001, f4.x) * (1.0 - 0.8 * self.blob_s14.w))
var col = lq_ink_floor(lq_glass_tint(soft), 0.35) + vec3(0.012, 0.012, 0.016)
col = col + vec3(lq_grain(self.rect_pos + pq, px))
let tl = normalize(vec2(-1.0, -1.0))
let lit = 0.45 + 0.55 * pow(max(dot(n, tl), 0.0), 2.0) + 0.3 * pow(max(dot(n, vec2(0.0, 0.0) - tl), 0.0), 2.0)
// The inner glow: light caught in the glass along its edge, a white that
// fades inward, most of it within seven points and a breath of it over
// twenty-four, brightest where the rim is.
let din = max(0.0 - d, 0.0)
col = mix(col, vec3(1.0, 1.0, 1.0), (0.45 * exp(0.0 - din / 7.0) + 0.18 * exp(0.0 - din / 24.0)) * (0.6 + 0.4 * min(lit, 1.0)))
// The rim: one device pixel, whitest at the top left, then the bottom
// right, softer between.
col = mix(col, vec3(1.0, 1.0, 1.0), Finish.band(d, 0.0, px, px) * min(lit, 1.0))
col = mix(col, vec3(1.0, 1.0, 1.0), Finish.band(d, px, 2.0 * px, px) * 0.25 * max(dot(n, tl), 0.0))
// Under it, a soft cool shadow; outside it, a faint cool line.
let sh = Finish.drop(pq, c, h - vec2(1.0, 1.0), max(rr - 1.0, 0.0), vec2(0.0, 3.0), 5.0) * 0.10 * (1.0 - Finish.cover(d, px))
var acc = vec4(vec3(0.165, 0.192, 0.251) * sh, sh)
let line = Finish.ring_out(d, 0.0, px, px) * 0.16
acc = Finish.over(acc, vec4(vec3(0.20, 0.22, 0.30) * line, line))
let a = Finish.cover(d, px)
return Finish.over(acc, vec4(col * a, a))
}
// The panes and the faders are drawn in place, never on an overlay of their
// own (`backdrop`): the screens' and the meter's water lifts itself over
// what lies under it, to bend it, only outside an overlay. Said here, not
// left to the default, because a switch from a sheet that turns it on
// (frosted) leaves its value on the parts already built.
mod.widgets.PanelView.backdrop = false
mod.widgets.SliderFader.backdrop = false
// The gutter a splitter keeps between two panes is a gap where the ground
// shows.
mod.widgets.Splitter.draw_bg.color_bg = #0000
// The rows of a tree lie on the glass: clear, every other one a breath of
// white; the open row is blue glass at a quarter.
mod.widgets.FileTreeNode.draw_bg.color_1 = instance(#ffffff00)
mod.widgets.FileTreeNode.draw_bg.color_2 = instance(#ffffff1a)
mod.widgets.FileTreeNode.draw_bg.color_active = instance(#4f7fd140)
// 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}}
// A ROW OF FIELDS IS ONE HEIGHT: the box metrics are stated once here and
// handed to each field below. Twenty-six, the row the buttons stand in; a
// capsule corner, half of it.
let field_height = 26.0
let field_pad_v = 6.0
let field_radius = 13.0
let field_border = 0.67
// The room a Fit field takes: where a field is Fit, the padding IS its
// height.
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.
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 takes
// the height alone: a padding would displace its line.
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)
mod.widgets.NumberField.draw_bg.border_radius = field_radius
field_frame(mod.widgets.ValueInput)
field_frame(mod.widgets.DateField)
field_frame(mod.widgets.TimeField)
field_frame(mod.widgets.DatePicker)
field_frame(mod.widgets.DateRangePicker)
// The chrome-less input a well holds must impose no height of its own.
mod.widgets.WellInput.min_height = 0.0
// The standing pills clip what they hold to themselves, so their label
// knows where in the glass it lies (its `draw_clip` is the pill). Their own
// quad grows 20 points for the shadow, which fades out before the quad's
// edge: wherever the clip is only the pill's own edge it reaches past it by
// that much; where a container clips into the pill, it keeps to that clip.
let lq_pill_room = fn(w) {
w.clip_x = true
w.clip_y = true
w.draw_bg.vertex = fn() {
let m = 20.0
let lo = self.rect_pos - vec2(m, m)
let sz = self.rect_size + vec2(2.0 * m, 2.0 * m)
let hi = self.rect_pos + self.rect_size
let own0 = vec2(step(abs(self.draw_clip.x - self.rect_pos.x), 0.01), step(abs(self.draw_clip.y - self.rect_pos.y), 0.01))
let own1 = vec2(step(abs(self.draw_clip.z - hi.x), 0.01), step(abs(self.draw_clip.w - hi.y), 0.01))
let c0 = self.draw_clip.xy - own0 * m
let c1 = self.draw_clip.zw + own1 * m
let p = self.geom.pos * sz + lo
let pc = clamp(clamp(p, c0, c1) + self.draw_list.view_shift, self.draw_list.view_clip.xy, self.draw_list.view_clip.zw)
self.pos = (pc - self.draw_list.view_shift - lo) / sz
self.world = self.draw_list.view_transform * vec4(pc.x, pc.y, self.draw_depth + self.draw_call.zbias, 1.0)
self.vertex_pos = self.draw_pass.camera_projection * (self.draw_pass.camera_view * self.world)
}
}
// THE LABEL INSIDE THE GLASS. The label reads as printed on the glass's
// back face, under its surface: the glass's upper sheen passes over the
// letters and lightens them where it crosses (0.22 of the way to white at
// the top of the pill, gone by six tenths of its height, a little more
// toward the left where the light is); the ink is softened toward the glass
// (0.06); a soft shadow of the letters lies on the body 0.7 points under
// them (0.16; on coloured glass 0.40, in a deeper shade of its colour); and a
// white label on coloured glass takes the colour at its edges (0.55). The
// glass's colour is the label's `material_glow_ink`, which this sheet's
// label colour does not otherwise read. Out of use the label is plain.
// The press's ring bends the letters as it passes under them: the same
// ring the glass draws (lq_pill: its centre, reach, front and life, from
// the same `down` and press clock), its swell (the push the glass's lens
// takes, widened to 0.40 of the reach) moving the letters out along its radius
// as it passes, 15.0 points at a 44 point button times the swell, so they bulge
// and sway with it and stand exactly as they were once it has gone. The
// swell is wide so the push can be large and still change by no more than
// about six tenths of a point a point, past which the letters fold over
// themselves; it eases to nothing within some fifteen points of the pill's
// middle, so the letters there do not tear.
// The glyph's quad grows 5.0 points every way so a letter pushed out of its
// cell is still drawn; its shadow keeps to the room it had (a point to the
// sides, two below). A glyph from the curve atlas keeps the platform's own
// quad and is drawn plain.
let lq_text_base = mod.draw.DrawText.vertex
let lq_label_inside = fn(w) {
w.draw_text.lq_clock = instance(0.0)
w.draw_text.ink_vertex = lq_text_base
w.draw_text.vertex = fn() {
self.ink_vertex()
if self.texture_index < 2.5 {
let lo = self.rect_pos - vec2(5.0, 5.0)
let hi = self.rect_pos + self.rect_size + vec2(5.0, 5.0)
let p = mix(lo, hi, self.geom.pos)
let pc = clamp(clamp(p, self.draw_clip.xy, self.draw_clip.zw) + self.draw_list.view_shift, self.draw_list.view_clip.xy, self.draw_list.view_clip.zw)
let pn = (pc - self.draw_list.view_shift - self.rect_pos) / max(self.rect_size, vec2(0.0001, 0.0001))
self.pos = pn
self.t = mix(self.t_min, self.t_max, pn)
self.world = self.draw_list.view_transform * vec4(pc.x, pc.y, self.glyph_depth + self.draw_call.zbias, 1.0)
self.vertex_pos = self.draw_pass.camera_projection * (self.draw_pass.camera_view * self.world)
}
}
w.draw_text.pixel = fn() {
// A glyph from the curve atlas (3) or one not yet in an atlas (-1)
// is drawn plain, by the platform's own sampling.
if self.texture_index > 2.5 || self.texture_index < -0.5 {
return self.sample_text_pixel()
}
let rs = max(self.rect_size, vec2(0.0001, 0.0001))
let q = self.pos * rs
let c = self.get_color()
let dpi = max(self.draw_pass.dpi_factor, 0.5)
let en = 1.0 - self.disabled
// Where in the pill this pixel lies.
let b0 = self.draw_clip.xy
let b1 = self.draw_clip.zw
let pp = self.rect_pos + q
// The press's ring, as the glass draws it (lq_pill).
let held = step(0.999, self.down)
let still = step(self.down + 0.001, self.lq_clock)
let going = (1.0 - held) * (1.0 - still) * step(0.0005, self.down)
let tpr = clamp(self.lq_clock * 1.6 / 1.17, 0.0, 1.0)
let fp = tpr * tpr * (3.0 - 2.0 * tpr)
let age = mix(mix(1000.0, (1.0 - self.down) * 1.6, going), self.lq_clock * 1.6, held)
let strength = clamp(1.0 - age / 1.6, 0.0, 1.0) * mix(0.9, 1.0 - 0.1 * fp, held) * en
let pc = (b0 + b1) * 0.5
let ph = max((b1 - b0) * 0.5, vec2(1.0, 1.0))
let kk = min(ph.x, ph.y) / 22.0
let lp = (pp - pc) / (ph + vec2(19.4 * kk, 19.4 * kk))
let dist = length(lp)
let wt = clamp(age / 1.32, 0.0, 1.0)
let centre = 1.25 * wt * wt * (3.0 - 2.0 * wt)
let delta = dist - centre
let life = clamp(1.0 - age / 1.6, 0.0, 1.0)
let wave = exp(-(delta * delta) / (0.40 * 0.40)) * life * life * strength * smoothstep(0.0, 0.10, age) * (1.0 - smoothstep(1.16, 1.44, dist))
let rq = pp - pc
let rdir = rq / sqrt(dot(rq, rq) + 256.0)
let bend = rdir * (wave * 15.0 * kk)
// The letters, read from where the ring pushes them.
let qs = q - bend
let inq = step(0.0, qs.x) * step(qs.x, rs.x) * step(0.0, qs.y) * step(qs.y, rs.y)
let dtx = dFdx(self.t)
let dty = dFdy(self.t)
let dxt = length(dtx)
let dyt = length(dty)
let tofs = (dtx * bend.x + dty * bend.y) * dpi
let off = 0.7 * dpi
var main = vec4(0.0, 0.0, 0.0, 0.0)
var w = 0.0
var has = 0.0
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 - tofs, self.t_min + half_texel, self.t_max - half_texel)
main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - tofs - dty * off, self.t_min + half_texel, self.t_max - half_texel)
w = self.sdf(scale * 5.0, ps, vec4(0.5, 0.5, 0.5, 1.0))
has = 1.0
} else if self.texture_index < 1.5 {
let tex_size = self.color_texture.size()
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - tofs, self.t_min + half_texel, self.t_max - half_texel)
let cs = self.color_texture.sample(pm)
main = vec4(cs.rgb * cs.a, cs.a)
} else {
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 - tofs, self.t_min + half_texel, self.t_max - half_texel)
main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
if self.texture_index < 2.5 {
let ps = clamp(self.t.xy - tofs - dty * off, self.t_min + half_texel, self.t_max - half_texel)
w = self.msdf(scale * 5.0, ps, vec4(0.5, 0.5, 0.5, 1.0))
has = 1.0
}
}
main = main * inq
// The shadow keeps to the room it had: a point to the sides, half a
// point above and two below the letter's cell.
let room = step(-1.0, qs.x) * step(qs.x, rs.x + 1.0) * step(-0.5, qs.y) * step(qs.y, rs.y + 2.0)
w = w * room
let v = clamp((pp.y - b0.y) / max(b1.y - b0.y, 1.0), 0.0, 1.0)
let u = clamp((pp.x - b0.x) / max(b1.x - b0.x, 1.0), 0.0, 1.0)
let light = step(0.5, dot(c.rgb, vec3(0.299, 0.587, 0.114)))
let glass = self.material_glow_ink.rgb
// The ink: softened toward the glass, its edge in the glass's colour
// on coloured glass, lightened where the upper sheen crosses it.
let a = clamp(main.a / max(c.a, 0.0001), 0.0, 1.0)
var ink = mix(c.rgb, vec3(0.80, 0.81, 0.84), 0.06 * (1.0 - light) * en)
ink = mix(ink, mix(ink, glass, 0.55), (1.0 - smoothstep(0.3, 0.95, a)) * light * en)
let sheen = 0.22 * (1.0 - smoothstep(0.28, 0.6, v)) * (1.0 - 0.35 * u) * (1.0 - light) * en
ink = mix(ink, vec3(0.97, 0.975, 0.985), sheen)
let body = vec4(ink * main.a, main.a)
// The shadow on the back face.
let shc = mix(vec3(0.10, 0.11, 0.14), glass * 0.40, light)
let sh = w * mix(0.16, 0.40, light) * has * c.a * en
return body + vec4(shc * sh, sh) * (1.0 - body.a)
}
}
// The label is bold, in the sheet's near-black ink (white on coloured
// glass), 0.7 of its alpha while held, back at once when let go; out of
// use, the disabled ink.
let lq_pill_label = fn(w, size) {
w.draw_text.text_style = mod.theme.font_bold{font_size: size}
w.draw_text.get_color = fn() {
let held = step(0.999, self.down)
let k = mix(mix(1.0, 1.0, self.hover), 0.7, held)
let ink = self.color.mix(self.color_disabled, self.disabled)
return vec4(ink.rgb, ink.a * mix(k, 1.0, self.disabled) * (1.0 - self.loading))
}
}
// The press as the pill reads it (THE GLASS PILL), and its label (THE LABEL
// INSIDE THE GLASS, on its own `lq_clock`): held, `down` is 1 at
// once and the clock (cap_time) runs to 1 over 1.6 seconds; let go, over
// the button or off it, both fall to 0 over 1.6 seconds, so the release's
// ring has its clock; the hover alone moves neither, and cuts in over a
// tenth of a second. Every value a held state needs is written plain, so a
// state held from the start (the states page) shows.
let lq_pill_states = fn(w) {
w.animator.hover.off.from = {all: Forward {duration: 0.1} down: Forward {duration: 1.6}}
w.animator.hover.off.apply = {draw_bg: {down: 0.0 hover: 0.0 cap_time: 0.0} draw_text: {down: 0.0 hover: 0.0 lq_clock: 0.0}}
w.animator.hover.on.from = {all: Forward {duration: 0.1} down: Forward {duration: 1.6}}
w.animator.hover.on.apply = {draw_bg: {down: 0.0 hover: 1.0 cap_time: 0.0} draw_text: {down: 0.0 hover: 1.0 lq_clock: 0.0}}
w.animator.hover.down.from = {all: Forward {duration: 1.6}}
w.animator.hover.down.apply = {draw_bg: {down: timeline(0.0, 1.0, 1.0, 1.0) hover: 1.0 cap_time: 1.0} draw_text: {down: timeline(0.0, 1.0, 1.0, 1.0) hover: 1.0 lq_clock: 1.0}}
}
// A click takes no key focus, so a mouse press shows no focus ring; the
// keyboard's focus still does.
let lq_pill_dress = fn(w, size) {
lq_pill_room(w)
w.grab_key_focus = false
lq_pill_label(w, size)
lq_label_inside(w)
lq_pill_states(w)
}
lq_pill_dress(mod.widgets.Button, 9.0)
lq_pill_dress(mod.widgets.ButtonCompound, 9.0)
lq_pill_dress(mod.widgets.ButtonPrimary, 9.0)
lq_pill_dress(mod.widgets.ButtonXs, 8.5)
lq_pill_dress(mod.widgets.ButtonSm, 8.5)
lq_pill_dress(mod.widgets.ButtonLg, 11.0)
lq_pill_dress(mod.widgets.ButtonXl, 13.0)
// ---------------------------------------------------------------- switches and meter
// 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)
// No face: the pane's glass shows through, and on it, the surround's
// shadow falling across from the top, the scale, the red zone, the
// needle and its pivot.
let insh = Finish.inset(p, c, hf, rf, vec2(0.0, 2.0), 1.8) * 0.3
var acc = vec4(0.0, 0.0, 0.0, insh)
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))
let sc = max(arc, tick)
acc = Finish.over(acc, vec4(self.color_scale.rgb * sc, sc))
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)
let za = zone_t * zone_r
acc = Finish.over(acc, vec4(self.color_zone.rgb * za, za))
}
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)
acc = Finish.over(acc, vec4(self.color_needle.rgb * needle, needle))
let pd = r - self.pivot_size
let pc = Finish.cover(pd, px)
acc = Finish.over(acc, vec4(mix(self.color_needle.rgb, vec3(0.85, 0.86, 0.88), 0.35) * pc, pc))
let prim = Finish.band(pd, 0.0, px, px) * 0.45
acc = Finish.over(acc, vec4(0.0, 0.0, 0.0, prim))
// The black device pixel where the glass meets the bezel, and the
// glass's quiet band and the light line inside its top edge.
let edge = Finish.band(face_d, 0.0, px, px) * 0.6
acc = Finish.over(acc, vec4(0.0, 0.0, 0.0, edge))
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)
acc = Finish.over(acc, vec4(ga, ga, ga, ga))
acc = acc * Finish.cover(face_d, px)
// The bezel ring, a shade of what it is set in, a device pixel of dark
// at its outer edge, and the lip of light under it.
let bz = self.color_bezel
var ring = vec4(bz.rgb * bz.a, bz.a)
ring = Finish.over(ring, vec4(0.0, 0.0, 0.0, 0.45 * Finish.band(outer, 0.0, px, px)))
ring = ring * Finish.cover(outer, px) * (1.0 - Finish.cover(face_d, px))
var col = Finish.over(ring, acc)
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
}
// ---------------------------------------------------------------- water on the screens
// WATER UNDER THE POINTER, on his word ('can we give the liquid effect vu
// and readouts screen a mouse move water ripple effect'), and stronger on
// his second ('the distortion effect on the screens/vu can be much
// stronger'): the glass of the readouts, the screen and the meter is a
// water surface. Moving the pointer across it drops ripples (the widgets'
// trail, eight drops at the most, one per 6 points moved, each alive
// 1.6 s); each drop sends a ring out at 60 points a second, 20 points from
// crest to crest, landing over its first 0.16 s and weaker as it ages and
// as it grows. Where the rings pass, the glass bends what lies under it,
// the digits, the needle and the scale, read from the window displaced by
// up to about 4 points each way at a fresh crest, 8 from flank to flank.
// The bend never folds the picture over itself, so a digit can swell, lean
// and squeeze but never breaks into pieces: the rings are trochoids, a
// broad smooth crest that magnifies (about four times at a fresh crest)
// and a narrower trough that squeezes (about three times at most), each
// ring's own magnification is bounded, and where rings can meet, the
// newest takes its share of one magnification budget first and the older
// rings share what it leaves (shares that are the same over the whole
// glass, so they add no bend of their own). The flank of a crest facing the light lifts toward
// white and the other takes the cool shade of the sheet's glass, on every
// ring at full strength. The ripple dies out over the last 12 points
// before the bezel, the bezel stays still, and what the glass reads never
// comes from past the face. Nothing moves while the pointer is still or
// away: the screens are then drawn exactly as before.
// A drop's strength: its full height a little after it lands, fading
// with what is left of its life and as its ring grows. `d` is the drop
// (place, age; age -1 for none).
let lq_ripple_amp = fn(d: vec3, life: float) -> float {
if d.z < 0.0 {
return 0.0
}
let left = clamp(1.0 - d.z / max(life, 0.01), 0.0, 1.0)
return 1.575 * left * sqrt(left) * smoothstep(0.0, 0.16, d.z) / (1.0 + d.z * 0.6667)
}
// One drop's ring at `p`, as a displacement of what the glass reads, for a
// drop of strength `a`: a packet two crests deep around the front, the
// phase run through the trochoid (Kepler's equation, solved in three
// Newton steps) so the crests are broad and the troughs a little narrower
// (never squeezing what lies under them more than about three times), and
// nothing at the drop's own centre.
let lq_ripple_ring = fn(p: vec2, d: vec3, a: float) -> vec2 {
if a <= 0.0 {
return vec2(0.0, 0.0)
}
let q = p - d.xy
let r = max(length(q), 0.001)
let u = r - d.z * 60.0
let phi = u * 0.3141593
var t = phi + 0.35 * sin(phi)
t = t - (t - 0.35 * sin(t) - phi) / (1.0 - 0.35 * cos(t))
t = t - (t - 0.35 * sin(t) - phi) / (1.0 - 0.35 * cos(t))
t = t - (t - 0.35 * sin(t) - phi) / (1.0 - 0.35 * cos(t))
let env = exp(0.0 - u * u / 196.0) * (1.0 - exp(0.0 - r * r / 36.0))
let disp = a * env * sin(t) * 3.183099
return q * (disp / r)
}
// 1 where the fields of two drops' rings can meet (their packets reach
// 31 points either side of the fronts), falling to 0 over 6 points apart.
let lq_ripple_meet = fn(da: vec3, db: vec3) -> float {
let dist = length(da.xy - db.xy)
let fa = max(da.z, 0.0) * 60.0
let fb = max(db.z, 0.0) * 60.0
let lo_a = max(fa - 30.8, 0.0)
let lo_b = max(fb - 30.8, 0.0)
let gap = max(dist - (fa + fb + 61.6), max(lo_a - fb - 30.8, lo_b - fa - 30.8) - dist)
return 1.0 - smoothstep(0.0, 6.0, gap)
}
// A drop's share of the magnification budget (0.88: the picture is never
// magnified more than about eight times), newest first: what the newer
// drops it can meet have left, over its own need `n` (0.612 per unit of
// strength, the most a ring magnifies).
let lq_ripple_share = fn(used: float, n: float) -> float {
return clamp((0.88 - used) / max(n, 0.00001), 0.0, 1.0)
}
// The water at `p`: `xy` the displacement of what the glass reads (shared,
// and still at the bezel), `zw` the slope the light catches (every ring at
// full strength), the face's edge `face_d` away (negative inside).
let lq_ripple_sum = fn(p: vec2, face_d: float, life: float, d0: vec3, d1: vec3, d2: vec3, d3: vec3, d4: vec3, d5: vec3, d6: vec3, d7: vec3) -> vec4 {
let a0 = lq_ripple_amp(d0, life)
let a1 = lq_ripple_amp(d1, life)
let a2 = lq_ripple_amp(d2, life)
let a3 = lq_ripple_amp(d3, life)
let a4 = lq_ripple_amp(d4, life)
let a5 = lq_ripple_amp(d5, life)
let a6 = lq_ripple_amp(d6, life)
let a7 = lq_ripple_amp(d7, life)
let n0 = a0 * 0.612
let n1 = a1 * 0.612
let n2 = a2 * 0.612
let n3 = a3 * 0.612
let n4 = a4 * 0.612
let n5 = a5 * 0.612
let n6 = a6 * 0.612
let n7 = a7 * 0.612
let s0 = lq_ripple_share(0.0, n0)
let t0 = s0 * n0
let s1 = lq_ripple_share(lq_ripple_meet(d1, d0) * t0, n1)
let t1 = s1 * n1
let s2 = lq_ripple_share(lq_ripple_meet(d2, d0) * t0 + lq_ripple_meet(d2, d1) * t1, n2)
let t2 = s2 * n2
let s3 = lq_ripple_share(lq_ripple_meet(d3, d0) * t0 + lq_ripple_meet(d3, d1) * t1 + lq_ripple_meet(d3, d2) * t2, n3)
let t3 = s3 * n3
let s4 = lq_ripple_share(lq_ripple_meet(d4, d0) * t0 + lq_ripple_meet(d4, d1) * t1 + lq_ripple_meet(d4, d2) * t2 + lq_ripple_meet(d4, d3) * t3, n4)
let t4 = s4 * n4
let s5 = lq_ripple_share(lq_ripple_meet(d5, d0) * t0 + lq_ripple_meet(d5, d1) * t1 + lq_ripple_meet(d5, d2) * t2 + lq_ripple_meet(d5, d3) * t3 + lq_ripple_meet(d5, d4) * t4, n5)
let t5 = s5 * n5
let s6 = lq_ripple_share(lq_ripple_meet(d6, d0) * t0 + lq_ripple_meet(d6, d1) * t1 + lq_ripple_meet(d6, d2) * t2 + lq_ripple_meet(d6, d3) * t3 + lq_ripple_meet(d6, d4) * t4 + lq_ripple_meet(d6, d5) * t5, n6)
let t6 = s6 * n6
let s7 = lq_ripple_share(lq_ripple_meet(d7, d0) * t0 + lq_ripple_meet(d7, d1) * t1 + lq_ripple_meet(d7, d2) * t2 + lq_ripple_meet(d7, d3) * t3 + lq_ripple_meet(d7, d4) * t4 + lq_ripple_meet(d7, d5) * t5 + lq_ripple_meet(d7, d6) * t6, n7)
let w0 = lq_ripple_ring(p, d0, a0)
let w1 = lq_ripple_ring(p, d1, a1)
let w2 = lq_ripple_ring(p, d2, a2)
let w3 = lq_ripple_ring(p, d3, a3)
let w4 = lq_ripple_ring(p, d4, a4)
let w5 = lq_ripple_ring(p, d5, a5)
let w6 = lq_ripple_ring(p, d6, a6)
let w7 = lq_ripple_ring(p, d7, a7)
let disp = w0 * s0 + w1 * s1 + w2 * s2 + w3 * s3 + w4 * s4 + w5 * s5 + w6 * s6 + w7 * s7
let slope = w0 + w1 + w2 + w3 + w4 + w5 + w6 + w7
let keep = smoothstep(0.0, 12.0, 0.0 - face_d)
return vec4(disp * keep, slope * keep)
}
// The glass over the water. `under` is what lies under it read along the
// displacement (when `seen` is 1; then the glass is opaque over the face,
// which it redraws), `glass` the sheet's own glass over it (premultiplied),
// `slope` the water's slope in points of displacement (about 4 at a fresh
// crest). The light comes from the upper left: the flank facing it lifts
// toward white, the other takes the cool glass shade.
let lq_ripple_lay = fn(under: vec3, seen: float, glass: vec4, slope: vec2, cov: float) -> vec4 {
let lit = dot(slope, vec2(-0.55, -0.83)) * 0.25
let light_a = clamp(lit * 0.95, 0.0, 0.7)
let shade_a = clamp((0.0 - lit) * 0.55, 0.0, 0.45)
let shade = vec3(0.286, 0.322, 0.408)
if seen > 0.5 {
var col = under * (1.0 - glass.a) + glass.rgb
col = mix(col, vec3(1.0, 1.0, 1.0), light_a)
col = mix(col, col * shade * 1.6, shade_a)
return vec4(col * cov, cov)
}
var out = glass
out = Finish.over(out, vec4(light_a, light_a, light_a, light_a))
out = Finish.over(out, vec4(shade * shade_a * 0.6, shade_a))
return out * cov
}
// The screen's glass: the library's quiet band and top line over the
// water. While the pointer's trail is alive the widget lifts it over the
// face and binds the window's scene of the frame (`glass_backdrop`).
mod.widgets.ScreenView.field_reach = 2.0
mod.widgets.ScreenView.trail_life = 1.6
mod.widgets.ScreenView.glass_backdrop = true
mod.widgets.ScreenView.draw_glass.trail_0 = instance(vec3(0.0, 0.0, -1.0))
mod.widgets.ScreenView.draw_glass.trail_1 = instance(vec3(0.0, 0.0, -1.0))
mod.widgets.ScreenView.draw_glass.trail_2 = instance(vec3(0.0, 0.0, -1.0))
mod.widgets.ScreenView.draw_glass.trail_3 = instance(vec3(0.0, 0.0, -1.0))
mod.widgets.ScreenView.draw_glass.trail_4 = instance(vec3(0.0, 0.0, -1.0))
mod.widgets.ScreenView.draw_glass.trail_5 = instance(vec3(0.0, 0.0, -1.0))
mod.widgets.ScreenView.draw_glass.trail_6 = instance(vec3(0.0, 0.0, -1.0))
mod.widgets.ScreenView.draw_glass.trail_7 = instance(vec3(0.0, 0.0, -1.0))
mod.widgets.ScreenView.draw_glass.trail_life = uniform(1.6)
mod.widgets.ScreenView.draw_glass.scene_texture = texture_2d(float)
mod.widgets.ScreenView.draw_glass.has_gauss = uniform(0.0)
mod.widgets.ScreenView.draw_glass.source_size = uniform(vec2(1.0, 1.0))
mod.widgets.ScreenView.draw_glass.source_y_flip = uniform(0.0)
mod.widgets.ScreenView.draw_glass.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.face_radius, min(ho.x, ho.y))
let b = Finish.snap(max(self.bezel, 0.0), px)
let hf = max(ho - vec2(b, b), vec2(0.5, 0.5))
let face_d = Material.sd_box(p, c, hf, max(r_out - b, 0.0))
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 a = clamp(self.sheen * (band + top), 0.0, 1.0)
let cov = Finish.cover(face_d + px, px)
let w = lq_ripple_sum(p, face_d, self.trail_life, self.trail_0, self.trail_1, self.trail_2, self.trail_3, self.trail_4, self.trail_5, self.trail_6, self.trail_7)
var under = vec3(0.0, 0.0, 0.0)
if self.has_gauss > 0.5 {
let ps = clamp(p + w.xy, c - hf, c + hf)
let sq = (self.rect_pos + ps) / max(self.source_size, vec2(1.0, 1.0))
under = self.scene_texture.sample(vec2(sq.x, mix(sq.y, 1.0 - sq.y, self.source_y_flip))).rgb
}
return lq_ripple_lay(under, self.has_gauss, vec4(a, a, a, a), w.zw, cov)
}
// The screen's face, on his word ('remove bg from the cards, meters,
// screens, readouts etc'): the library's bezel and inner shadow and the
// black device pixel where the glass meets the bezel, with no fill, so the
// pane's glass and its pools show through and the digits lie on it.
mod.widgets.ScreenView.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(max(self.bezel, 0.0), px)
let hf = max(ho - vec2(b, b), vec2(0.5, 0.5))
let face_d = Material.sd_box(p, c, hf, max(r_out - b, 0.0))
var acc = vec4(0.0, 0.0, 0.0, 0.0)
if self.recess > 0.0 {
let insh = Finish.inset(p, c, hf, max(r_out - b, 0.0), vec2(0.0, self.recess * 0.5), max(self.recess * 0.45, 0.3))
acc = vec4(0.0, 0.0, 0.0, 0.3 * insh)
}
if self.scan > 0.0 {
let sl = 1.0 - Finish.scan(p.y - (c.y - hf.y), px, 3.0 * px, self.scan)
acc = Finish.over(acc, vec4(0.0, 0.0, 0.0, sl))
}
acc = Finish.over(acc, vec4(0.0, 0.0, 0.0, 0.6 * Finish.band(face_d, 0.0, px, px)))
acc = acc * Finish.cover(face_d, px)
let bz = self.color_bezel
var ring = vec4(bz.rgb * bz.a, bz.a)
ring = Finish.over(ring, vec4(0.0, 0.0, 0.0, 0.45 * Finish.band(outer, 0.0, px, px)))
ring = ring * Finish.cover(outer, px) * (1.0 - Finish.cover(face_d, px))
let col = Finish.over(ring, acc)
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)
}
// The meter's glass, over its face inside the housing's 2 point bezel; its
// own quiet glass is part of the face it reads.
mod.widgets.NeedleMeter.field_reach = 2.0
mod.widgets.NeedleMeter.trail_life = 1.6
mod.widgets.NeedleMeter.glass_backdrop = true
mod.widgets.NeedleMeter.draw_glass.trail_0 = instance(vec3(0.0, 0.0, -1.0))
mod.widgets.NeedleMeter.draw_glass.trail_1 = instance(vec3(0.0, 0.0, -1.0))
mod.widgets.NeedleMeter.draw_glass.trail_2 = instance(vec3(0.0, 0.0, -1.0))
mod.widgets.NeedleMeter.draw_glass.trail_3 = instance(vec3(0.0, 0.0, -1.0))
mod.widgets.NeedleMeter.draw_glass.trail_4 = instance(vec3(0.0, 0.0, -1.0))
mod.widgets.NeedleMeter.draw_glass.trail_5 = instance(vec3(0.0, 0.0, -1.0))
mod.widgets.NeedleMeter.draw_glass.trail_6 = instance(vec3(0.0, 0.0, -1.0))
mod.widgets.NeedleMeter.draw_glass.trail_7 = instance(vec3(0.0, 0.0, -1.0))
mod.widgets.NeedleMeter.draw_glass.trail_life = uniform(1.6)
mod.widgets.NeedleMeter.draw_glass.scene_texture = texture_2d(float)
mod.widgets.NeedleMeter.draw_glass.has_gauss = uniform(0.0)
mod.widgets.NeedleMeter.draw_glass.source_size = uniform(vec2(1.0, 1.0))
mod.widgets.NeedleMeter.draw_glass.source_y_flip = uniform(0.0)
mod.widgets.NeedleMeter.draw_glass.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.face_radius, min(ho.x, ho.y))
let b = Finish.snap(2.0, px)
let hf = max(ho - vec2(b, b), vec2(0.5, 0.5))
let face_d = Material.sd_box(p, c, hf, max(r_out - b, 0.0))
let cov = Finish.cover(face_d + px, px)
let w = lq_ripple_sum(p, face_d, self.trail_life, self.trail_0, self.trail_1, self.trail_2, self.trail_3, self.trail_4, self.trail_5, self.trail_6, self.trail_7)
var under = vec3(0.0, 0.0, 0.0)
if self.has_gauss > 0.5 {
let ps = clamp(p + w.xy, c - hf, c + hf)
let sq = (self.rect_pos + ps) / max(self.source_size, vec2(1.0, 1.0))
under = self.scene_texture.sample(vec2(sq.x, mix(sq.y, 1.0 - sq.y, self.source_y_flip))).rgb
}
return lq_ripple_lay(under, self.has_gauss, vec4(0.0, 0.0, 0.0, 0.0), w.zw, cov)
}
// ---------------------------------------------------------------- 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 lq_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 lq_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 lq_led_lamp_hue = fn(ink: vec3, vivid: float) -> vec3 {
let f = lq_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 lq_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 = lq_led_lamp_full(ink)
let hue = lq_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 = lq_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 ring of clear glass, as its lens buttons are: a crisp white seal on its upper edge and a soft shadow.
// The halo's room is held in the lamp's own box (the library reserves a
// lens thickness either side once a halo is asked for), so the light is
// never cut at its edge.
mod.widgets.Lamp.halo = 0.3
mod.widgets.Lamp.halo_reach = 1.0
mod.widgets.Lamp.draw_bg.vertex = fn() {
let m = 2.0
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
mod.widgets.Lamp.draw_bg.pixel = fn() {
let m = 2.0
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let reach = clamp(self.reach, 0.0, 1.0)
let t = self.rect_size.y / (1.0 + 2.0 * reach)
let room = t * reach
let c = self.rect_size * 0.5
let half_len = max((self.rect_size.x - 2.0 * room) * 0.5, t * 0.5)
// The lens is the lamp's size; its socket stands round it in the room.
let hb = vec2(mix(t * 0.5, half_len, self.bar), t * 0.5)
let lo = lq_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.812, 0.816, 0.831), max(room - 0.5, 2.0), vec3(0.0, 0.0, 0.0), 1.0)
let up = lq_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.812, 0.816, 0.831), max(room - 0.5, 2.0), vec3(1.0, 1.0, 1.0), 1.0)
return lq_unfill(lo, up, 0.08) * self.opacity
}
true