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