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