use mod.prelude.widgets_internal.* use mod.sdf.Cap // Automotive: a racing dash, a deck of dark brushed steel between bands of // carbon fibre. Satin graphite caps, chrome bezels round every instrument, // wells of dark brushed steel behind silver frames, plates of lighter steel, // neon tubes under chrome slider caps, a rainbow bar of light cells, // tachometer dials with a red line, amber-lit gold tabs, a red safety cover // over the Delete switch. 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 au_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 au_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 au_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 au_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 au_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 = au_hash(i) let b = au_hash(i + vec2(1.0, 0.0)) let c = au_hash(i + vec2(0.0, 1.0)) let d = au_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 au_brush = fn(q: vec2, s: float) -> float { let a = au_noise(vec2(q.x / (s * 60.0), q.y / s)) let b = au_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 au_grain_at = fn(q: vec2, px: float, amt: float) -> float { let i = floor(q / px) return (au_hash(i) - au_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 au_plate_tex = fn(p: vec2, px: float) -> float { return au_brush(p, 0.7) * 0.030 + au_grain_at(p, px, 0.010) } // THE WEAVE. A two-over-two twill of tows `s` points wide, as carbon fibre // lies under its resin: every tow floats over two cells, round across and // dipping where it dives under at either end. Returned: the float's // brightness 0..1 (its roundness, its dip, its fibres and a shade of its // own), whether it runs across (1) or down (0), and how near its crown the // point lies. let au_weave = fn(w: vec2, s: float) -> vec3 { let q = w / s let cell = floor(q) let f = q - cell let k = cell.x + cell.y let n = k - 4.0 * floor(k * 0.25) let horiz = step(n, 1.5) let run = mix(n - 2.0, n, horiz) let across = mix(f.x, f.y, horiz) let along = (run + mix(f.y, f.x, horiz)) * 0.5 let first = cell - mix(vec2(0.0, run), vec2(run, 0.0), horiz) let round = sin(clamp(across, 0.0, 1.0) * PI) let dip = smoothstep(0.0, 0.17, along) * smoothstep(0.0, 0.17, 1.0 - along) let own = au_hash(first + vec2(horiz * 31.0, 7.0)) let fib = au_noise(vec2(along * 3.0 + first.x * 1.7, across * 3.5 + first.y * 2.3)) let b = pow(round, 0.75) * (0.50 + 0.50 * dip) * (0.84 + 0.16 * fib) * (0.82 + 0.32 * own) return vec3(b, horiz, round * dip) } // Carbon fibre at window point `w`: the tows that run across catch the light // and the ones that run down stay in shade, so the weave reads as rising // diagonal ribs; a lit float carries a highlight along its crown. `lift` // scales the light (a well's weave lies deeper than the ground's). let au_carbon = fn(w: vec2, s: float, lift: float) -> vec3 { let v = au_weave(w, s) let lit = mix(0.38, 1.0, v.y) let crown = pow(v.z, 5.0) * v.y // The light lies along the ribs: brightest down the middle of each // rising band of lit floats, so every float shades off toward its far // corners and the bands read as chains of diamonds. let rib = 0.5 + 0.5 * cos(2.0 * PI * ((w.x + w.y) / s - 1.5) * 0.25) let c = vec3(0.011, 0.012, 0.014) + (vec3(0.150, 0.154, 0.164) * v.x * lit + vec3(0.050, 0.052, 0.056) * crown * v.x) * lift * (0.50 + 0.50 * rib) return c } // BRUSHED METAL at point `w` (points): every device-pixel row its own // streaks, long ones and short fine ones, changing from row to row, so the // grain stays crisp at the device pixel however it is scaled; a rare bright // scratch along a row; under them a slower drift of a few rows at a time // and a grain of single pixels. Nothing is tiled: the streaks come from the // unaligned hash. -0.6..0.6 about. let au_brushed = fn(w: vec2, px: float) -> float { let row = floor(w.y / px) let a = au_noise(vec2(w.x / 70.0 + au_hash(vec2(row, 3.0)) * 40.0, row)) let f = au_noise(vec2(w.x / 13.0 + au_hash(vec2(row, 11.0)) * 90.0, row + 517.0)) let b = au_noise(vec2(w.x / 380.0 + 7.0, w.y / (px * 9.0))) let c = au_hash(vec2(floor(w.x / px), row)) - 0.5 let seg = floor(w.x / 50.0 + au_hash(vec2(row, 7.0)) * 3.0) let scr = step(0.993, au_hash(vec2(row, seg))) * smoothstep(0.0, 0.3, au_noise(vec2(w.x / 9.0, row + 91.0))) return (a - 0.5) * 0.80 + (f - 0.5) * 0.50 + (b - 0.5) * 0.22 + c * 0.18 + scr * 0.9 } // The deck the controls stand on: charcoal steel, #2a2c30 at its darkest to // #33363b where the light lies, brushed across in fine crisp streaks, with // a broad soft sheen running down from the upper left and a slow cloud of // light over it. `ww`, `wh` the window's size; `w` a window point. let au_deck = fn(w: vec2, px: float, ww: float, wh: float) -> vec3 { let u = w.x / max(ww, 1.0) let v = w.y / max(wh, 1.0) let s = u - 0.38 + 0.45 * (v - 0.4) let sheen = exp(-s * s / 0.07) let cloud = au_noise(w / 260.0 + vec2(5.0, 1.0)) - 0.5 let lv = 0.168 + 0.030 * sheen + 0.010 * cloud let br = au_brushed(w, px) * 0.040 let k = lv + br return vec3(k, k * 1.045, k * 1.13) } // A PLATE OF STEEL lighter than the deck, at point `p` of the plate (so it // moves with it), `t` 0..1 down it and `u` 0..1 across: #535659 under its // top edge falling to #43464a at its foot, brushed across in the deck's // crisp streaks, a broad soft sheen sweeping down from the upper middle. // Held under #555 everywhere, so light words keep 5:1 on it. let au_steel = fn(p: vec2, px: float, t: float, u: float) -> vec3 { let k0 = mix(0.312, 0.262, smoothstep(0.0, 1.0, t)) let s = u - 0.62 + 0.55 * t let sheen = exp(-s * s / 0.05) * (1.0 - 0.5 * t) let k = min(k0 + 0.020 * sheen + au_brushed(p, px) * 0.036, 0.334) return vec3(k, k * 1.03, k * 1.075) } // A dark well of the same brushed steel, for the floors of fields. let au_dark_steel = fn(p: vec2, px: float, lift: float) -> vec3 { let k = 0.082 + 0.022 * lift + au_brushed(p, px) * 0.032 return vec3(k, k * 1.04, k * 1.12) } // A HEXAGON MESH at point `m` (points, the mesh's own origin a hole's // middle): hexagonal holes `s` points apart on a hex lattice, each hole's // flat sides left and right, in a web `web` points wide. The web is `webc` // steel; its edge round each hole catches the light where it faces up (the // rim over the hole) and falls dark where it faces down; down in a hole // the lower wall shows a little light. Crisp at the device pixel. let au_mesh = fn(m: vec2, s: float, web: float, px: float, webc: vec3, lit: float) -> vec3 { let r = vec2(s, s * 1.7320508) let hh = r * 0.5 let a = m - r * floor(m / r) - hh let b0 = m - hh let b = b0 - r * floor(b0 / r) - hh var q = b if dot(a, a) < dot(b, b) { q = a } let aq = abs(q) let dh = max(aq.x, dot(aq, vec2(0.5, 0.8660254))) let ap = s * 0.5 - web * 0.5 let d = dh - ap let facing = -q.y / max(length(q), 0.001) var wc = webc let rim = Finish.ring_out(d, 0.0, px, px) let rim2 = Finish.ring_out(d, px, px * 2.0, px) wc = mix(wc, min(webc * 1.9 + vec3(0.10, 0.10, 0.105), vec3(1.0, 1.0, 1.0)), rim * smoothstep(0.15, 0.85, facing) * lit) wc = mix(wc, min(webc * 1.35 + vec3(0.03, 0.03, 0.03), vec3(1.0, 1.0, 1.0)), rim2 * smoothstep(0.3, 0.9, facing) * lit * 0.6) wc = mix(wc, webc * 0.45, rim * smoothstep(0.15, 0.85, -facing)) let floor_lit = smoothstep(0.2, 1.0, q.y / max(ap, 0.1)) let hc = vec3(0.010, 0.010, 0.012) + vec3(0.050, 0.051, 0.054) * floor_lit * lit return mix(wc, hc, Finish.cover(d, px)) } // The window ground at a window point `w` (points) in a window `ww` by `wh` // points. The deck of brushed steel takes the middle of the window; the // carbon weave shows only in a band along the top (under the caption and // the tool bar) and a band along the bottom. The deck stands a little proud // of the weave: its corners are cut at 45 degrees, its top edge is a lit // bevel two device pixels deep over a black seam, its foot a dark bevel // over the black seam, and it throws a short shadow down onto the weave // under it, gone within six points. Nothing here moves with a scrolled view. let au_ground = fn(w: vec2, px: float, ww: float, wh: float) -> vec3 { let top = Finish.snap(88.0, px) let bot = Finish.snap(wh - min(215.0, 0.172 * wh), px) let c = vec2(ww * 0.5, (top + bot) * 0.5) let h = vec2(ww * 0.5 + px * 0.5, (bot - top) * 0.5) let d = Finish.sd_chamfer(w, c, h, 22.0) // The weave, under a slow light lying toward the top of the window. let dx = (w.x - ww * 0.42) / max(ww * 0.55, 1.0) let cloud = au_noise(w / 190.0) * 0.6 + au_noise(w / 63.0 + vec2(11.0, 3.0)) * 0.4 let light = exp(-dx * dx) * (0.85 + 0.35 * cloud) var cb = au_carbon(w, 4.2, light) let g = au_grain_at(w, px, 0.006) cb = cb + vec3(g, g, g) // The deck's shadow on the weave: under its foot and its lower cuts. let sh = Finish.drop(w, c + vec2(0.0, 2.0), h, 2.0, vec2(0.0, 0.0), 2.6) * 0.7 cb = cb * (1.0 - sh * step(c.y, w.y)) // The deck and its bevel, lit by the way its edge faces. var dk = au_deck(w, px, ww, wh) let e = px * 0.5 let gx = Finish.sd_chamfer(w + vec2(e, 0.0), c, h, 22.0) - Finish.sd_chamfer(w - vec2(e, 0.0), c, h, 22.0) let gy = Finish.sd_chamfer(w + vec2(0.0, e), c, h, 22.0) - Finish.sd_chamfer(w - vec2(0.0, e), c, h, 22.0) let n = vec2(gx, gy) / max(length(vec2(gx, gy)), 0.0001) let up = smoothstep(0.2, 0.9, dot(n, vec2(-0.30, -0.95))) let down = smoothstep(0.2, 0.9, n.y) 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) dk = mix(dk, vec3(0.540, 0.552, 0.575), r1 * up) dk = mix(dk, vec3(0.380, 0.390, 0.410), r2 * up) dk = mix(dk, vec3(0.270, 0.278, 0.295), r3 * up) dk = mix(dk, vec3(0.085, 0.088, 0.095), (r1 + r2) * down) dk = mix(dk, vec3(0.120, 0.124, 0.132), r3 * down) dk = mix(dk, vec3(0.006, 0.006, 0.008), Finish.band(d, 0.0, px, px)) return mix(cb, dk, Finish.cover(d, px)) } // The room a chrome bezel mirrors, fixed on the screen so the pattern stands // still as a knob turns: grey walls, pale toward the viewer, darker at the // height of the bezel's middle, 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 lamp low down and a dim shelf up and to the left. // Each shape is an oval with a sharp edge, so on a turned band 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. let au_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) } let au_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.36, 0.66, smoothstep(0.0, 0.6, z)) w = mix(w, 0.52, 1.0 - smoothstep(-0.70, -0.35, z)) var c = vec3(w, w + 0.005, w + 0.015) + vec3(0.07, 0.07, 0.07) * clamp(-r.y, -1.0, 1.0) c = mix(c, vec3(1.0, 1.0, 1.0), au_room_patch(a, z, -2.25, 0.62, 0.05, 0.50, k)) c = mix(c, vec3(0.96, 0.96, 0.95), au_room_patch(a, z, 0.80, 0.55, -0.15, 0.55, k)) c = mix(c, vec3(0.05, 0.05, 0.055), au_room_patch(a, z, 0.0, 0.42, -0.30, 0.55, k)) c = mix(c, vec3(0.08, 0.08, 0.09), au_room_patch(a, z, 3.14159265, 0.50, 0.25, 0.60, k)) c = mix(c, vec3(0.70, 0.71, 0.73), au_room_patch(a, z, 1.80, 0.40, 0.30, 0.42, k) * wide) c = mix(c, vec3(0.40, 0.41, 0.43), au_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 polished chrome ring round `c` from `r0` to `r1`: the band turns from // facing the viewer at its inner edge to facing out at its outer edge, and // mirrors the room along that turn; a black line at either edge. // Premultiplied. let au_chrome_ring = fn(p: vec2, c: vec2, r0: float, r1: float, px: float, bright: float) -> vec4 { let q = p - c let rl = length(q) let nk = q / max(rl, 0.001) let w = max(r1 - r0, px) let u = clamp((rl - r0) / w, 0.0, 1.0) let wide = smoothstep(3.0, 9.0, w / px) let th = mix(mix(0.62, 0.30, wide), mix(0.98, 1.20, wide), u) var col = au_chrome_room(vec3(nk * sin(2.0 * th), cos(2.0 * th)), wide) col = min(col * (1.0 + 0.10 * bright) + vec3(0.04, 0.04, 0.04) * bright, vec3(1.0, 1.0, 1.0)) let d = abs(rl - (r0 + r1) * 0.5) - w * 0.5 col = mix(col, vec3(0.02, 0.02, 0.025), Finish.band(d, 0.0, px, px)) let a = Finish.cover(d, px) return vec4(col * a, a) } // The five neon colours of the slider tubes: blue, green, yellow, orange, // purple, picked by `k` round the five. let au_neon = fn(k: float) -> vec3 { let i = k - 5.0 * floor(k / 5.0 + 0.001) if i < 0.5 { return vec3(0.22, 0.80, 1.0) } if i < 1.5 { return vec3(0.22, 1.0, 0.46) } if i < 2.5 { return vec3(1.0, 0.93, 0.24) } if i < 3.5 { return vec3(1.0, 0.56, 0.18) } return vec3(0.80, 0.34, 1.0) } // Every slider's tube burns the same neon blue. A colour picked from where // a slider stands would change as its view scrolls, and a slider has // nothing else to tell it apart from its neighbours; the rainbow is kept // for the feedback bars. let au_neon_at = fn(o: vec2) -> vec3 { return au_neon(0.0) } // The rainbow of the feedback bars at `t` 0..1: violet, blue, cyan, green, // yellow, orange. let au_rainbow = fn(t: float) -> vec3 { let x = clamp(t, 0.0, 1.0) * 5.0 let a = vec3(0.70, 0.30, 1.0) let b = vec3(0.24, 0.48, 1.0) let c = vec3(0.16, 0.90, 1.0) let d = vec3(0.26, 1.0, 0.36) let e = vec3(1.0, 0.92, 0.22) let f = vec3(1.0, 0.56, 0.16) var col = mix(a, b, clamp(x, 0.0, 1.0)) col = mix(col, c, clamp(x - 1.0, 0.0, 1.0)) col = mix(col, d, clamp(x - 2.0, 0.0, 1.0)) col = mix(col, e, clamp(x - 3.0, 0.0, 1.0)) return mix(col, f, clamp(x - 4.0, 0.0, 1.0)) } // 1 when bit `b` (a power of two) of `m` is set. let au_bit = fn(m: float, b: float) -> float { return step(0.25, fract(floor(m / b) * 0.5)) } // A gauge numeral: the distance to the centre line of digit `d` drawn as a // squared dash type, in glyph units (0.56 wide, 1 tall, top at 0). The // strokes meet in round joints; the one, the four and the seven are drawn // as type draws them, not as segments. let au_glyph = fn(q: vec2, d: float) -> float { let n = floor(d + 0.5) let tl = vec2(0.0, 0.0) let tr = vec2(0.52, 0.0) let ml = vec2(0.0, 0.5) let mr = vec2(0.52, 0.5) let bl = vec2(0.0, 1.0) let br = vec2(0.52, 1.0) var m = 63.0 if n > 1.5 { m = 91.0 } if n > 2.5 { m = 79.0 } if n > 4.5 { m = 109.0 } if n > 5.5 { m = 125.0 } if n > 7.5 { m = 127.0 } if n > 8.5 { m = 111.0 } var r = 10.0 r = min(r, au_seg(q, tl, tr) + (1.0 - au_bit(m, 1.0)) * 10.0) r = min(r, au_seg(q, tr, mr) + (1.0 - au_bit(m, 2.0)) * 10.0) r = min(r, au_seg(q, mr, br) + (1.0 - au_bit(m, 4.0)) * 10.0) r = min(r, au_seg(q, bl, br) + (1.0 - au_bit(m, 8.0)) * 10.0) r = min(r, au_seg(q, ml, bl) + (1.0 - au_bit(m, 16.0)) * 10.0) r = min(r, au_seg(q, tl, ml) + (1.0 - au_bit(m, 32.0)) * 10.0) r = min(r, au_seg(q, ml, mr) + (1.0 - au_bit(m, 64.0)) * 10.0) if n > 0.5 && n < 1.5 { r = min(au_seg(q, vec2(0.30, 0.0), vec2(0.30, 1.0)), au_seg(q, vec2(0.30, 0.0), vec2(0.08, 0.20))) } if n > 3.5 && n < 4.5 { r = min(min(au_seg(q, vec2(0.40, 0.0), vec2(0.40, 1.0)), au_seg(q, vec2(0.40, 0.0), vec2(0.0, 0.66))), au_seg(q, vec2(0.0, 0.66), vec2(0.56, 0.66))) } if n > 6.5 && n < 7.5 { r = min(au_seg(q, tl, tr), au_seg(q, tr, vec2(0.16, 1.0))) } return r } // A number 0..99 set at `c` (its middle), `h` points tall, leaning like // dash type; the distance to its strokes in points less half the stroke. let au_number = fn(p: vec2, c: vec2, h: float, v: float, stroke: float) -> float { let n = floor(v + 0.5) let two = step(9.5, n) let gw = 0.56 * h let gap = 0.22 * h let tw = gw + two * (gw + gap) var q = (p - c + vec2(tw * 0.5, h * 0.5)) / h q = vec2(q.x - (0.5 - q.y) * 0.16, q.y) let tens = floor(n / 10.0) let ones = n - tens * 10.0 var d = au_glyph(q - vec2(two * (0.56 + 0.22), 0.0), ones) if two > 0.5 { d = min(d, au_glyph(q, tens)) } return d * h - stroke * 0.5 } // A raised graphite cap: the drop and contact shadows under it, the black // outline, the satin face falling from the light at the top, the soft top // edge, the faint side light, the bottom rim. `lift` 1 casts the drop // shadow, 0 seats the cap on the ground with the contact shadow only. `rim` // is the bottom rim's ink. Premultiplied. let au_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 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 dense shadow under its foot over a soft fall // gone within half an 8 point gap to the sides. 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.92 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.8 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 shows under its foot. 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.30, 0.31, 0.33, 1.0) * lip) // The face: satin graphite, #45474b under the top edge falling to // #222326 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.4) var face = mix(vec3(0.270, 0.276, 0.290), vec3(0.133, 0.137, 0.148), ft) + tint let tx = au_plate_tex(p, px) face = face + vec3(tx, tx, tx) * 0.7 * up face = face + vec3(0.045, 0.046, 0.049) * (1.0 + small) * hover * up // Held or chosen: sunk, a floor that falls a little and a shade under // the top edge. let shade = 0.6 * (1.0 - smoothstep(0.0, 0.24 * min(hh, 24.0), p.y - lo.y - px)) let held = mix(mix(vec3(0.098, 0.100, 0.108), vec3(0.074, 0.076, 0.082), t) + tint, vec3(0.0, 0.0, 0.0), shade) face = mix(face, held, down * (1.0 - disabled)) face = mix(face, mix(vec3(0.115, 0.117, 0.124), vec3(0.090, 0.092, 0.098), t), disabled) 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) // A satin sheen over the upper part of the face, and a soft bloom on // its upper left where the light lands. 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.036, 0.037, 0.040) * Finish.sheen(uv, 0.0, 0.5, 0.25) * up * (0.6 + 0.4 * gloss) let sw = max(min(0.30 * (hi.x - lo.x), 20.0), 2.0) let sq = (p - vec2(lo.x + min(0.24 * (hi.x - lo.x), 16.0), lo.y + 0.10 * hh)) / vec2(sw, max(0.24 * hh, 1.5)) face = face + vec3(0.050, 0.051, 0.054) * exp(-dot(sq, sq)) * up // The edge: a light row along the top (#8a8c90), half of it under, a // white catch on the top-left corner, a quieter line down the sides. 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.500, 0.508, 0.528), vec3(0.600, 0.608, 0.626), max(gloss, hover)) face = mix(face, hot, r1 * topw * up) face = mix(face, hot * 0.6, r2 * topw * up * 0.6) let nl = dot(n, vec2(-0.62, -0.78)) face = mix(face, vec3(0.86, 0.87, 0.89), r1 * smoothstep(0.93, 0.995, nl) * up * 0.8) let sidec = mix(vec3(0.330, 0.336, 0.352), vec3(0.180, 0.184, 0.196), t) face = mix(face, sidec, r1 * sidew * (0.35 + 0.65 * up)) face = mix(face, rim.rgb, (r1 + 0.35 * r2) * botw * rim.a * (1.0 - disabled)) let ol = mix(vec3(0.024, 0.024, 0.028), vec3(0.008, 0.008, 0.010), t) face = mix(face, mix(ol, vec3(0.030, 0.031, 0.034), disabled), Finish.band(d, 0.0, px, px)) let a = Finish.cover(d, px) * (1.0 - 0.25 * disabled) var o = Finish.over(under, vec4(face * a, a)) // Focus: a thin blue ring just outside the outline, and a 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.227, 0.612, 1.0, 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); a dark lens when not (a bar: // one dark glass). `hs` is the half size; the ends are round. let au_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) let tl = clamp((p.y - (c.y - hs.y)) / max(2.0 * hs.y, px), 0.0, 1.0) var lens = mix(vec3(0.150, 0.152, 0.165), vec3(0.060, 0.062, 0.068), smoothstep(0.2, 0.9, tl)) lens = mix(lens, rimc * 0.22, 0.25) let sp = p - (c + vec2(-0.3 * hs.x, -0.35 * hs.y)) lens = lens + vec3(0.16, 0.16, 0.17) * exp(-dot(sp, sp) / max(2.0 * 0.09 * hs.y * hs.y, 0.001)) * step(1.5, hs.y) 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.110, 0.112, 0.122), vec3(0.070, 0.072, 0.078), gt) glass = mix(glass, rimc, 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.016, 0.016, 0.020, 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) 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) let rim = Finish.ring_out(d, 0.0, px, px) o = Finish.over(o, vec4(rimc, 1.0) * rim * lit) 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 } // A lamp lying in the bottom rim of a lit cap: a white-hot bar three device // pixels tall and `hw` half wide whose last row is `yb`, the glow's colour // on the face round it, a pale halo hugging the bar, and a spill that runs // out past the outline onto the ground under the cap. `inside` is the cap's // cover, `below` 1 on the ground under it. Premultiplied over `dst`. let au_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 au_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} mod.widgets.ButtonPrimary.padding = mod.turtle.Inset{left: 10.0 right: 10.0 top: 4.0 bottom: 4.0} mod.widgets.ButtonSecondary.padding = mod.turtle.Inset{left: 10.0 right: 10.0 top: 4.0 bottom: 4.0} // The safety cover's hinge takes the first five points of its box: the // legend keeps ten points clear of it. mod.widgets.ButtonDanger.padding = mod.turtle.Inset{left: 15.0 right: 10.0 top: 4.0 bottom: 4.0} // 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 SAFETY COVER: the Delete cap is a flip-up cover of red moulded plastic // over its switch, hinged on the left. A dark base plate shows round the // hinge, a chrome hinge barrel stands on it, and the cover runs from the // barrel to the right end: glossy red falling from the light at the top, a // hard white reflection across its upper part, a raised lip at the far end // where a finger lifts it, a dark rim at its foot, and its own shadow on // the plate. Under the hand it brightens; held, it presses down and dims. let au_cover = fn(p: vec2, lo: vec2, hi: vec2, px: float, hover: float, down: float, disabled: float, focus: float) -> vec4 { let up = (1.0 - down) * (1.0 - disabled) var res = au_cap(p, lo, hi, 3.0, px, 0.0, down, disabled, 0.0, 1.0, vec3(-0.04, -0.04, -0.04), vec4(0.20, 0.20, 0.21, 1.0), 0.0) // The cover's box, from the hinge to the right end. let clo = vec2(lo.x + 4.5, lo.y + 1.5) let chi = vec2(hi.x - 1.5, hi.y - 2.0) let cc = (clo + chi) * 0.5 let ch = (chi - clo) * 0.5 let rc = 3.5 let d = Material.sd_box(p, cc, ch, rc) // Its shadow on the plate: it stands a point and a half above it. let sh = Finish.drop(p, cc, ch, rc, vec2(0.8, 1.2) * (1.0 - 0.6 * down), 1.0) * 0.7 * (1.0 - Finish.cover(d, px)) res = Finish.over(res, vec4(0.0, 0.0, 0.0, sh * Finish.cover(Material.sd_box(p, (lo + hi) * 0.5, (hi - lo) * 0.5, 3.0), px))) let t = clamp((p.y - clo.y) / max(chi.y - clo.y, 1.0), 0.0, 1.0) let u = clamp((p.x - clo.x) / max(chi.x - clo.x, 1.0), 0.0, 1.0) var red = mix(vec3(0.800, 0.070, 0.060), vec3(0.380, 0.020, 0.020), smoothstep(0.0, 1.0, t)) red = red + vec3(0.12, 0.03, 0.03) * hover * up // The moulding: the body a shade darker toward the hinge, the lip at the // far end standing up into the light. red = red * (0.86 + 0.14 * u) let lipx = smoothstep(chi.x - 7.0, chi.x - 4.5, p.x) red = mix(red, mix(vec3(0.95, 0.16, 0.13), vec3(0.50, 0.03, 0.03), t), lipx * 0.85) red = mix(red, red * 0.55, smoothstep(chi.x - 7.5, chi.x - 6.5, p.x) * (1.0 - smoothstep(chi.x - 6.5, chi.x - 5.5, p.x)) * 0.8) // The reflection: a hard pale band across the upper third of the // gloss, fading toward the hinge, and a white catch on the lip. let band = smoothstep(0.06, 0.12, t) * (1.0 - smoothstep(0.24, 0.34, t)) * smoothstep(0.0, 0.25, u) red = mix(red, vec3(1.0, 0.86, 0.84), band * 0.34 * up) let lc = (p - vec2(chi.x - 4.0, clo.y + 3.0)) / vec2(1.6, 1.4) red = mix(red, vec3(1.0, 0.92, 0.90), exp(-dot(lc, lc)) * 0.8 * up) // The rims: a pink top edge, a deep red foot, the black outline. let r1 = Finish.band(d, px, px * 2.0, px) red = mix(red, vec3(1.0, 0.50, 0.46), r1 * (1.0 - step(cc.y, p.y)) * 0.7 * up) red = mix(red, vec3(0.22, 0.01, 0.01), r1 * step(cc.y, p.y) * 0.8) red = red * (1.0 - 0.35 * down) let gl = Finish.lum(red) red = mix(red, vec3(gl, gl, gl) * 0.7 + vec3(0.05, 0.02, 0.02), disabled) red = mix(red, vec3(0.06, 0.0, 0.0), Finish.band(d, 0.0, px, px)) let a = Finish.cover(d, px) res = Finish.over(res, vec4(red * a, a)) // The hinge: a chrome barrel down the left of the cover, round across, // with a dark seam at either end. let hlo = vec2(lo.x + 2.0, lo.y + 3.0) let hhi = vec2(lo.x + 6.5, hi.y - 3.5) let hd = Material.sd_box(p, (hlo + hhi) * 0.5, (hhi - hlo) * 0.5, 1.5) let hx = clamp((p.x - hlo.x) / max(hhi.x - hlo.x, 0.5), 0.0, 1.0) var hc = mix(vec3(0.30, 0.30, 0.32), vec3(0.95, 0.95, 0.96), exp(-(hx - 0.35) * (hx - 0.35) / 0.04)) hc = mix(hc, vec3(0.12, 0.12, 0.13), smoothstep(0.75, 1.0, hx) * 0.8) let hm = (hlo.y + hhi.y) * 0.5 let seam = (1.0 - smoothstep(px * 0.5, px * 1.5, abs(p.y - (hm - 2.5)))) + (1.0 - smoothstep(px * 0.5, px * 1.5, abs(p.y - (hm + 2.5)))) hc = mix(hc, vec3(0.05, 0.05, 0.06), clamp(seam, 0.0, 1.0) * 0.8) hc = mix(hc, vec3(0.03, 0.03, 0.035), Finish.band(hd, 0.0, px, px)) hc = mix(hc, hc * 0.5, disabled) let ha = Finish.cover(hd, px) res = Finish.over(res, vec4(hc * ha, ha)) let ring = Finish.ring_out(d, 0.0, px, px) * 0.9 + Finish.glow(d - px, 0.7) * 0.3 * (1.0 - a) return Finish.over(res, vec4(0.227, 0.612, 1.0, 1.0) * clamp(ring, 0.0, 1.0) * focus * (1.0 - disabled)) } // The cap and its states, shared by every button template; `kind` picks the // variant: 0 flat (a legend on the ground, a cap only under the hand), 1 // raised cap, 2 primary (the blue lit rim), 3 secondary (a chrome trim), 4 // tertiary (a carbon inlay), 5 danger (the safety cover), 6 outline, 7 // flatter. let au_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 = au_px(dpi) let p = pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let lo = vec2(au_snap(0.0, o.x, px), au_snap(0.0, o.y, px)) let hi = vec2(au_snap(size.x, o.x, px), au_snap(size.y, o.y, px)) let c = (lo + hi) * 0.5 let h = (hi - lo) * 0.5 let r = 3.5 let d = Material.sd_box(p, c, h, r) let inside = Finish.cover(d, px) var res = vec4(0.0, 0.0, 0.0, 0.0) if kind > 4.5 && kind < 5.5 { res = au_cover(p, lo, hi, px, hover, down, disabled, focus) } else { if kind < 0.5 || kind > 5.5 { // Flat, outline, flatter: no cap at rest. Under the hand a faint // cap shows; held, it sinks. if kind > 5.5 && kind < 6.5 { // Outline: a frame of silver round a window on the ground, // the weave under a light smoke. 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 t = clamp((p.y - lo.y) / max(hi.y - lo.y, 1.0), 0.0, 1.0) res = vec4(0.0, 0.0, 0.0, 0.32) * inside let sil = mix(vec3(0.62, 0.63, 0.65), vec3(0.34, 0.35, 0.37), t) * (1.0 - 0.5 * disabled) res = Finish.over(res, vec4(0.0, 0.0, 0.0, 1.0) * Finish.band(d, px, px * 2.0, px) * 0.7) res = Finish.over(res, vec4(sil, 1.0) * ring) res = Finish.over(res, vec4(0.0, 0.0, 0.0, 1.0) * Finish.ring_out(d, 0.0, px, px) * 0.6) } if hover + down > 0.001 && kind < 6.5 { let k = mix(0.75, 1.0, down) res = Finish.over(res, au_cap(p, lo, hi, r, px, hover, down, disabled, 0.0, 0.0, vec3(0.0, 0.0, 0.0), vec4(0.20, 0.20, 0.21, 1.0), 1.0) * clamp(max(hover, down), 0.0, 1.0) * k) } if kind > 5.5 && kind < 6.5 && hover + down > 0.001 { res = Finish.over(res, au_cap(p, lo, hi, r, px, hover, down, disabled, 0.0, 0.0, vec3(0.0, 0.0, 0.0), vec4(0.20, 0.20, 0.21, 1.0), 1.0) * clamp(max(hover, down), 0.0, 1.0) * 0.8) } let fr = Finish.ring_out(d, 0.0, px, px) * 0.9 + Finish.glow(d - px, 0.7) * 0.3 * (1.0 - inside) res = Finish.over(res, vec4(0.227, 0.612, 1.0, 1.0) * clamp(fr, 0.0, 1.0) * focus * (1.0 - disabled)) } else { var tint = vec3(0.0, 0.0, 0.0) let lit = 1.0 - disabled if kind > 1.5 && kind < 2.5 { // Primary: the face cast deep blue by its own rim light. tint = vec3(-0.070, -0.022, 0.030) * lit } if kind > 3.5 && kind < 4.5 { tint = vec3(-0.040, -0.040, -0.040) } let prim = step(1.5, kind) * step(kind, 2.5) res = au_cap(p, lo, hi, r, px, hover, down, disabled, focus * (1.0 - prim), 1.0, tint, vec4(0.20, 0.20, 0.21, 1.0), 1.0) let up = (1.0 - down) * lit if kind > 3.5 && kind < 4.5 { // Tertiary: a panel of the weave let into the face under a // satin coat, the cap's own frame two points wide round it. let wd = Material.sd_box(p, c, h - vec2(2.5, 2.5), max(r - 2.0, 1.0)) let wv = au_carbon(p + o, 2.5, 1.5) + vec3(0.03, 0.03, 0.032) let wa = Finish.cover(wd, px) * (1.0 - 0.6 * disabled) var win = mix(wv, wv * 0.6, down) win = mix(win, vec3(0.0, 0.0, 0.0), Finish.band(wd, 0.0, px, px) * 0.8) res = Finish.over(res, vec4(win * wa, wa)) let rl = Finish.ring_out(wd, 0.0, px, px) * step(c.y, p.y) res = Finish.over(res, vec4(0.36, 0.37, 0.39, 1.0) * rl * 0.7 * up) } if kind > 2.5 && kind < 3.5 { // Secondary: trimmed in polished chrome, a bright hairline // just outside the black outline, lit on the top. let t = clamp((p.y - lo.y) / max(hi.y - lo.y, 1.0), 0.0, 1.0) let trim = mix(vec3(0.78, 0.79, 0.81), vec3(0.40, 0.41, 0.43), smoothstep(0.0, 1.0, t)) res = Finish.over(res, vec4(trim, 1.0) * Finish.ring_out(d, 0.0, px, px) * (0.4 + 0.6 * lit)) } if kind > 1.5 && kind < 2.5 { // Primary: a blue light runs round the rim, two device pixels // of it inside the outline (the outer one nearly white), a // glow on the face inside it, and a halo on the ground that // is gone within half the gap to the next cap. let on = lit * (1.0 - 0.35 * down) * (1.0 + 0.15 * hover) let depth = max(-d, 0.0) let inner = exp(-max(depth - px * 2.0, 0.0) / 2.2) * inside res = Finish.over(res, vec4(0.20, 0.52, 1.0, 1.0) * clamp(inner * 0.30 * on, 0.0, 1.0)) let b1 = Finish.band(d, 0.0, px, px) let b2 = Finish.band(d, px, px * 2.0, px) let b3 = Finish.band(d, px * 2.0, px * 3.0, px) res = Finish.over(res, vec4(0.25, 0.60, 1.0, 1.0) * b1 * on) res = Finish.over(res, vec4(0.72, 0.88, 1.0, 1.0) * b2 * on) res = Finish.over(res, vec4(0.30, 0.64, 1.0, 1.0) * b3 * 0.8 * on) let x = max(d, 0.0) let halo = (0.55 * exp(-(x * x) / (2.0 * 0.8 * 0.8)) + 0.25 * exp(-(x * x) / (2.0 * 1.8 * 1.8))) * (1.0 - smoothstep(2.5, 4.0, x)) * (1.0 - inside) res = Finish.over(res, vec4(0.20, 0.55, 1.0, 1.0) * clamp(halo * on, 0.0, 1.0)) // Focus brightens the light rather than ringing it. res = Finish.over(res, vec4(0.80, 0.92, 1.0, 1.0) * b2 * 0.5 * focus * lit) } } } if loading > 0.001 { let rad = max(min(h.x, h.y) - 7.25, 2.0) res = au_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(au_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(au_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(au_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(au_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(au_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(au_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(au_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) } // The size rungs are made from the flat template; here they carry the cap. mod.widgets.ButtonXs.draw_bg.pixel = fn() { return self.host_over(au_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.ButtonSm.draw_bg.pixel = fn() { return self.host_over(au_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.ButtonLg.draw_bg.pixel = fn() { return self.host_over(au_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.ButtonXl.draw_bg.pixel = fn() { return self.host_over(au_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.ButtonFlatter.draw_bg.pixel = fn() { return self.host_over(au_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 #dcdde0, the primary's lit legend blue-white, // the cover's legend white on its red, every variant #63666b disabled. mod.widgets.ButtonPrimary.draw_text.color = #eef6ff mod.widgets.ButtonPrimary.draw_text.color_hover = #f6faff mod.widgets.ButtonPrimary.draw_text.color_down = #e2eefc mod.widgets.ButtonPrimary.draw_text.color_focus = #eef6ff mod.widgets.ButtonPrimary.draw_text.color_disabled = #63666b mod.widgets.ButtonPrimary.draw_icon.color = #eef6ff mod.widgets.ButtonSecondary.draw_text.color = #dcdde0 mod.widgets.ButtonSecondary.draw_text.color_hover = #e8e9eb mod.widgets.ButtonSecondary.draw_text.color_down = #c9cbcf mod.widgets.ButtonSecondary.draw_text.color_focus = #dcdde0 mod.widgets.ButtonSecondary.draw_icon.color = #dcdde0 mod.widgets.ButtonTertiary.draw_text.color = #dcdde0 mod.widgets.ButtonTertiary.draw_text.color_hover = #e8e9eb mod.widgets.ButtonTertiary.draw_text.color_down = #c9cbcf mod.widgets.ButtonTertiary.draw_text.color_focus = #dcdde0 mod.widgets.ButtonTertiary.draw_icon.color = #dcdde0 mod.widgets.ButtonDanger.draw_text.color = #ffffff mod.widgets.ButtonDanger.draw_text.color_hover = #ffffff mod.widgets.ButtonDanger.draw_text.color_down = #f2e6e5 mod.widgets.ButtonDanger.draw_text.color_focus = #ffffff mod.widgets.ButtonDanger.draw_icon.color = #ffffff mod.widgets.ButtonOutline.draw_text.color = #c9cbcf mod.widgets.ButtonOutline.draw_text.color_hover = #e2e3e6 mod.widgets.ButtonOutline.draw_text.color_down = #c9cbcf mod.widgets.ButtonOutline.draw_text.color_focus = #dcdde0 mod.widgets.ButtonOutline.draw_text.color_disabled = #63666b mod.widgets.ButtonOutline.draw_icon.color = #c9cbcf // The trailing icon takes the cap's ink too, never the drawing's own black. mod.widgets.ButtonFlat.draw_icon_end.color = #dcdde0 // ---------------------------------------------------------------- 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 au_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.200, 0.204, 0.214), vec3(0.420, 0.428, 0.446), 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 #060607, the next half way to it. col = mix(col, vec3(0.022, 0.023, 0.026), 1.0 - step(px * 2.0, top)) col = mix(col, vec3(0.022, 0.023, 0.026), 0.5 * step(px * 2.0, top) * (1.0 - step(px * 3.0, top))) col = mix(col, mix(vec3(0.016, 0.016, 0.019), vec3(0.010, 0.010, 0.012), 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.227, 0.612, 1.0, 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 au_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.227, 0.612, 1.0), 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 well cut into the ground, its floor dark graphite. // Checked, a blue light pools on its floor under the lit mark and glows // onto the ground round the box. mod.widgets.CheckBoxFlat.draw_bg.pixel = fn() { let m = 5.0 let px = au_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(au_snap(x0, o.x, px), au_snap(y0, o.y, px)) let hi = vec2(au_snap(x0 + sz, o.x, px), au_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) // Under the hand the floor lifts; held, it falls a little. let held = max(self.down, self.pressed) let fill = vec3(0.050, 0.051, 0.056) + vec3(0.030, 0.031, 0.034) * self.hover * (1.0 - self.disabled) - vec3(0.02, 0.02, 0.02) * held var res = au_well(p, lo, hi, r, px, fill, self.focus, self.disabled, 1.0) // The error: the outline turns the alt red. let d = Material.sd_box(p, c, (hi - lo) * 0.5, r) // The floor is lit from under the mark: a soft blue pool. let gq = (p - c - vec2(0.0, sz * 0.2)) / (sz * 0.34) res = Finish.over(res, vec4(0.020, 0.330, 0.700, 1.0) * exp(-dot(gq, gq)) * 0.40 * on * (1.0 - self.disabled) * Finish.cover(d + px * 2.0, px)) // Lit, the well glows through its outline: a blue halo on the ground // round the box, gone within four points. res = Finish.over(res, vec4(0.227, 0.612, 1.0, 1.0) * Finish.glow(d, 0.9) * 0.32 * (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(au_seg(p, cm + vec2(-3.6, 0.1) * k, cm + vec2(-1.1, 2.6) * k), au_seg(p, cm + vec2(-1.1, 2.6) * k, cm + vec2(3.7, -2.8) * k)) let cy = au_snap((lo.y + hi.y) * 0.5, o.y, px) + px * 0.5 let dd = au_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 au_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 graphite body, a satin thumb // half its length; the thumb carries an LED bar, lit blue 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 = au_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(au_snap(x0, o.x, px), au_snap(y0, o.y, px)) let hi = vec2(au_snap(x0 + tw, o.x, px), au_snap(y0 + th, o.y, px)) let on = self.active * (1.0 - self.disabled) // The body a darker raised cap, a breath of blue in it when on; the // thumb a brighter cap standing on it. var res = au_cap(p, lo, hi, 3.0, px, 0.0, 0.0, self.disabled, self.focus, 1.0, vec3(-0.075, -0.075, -0.078) + vec3(0.0, 0.012, 0.03) * on, vec4(0.17, 0.17, 0.18, 1.0), 0.0) // The thumb travels half the track. let kt = mix(self.active, self.drag_pos, self.drag) let kw = au_snap(x0 + tw * 0.5, o.x, px) - lo.x let kx = au_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 = au_cap(p, klo, khi, 3.0, px, self.hover, max(self.pressed, self.down), self.disabled, 0.0, 0.6, vec3(0.02, 0.02, 0.021), vec4(0.20, 0.20, 0.21, 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, au_snap((lo.y + hi.y) * 0.5, o.y, px)) res = au_led(res, p, lc, vec2(4.0, px), on, 0.8, px, vec3(0.94, 0.984, 1.0), vec3(0.227, 0.612, 1.0)) return res } // The radio: the check box's well made round, a socket. Chosen, an LED dot // is lit in its middle and its blue pools round it; not chosen, the dot is // a dark lens. 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 = au_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(au_snap(0.0, o.x, px), au_snap(y0, o.y, px)) let hi = vec2(au_snap(sz, o.x, px), au_snap(y0 + sz, o.y, px)) let c = (lo + hi) * 0.5 let on = self.active * (1.0 - self.disabled) let fill = vec3(0.050, 0.051, 0.056) + vec3(0.030, 0.031, 0.034) * self.hover * (1.0 - self.disabled) var res = au_well(p, lo, hi, sz * 0.5, px, fill, self.focus, self.disabled, 1.0) // Chosen, the socket is lit from the dot: 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.030, 0.360, 0.760, 1.0) * exp(-dot(gq, gq)) * 0.45 * 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.227, 0.612, 1.0, 1.0) * Finish.glow(dro, 0.9) * 0.34 * (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 = au_led(res, p, cd, vec2(3.0 - px, 3.0 - px), on, 1.0, px, vec3(0.96, 0.99, 1.0), vec3(0.227, 0.612, 1.0)) return res } // A tab: a dark cap rimmed in brushed gold, as a tuning panel marks the // pages of its menu. The rim is a band of gold a point and a half wide just // inside the black outline, lit from the top. The tab in use is lit from // within: its rim burns amber, a warm glow lies on the face along it and a // short halo on the ground over and under it, gone within four points; a quiet tab // keeps its gold dim. Under the hand the rim brightens; pressed, the tab // sinks. Premultiplied. let au_gold_tab = fn(p: vec2, lo: vec2, hi: vec2, px: float, hover: float, down: float, active: float, disabled: float, focus: float) -> vec4 { let r = 3.5 let c = (lo + hi) * 0.5 let h = (hi - lo) * 0.5 let d = Material.sd_box(p, c, h, r) let inside = Finish.cover(d, px) var res = au_cap(p, lo, hi, r, px, hover * (1.0 - active), down * (1.0 - active), disabled, focus, 1.0, vec3(-0.075, -0.080, -0.090) + vec3(0.02, 0.012, 0.0) * active, vec4(0.20, 0.20, 0.21, 1.0), 0.0) let t = clamp((p.y - lo.y) / max(hi.y - lo.y, 1.0), 0.0, 1.0) let on = active * (1.0 - disabled) // The warm light inside the lit rim. let depth = max(-d, 0.0) res = Finish.over(res, vec4(1.0, 0.62, 0.16, 1.0) * clamp(exp(-max(depth - px * 3.0, 0.0) / 2.4) * inside * 0.26 * on, 0.0, 1.0)) // The gold band. let gold = mix(vec3(0.840, 0.660, 0.330), vec3(0.470, 0.330, 0.140), smoothstep(0.0, 1.0, t)) let lit = mix(vec3(1.0, 0.86, 0.52), vec3(0.95, 0.60, 0.20), smoothstep(0.0, 1.0, t)) var gc = mix(gold * (0.62 + 0.25 * hover), lit, on) gc = mix(gc, vec3(0.30, 0.28, 0.25), disabled) let band = Finish.band(d, px, px * 2.5, px) res = Finish.over(res, vec4(gc, 1.0) * band) res = Finish.over(res, vec4(vec3(1.0, 0.95, 0.80), 1.0) * Finish.band(d, px, px * 2.0, px) * step(p.y, lo.y + r) * 0.35 * (0.5 + 0.5 * on)) let x = max(d, 0.0) // Tabs stand a point or two apart, so the halo keeps to the ground over // and under the tab and is gone within a point to the sides. let side = 1.0 - smoothstep(0.0, 1.0, max(lo.x - p.x, p.x - hi.x)) let halo = (0.45 * exp(-(x * x) / (2.0 * 0.8 * 0.8)) + 0.2 * exp(-(x * x) / (2.0 * 1.8 * 1.8))) * (1.0 - smoothstep(2.5, 4.0, x)) * (1.0 - inside) * side return Finish.over(res, vec4(1.0, 0.62, 0.18, 1.0) * clamp(halo * on, 0.0, 1.0)) } // The radio tab is the same gold-rimmed tab: chosen, its rim lit. 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 = au_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(au_snap(0.0, o.x, px), au_snap(0.0, o.y, px)) let hi = vec2(au_snap(self.rect_size.x, o.x, px), au_snap(self.rect_size.y, o.y, px)) return au_gold_tab(p, lo, hi, px, self.hover, self.down, self.active, self.disabled, self.focus) } mod.widgets.RadioButtonTabFlat.draw_text.color = #cfae6c mod.widgets.RadioButtonTabFlat.draw_text.color_hover = #e6c684 mod.widgets.RadioButtonTabFlat.draw_text.color_active = #ffe2a8 mod.widgets.RadioButtonTabFlat.draw_text.color_disabled = #63666b // ---------------------------------------------------------------- sliders // THE GROOVE a value runs in: a slot cut into the ground with round ends, a // silver lip under its foot and a dark one over its top, the black outline, // and a floor of near-black hexagon mesh (`au_mesh`, holes 3.2 points // apart, a row of them along the groove's middle, a thin charcoal web with // only a faint cool light on each upper rim) under the shadow of its upper // wall: a texture noticed up close. The // tube lies over the mesh, so the mesh shows on the unlit part and dimly // under the glow. Premultiplied. let au_groove = fn(p: vec2, o: vec2, lo: vec2, hi: vec2, px: float, disabled: float) -> vec4 { let r = (hi.y - lo.y) * 0.5 var res = au_well(p, lo, hi, r, px, vec3(0.030, 0.031, 0.034), 0.0, disabled, 1.0) let c = (lo + hi) * 0.5 let d = Material.sd_box(p, c, (hi - lo) * 0.5, r) var mc = au_mesh(p - vec2(lo.x + r, c.y), 3.2, 0.6, px, vec3(0.118, 0.126, 0.140), 0.40 * (1.0 - 0.6 * disabled)) mc = mc * (1.0 - 0.75 * Finish.inset(p, c, (hi - lo) * 0.5 - vec2(px * 2.0, px * 2.0), max(r - px * 2.0, 0.5), vec2(0.0, 1.4), 1.4)) mc = mc * (1.0 - 0.45 * disabled) let a = Finish.cover(d + px * 2.0, px) return Finish.over(res, vec4(mc * a, a)) } // A NEON TUBE lying in the groove from `x0` to `x1` on the line `cy`, `rt` // points round: a white-hot core along its middle a third of its width, // its colour at full strength toward its glass, and its light blooming on // the groove's floor and out over the ground round it in two falls, gone // within nine points. `col` is its colour. Premultiplied over `dst`. let au_tube = fn(dst: vec4, p: vec2, x0: float, x1: float, cy: float, rt: float, px: float, col: vec3, disabled: float) -> vec4 { let on = step(0.5, x1 - x0) let hl = max((x1 - x0) * 0.5, rt) let d = Finish.sd_pill(p, vec2(x0 + hl, cy), vec2(hl, rt)) let live = 1.0 - disabled let x = max(d, 0.0) let halo = (0.70 * exp(-(x * x) / (2.0 * 1.1 * 1.1)) + 0.34 * exp(-(x * x) / (2.0 * 3.4 * 3.4))) * (1.0 - smoothstep(6.0, 9.0, x)) * (1.0 - Finish.cover(d, px)) var res = Finish.over(dst, vec4(col, 1.0) * clamp(halo * live * on, 0.0, 1.0)) let k = clamp(abs(p.y - cy) / max(rt, 0.5), 0.0, 1.0) var tc = mix(mix(col, vec3(1.0, 1.0, 1.0), 0.92), mix(col, vec3(1.0, 1.0, 1.0), 0.25), smoothstep(0.18, 0.55, k)) tc = mix(tc, col, smoothstep(0.55, 0.85, k)) tc = mix(tc, col * 0.75, smoothstep(0.88, 1.0, k)) let g = Finish.lum(col) tc = mix(tc, vec3(g, g, g) * 0.35 + vec3(0.05, 0.05, 0.055), disabled) return Finish.over(res, vec4(tc, 1.0) * Finish.cover(d, px) * on) } // THE SLIDER CAP: a chrome barrel lying across the groove, its axis along // the travel. Round about that axis it mirrors the room (a bright band over // its top, a dark one under its middle, the floor's light along its foot); // three ring grooves are turned into its right end; its ends are rounded // over. The tube's light catches its outline and lies round it on the // ground; held, that light is stronger. `hs` is the half size, `col` the // tube's colour. Premultiplied over `dst`. let au_barrel = fn(dst: vec4, p: vec2, c: vec2, hs: vec2, px: float, hover: float, held: float, disabled: float, col: vec3) -> vec4 { let r = min(2.5, hs.x * 0.4) let d = Material.sd_box(p, c, hs, r) let live = 1.0 - disabled // Its shadow on the groove and the ground. let sh = Finish.drop(p, c + vec2(0.0, 1.2), hs - vec2(0.5, 0.5), r, vec2(0.0, 0.8), 1.4) * 0.8 * (1.0 - Finish.cover(d, px)) var res = Finish.over(dst, vec4(0.0, 0.0, 0.0, sh)) // The tube's light round it. let x = max(d, 0.0) let gl = (0.42 * exp(-(x * x) / (2.0 * 0.8 * 0.8)) + 0.16 * exp(-(x * x) / (2.0 * 2.0 * 2.0))) * (1.0 - smoothstep(3.0, 4.5, x)) * (1.0 - Finish.cover(d, px)) res = Finish.over(res, vec4(col, 1.0) * clamp(gl * (0.7 + 0.5 * held) * live, 0.0, 1.0)) // The chrome: the normal round the axis, mirrored into the room. let ny = clamp((p.y - c.y) / max(hs.y, 0.5), -1.0, 1.0) let ex = clamp((abs(p.x - c.x) - (hs.x - r * 1.6)) / max(r * 1.6, 0.5), 0.0, 1.0) var ch = au_chrome_bar(ny) * (1.0 - 0.35 * ex * ex) ch = ch + col * 0.22 * smoothstep(0.45, 0.9, ny) * live ch = ch * (1.0 + 0.10 * hover * live) // The ring grooves at the right end, each a dark cut with a lit edge // after it. let gx = c.x + hs.x - 2.4 let k1 = abs(p.x - gx) let k2 = abs(p.x - (gx - 2.0)) let k3 = abs(p.x - (gx - 4.0)) let cut = (1.0 - smoothstep(px * 0.3, px * 0.9, min(min(k1, k2), k3))) * step(abs(p.y - c.y), hs.y - 0.6) let lit = (1.0 - smoothstep(px * 0.3, px * 0.9, min(min(abs(p.x - gx - px), abs(p.x - gx + 2.0 - px)), abs(p.x - gx + 4.0 - px)))) * step(abs(p.y - c.y), hs.y - 0.6) ch = mix(ch, ch * 0.22, cut * 0.9) ch = mix(ch, min(ch * 1.25 + vec3(0.12, 0.12, 0.12), vec3(1.0, 1.0, 1.0)), lit * 0.6) let gg = Finish.lum(ch) ch = mix(ch, vec3(gg, gg, gg) * 0.45 + vec3(0.04, 0.04, 0.045), disabled) // The outline: black, with the tube's colour lit along it. ch = mix(ch, mix(col, vec3(1.0, 1.0, 1.0), 0.25), Finish.band(d, px, px * 2.0, px) * 0.55 * live) ch = mix(ch, vec3(0.02, 0.02, 0.025), Finish.band(d, 0.0, px, px)) let a = Finish.cover(d, px) return Finish.over(res, vec4(ch * a, a)) } // Polished chrome on a cylinder lying across the light, by `u` -1 (its top // edge) to 1 (its foot): a dark rim, a white band over the upper third, the // deep horizon under the middle, the floor's grey below and a pale // reflection near the foot. let au_chrome_bar = fn(u: float) -> vec3 { var v = 0.50 v = mix(v, 0.98, exp(-(u + 0.42) * (u + 0.42) / 0.030)) v = mix(v, 0.10, exp(-(u - 0.14) * (u - 0.14) / 0.028)) v = mix(v, 0.74, exp(-(u - 0.70) * (u - 0.70) / 0.018)) v = v * mix(1.0, 0.40, smoothstep(0.82, 1.0, abs(u))) return vec3(v, v * 1.005, v * 1.02) } // A round thumb (the Band's): a chrome ball in a ring of the tube's light. let au_ball = fn(dst: vec4, p: vec2, c: vec2, rad: float, px: float, hover: float, held: float, disabled: float, col: vec3) -> vec4 { let q = p - c let rl = length(q) let d = rl - rad let live = 1.0 - disabled let sh = Finish.drop(p, c, vec2(rad, rad) * 0.92, rad * 0.92, vec2(0.0, min(0.2 * rad, 3.0)), min(0.15 * rad, 2.0)) * 0.85 * (1.0 - Finish.cover(d, px)) var res = Finish.over(dst, vec4(0.0, 0.0, 0.0, sh)) let x = max(d, 0.0) let gl = (0.42 * exp(-(x * x) / (2.0 * 0.8 * 0.8)) + 0.16 * exp(-(x * x) / (2.0 * 2.0 * 2.0))) * (1.0 - smoothstep(3.0, 4.5, x)) * (1.0 - Finish.cover(d, px)) res = Finish.over(res, vec4(col, 1.0) * clamp(gl * (0.7 + 0.5 * held) * live, 0.0, 1.0)) let nxy = q / max(rad, 0.5) let nz = sqrt(max(1.0 - dot(nxy, nxy), 0.0)) let n = normalize(vec3(nxy, nz + 0.001)) let rf = vec3(2.0 * n.z * n.x, 2.0 * n.z * n.y, 2.0 * n.z * n.z - 1.0) var ch = au_chrome_room(rf, 1.0) * (1.0 + 0.10 * hover * live) let gg = Finish.lum(ch) ch = mix(ch, vec3(gg, gg, gg) * 0.45 + vec3(0.04, 0.04, 0.045), disabled) ch = mix(ch, mix(col, vec3(1.0, 1.0, 1.0), 0.25), Finish.band(d, px, px * 2.0, px) * 0.55 * live) ch = mix(ch, vec3(0.02, 0.02, 0.025), Finish.band(d, 0.0, px, px)) let a = Finish.cover(d, px) return Finish.over(res, vec4(ch * a, a)) } // A turned-metal disc (the spinner's middle): dark steel, lathe rings, a // specular up and to the left, a lit rim on its top edge. Premultiplied. let au_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 t = clamp(q.y / max(rad, 0.5) * 0.5 + 0.5, 0.0, 1.0) var m = mix(vec3(0.560, 0.565, 0.580), vec3(0.160, 0.162, 0.170), smoothstep(0.05, 0.95, t)) m = m * Finish.spun(p, c, vec2(-0.35, -0.94)) let sp = (q - vec2(-0.32 * rad, -0.40 * rad)) / max(0.36 * rad, 0.8) m = m + vec3(0.16, 0.16, 0.165) * exp(-dot(sp, sp)) + vec3(0.03, 0.03, 0.03) * hover m = mix(m, vec3(0.80, 0.81, 0.83), Finish.band(d, px, px * 2.0, px) * smoothstep(0.1, 0.9, -q.y / max(rl, 0.001)) * 0.9) m = mix(m, mix(m, vec3(0.12, 0.12, 0.13), 0.65), disabled) m = mix(m, vec3(0.012, 0.012, 0.016), Finish.band(d, 0.0, px, px)) let a = Finish.cover(d, px) return vec4(m * a, a) } // The bar slider: an 11 point groove under its label row, the neon tube from // its start to the cap, and the chrome barrel cap riding it, 18 points // along and 20 across. mod.widgets.SliderFlat.clip_x = false mod.widgets.SliderFlat.clip_y = false mod.widgets.SliderFlat.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.SliderFlat.draw_bg.pixel = fn() { let m = 12.0 let px = au_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 hw = 9.0 let cy = au_snap(20.0 + (self.rect_size.y - 20.0) * 0.5, o.y, px) let lo = vec2(au_snap(0.0, o.x, px), cy - 5.5) let hi = vec2(au_snap(self.rect_size.x, o.x, px), cy + 5.5) let col = au_neon_at(o) var res = au_groove(p, o, lo, hi, px, self.disabled) let span = self.rect_size.x - hw * 2.0 let hx = hw + self.slide_pos * span let ox = hw + self.origin_pos * span let f0 = mix(lo.x + 3.0, min(ox, hx), self.arc_origin) let f1 = mix(hx, max(ox, hx), self.arc_origin) res = au_tube(res, p, f0, f1, cy, 2.7, px, col, self.disabled) let held = max(self.drag, self.focus) return au_barrel(res, p, vec2(Finish.snap(o.x + hx, px) - o.x, cy), vec2(hw, 10.0), px, self.hover, held, self.disabled, col) } // 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: a slimmer groove and tube under its label, and a // smaller barrel riding it. mod.widgets.SliderMinimal.clip_x = false mod.widgets.SliderMinimal.clip_y = false mod.widgets.SliderMinimal.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.SliderMinimal.draw_bg.pixel = fn() { let m = 10.0 let px = au_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 hw = 7.0 let cy = au_snap(self.offset_y + (self.rect_size.y - self.offset_y) * 0.5, o.y, px) let lo = vec2(au_snap(0.0, o.x, px), cy - 4.0) let hi = vec2(au_snap(self.rect_size.x, o.x, px), cy + 4.0) let col = au_neon_at(o) var res = au_groove(p, o, lo, hi, px, self.disabled) let span = self.rect_size.x - hw * 2.0 let hx = hw + self.slide_pos * span let ox = hw + self.origin_pos * span let f0 = mix(lo.x + 2.5, min(ox, hx), self.arc_origin) let f1 = mix(hx, max(ox, hx), self.arc_origin) res = au_tube(res, p, f0, f1, cy, 2.0, px, col, self.disabled) let held = max(self.drag, self.hover) return au_barrel(res, p, vec2(Finish.snap(o.x + hx, px) - o.x, cy), vec2(hw, 7.5), px, self.hover, held, self.disabled, col) } // The round slider: a label column, then the bar slider's groove, tube and // barrel. The groove 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 = 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.SliderRoundFlat.draw_bg.pixel = fn() { let m = 12.0 let px = au_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 hw = 9.0 let cy = au_snap(self.rect_size.y * 0.5, o.y, px) let x0 = au_snap(self.label_size, o.x, px) let lo = vec2(x0, cy - 5.5) let hi = vec2(au_snap(self.rect_size.x - 37.0, o.x, px), cy + 5.5) let col = au_neon_at(o) var res = au_groove(p, o, lo, hi, px, self.disabled) let span = hi.x - lo.x - hw * 2.0 let hx = lo.x + hw + self.slide_pos * span res = au_tube(res, p, lo.x + 3.0, hx, cy, 2.7, px, col, self.disabled) let held = max(self.drag, self.focus) return au_barrel(res, p, vec2(Finish.snap(o.x + hx, px) - o.x, cy), vec2(hw, 10.0), px, self.hover, held, self.disabled, col) } // The fader: a 3 point slot with a black centre line, a ladder of LED dots // beside it lit green from the bottom up to the value (amber over the last // fifth), a tick scale on the other side, and a chrome cap: the slider // bench's crossfader block drawn by the cap engine (`mod.sdf.Cap`), a low // block of polished chrome 30 points along the travel and 0.55 of the // control across, 6.5 points high, its ends chamfered up to a level top, // its sides falling over a rounded shoulder, six ribs cut across its top. // 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 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(1.35) 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(0.55) 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: bright 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(#6b6e73) mod.widgets.SliderFader.draw_bg.light_color = uniform(#e6e8eb) 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 au_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 = au_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 = au_fader_room(self.cap_light, self.cap_height, self.cap_shadow_blur, self.cap_glow, self.cap_px) let px = au_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(au_snap(slo.x, o.x, px), au_snap(slo.y, o.y, px)) let hi = vec2(au_snap(shi.x, o.x, px), au_snap(shi.y, o.y, px)) var res = au_well(p, lo, hi, 0.0, px, vec3(0.030, 0.031, 0.034), 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.010, 0.010, 0.012, 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) let lc = mix(vec3(0.30, 1.0, 0.40), vec3(1.0, 0.78, 0.20), step(0.8, (ka - foot) / max(travel, 1.0))) res = mix(res, au_led(res, p, dotp, vec2(1.25 - px, 1.25 - px), lit, 0.0, px, mix(lc, vec3(1.0, 1.0, 1.0), 0.55), lc), 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.52, 0.53, 0.55, 1.0) * tick) // THE CAP, the bench'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 { // The bench'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.227, 0.612, 1.0, 1.0) * clamp(ring, 0.0, 1.0) * self.focus * live) } // ---------------------------------------------------------------- knobs // The angle a knob's value stands at, and the direction it points: angles // run from straight down, clockwise, the gap at the bottom. let au_dir = fn(a: float) -> vec2 { return vec2(-sin(a), cos(a)) } let au_angle = fn(q: vec2) -> float { var ang = atan2(-q.x, q.y) if ang < 0.0 { ang = ang + 2.0 * PI } return ang } // A SMALL KNOB, as a dash has them: a machined aluminium knob in a // chrome-rimmed socket. Out to in: a polished chrome ring on the ground, a // narrow dark socket whose floor is lit amber from the start of the scale // up to the value (from the origin, on a knob that has one), the knob's // soft shadow in the socket, its edge knurled bright silver with fine // ridges that turn with it, a thin dark groove, a light spun-aluminium cap // (the lathe's fan of light along the light's axis over fine concentric // rings, brightest up and to the left), a small dark centre, and a dark // index line on the cap toward the value, red while held. Angles from // straight down, clockwise. let au_knurl = fn(p: vec2, c: vec2, rr: float, px: float, slide: float, origin: float, arc_origin: float, gap: float, hover: float, held: float, focus: float, disabled: float) -> vec4 { let q = p - c let rl = length(q) let nk = q / max(rl, 0.001) let on = 1.0 - disabled let rb = Finish.snap(rr, px) let bw = max(Finish.snap(0.08 * rb, px), px * 2.0) let rs = rb - bw let R = Finish.snap(0.82 * rb, px) let sh = Finish.drop(p, c, vec2(rb, rb), rb, vec2(0.0, 1.2), 1.6) * 0.75 * (1.0 - smoothstep(rb + 3.0, rb + 6.0, rl)) var res = vec4(0.0, 0.0, 0.0, sh) res = Finish.over(res, au_chrome_ring(p, c, rs, rb, px, hover)) // The socket, and the amber light on its floor over the value's arc. 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) let ang = au_angle(q) let ac = clamp(ang, a0, a1) let ap = c + au_dir(ac) * ((R + rs) * 0.5) let da = length(p - ap) let live = step(0.002, a1 - a0) * on let sd = rl - rs var sc = mix(vec3(0.010, 0.010, 0.012), vec3(0.045, 0.046, 0.050), smoothstep(-0.6, 0.9, nk.y)) let aw = max((rs - R) * 0.32, px * 0.8) let arc = Finish.cover(da - aw, px) * live let ag = exp(-max(da - aw, 0.0) / 1.2) * 0.5 * live sc = mix(sc, vec3(1.0, 0.55, 0.12), clamp(ag, 0.0, 1.0)) sc = mix(sc, vec3(1.0, 0.86, 0.55), arc) let sa = Finish.cover(sd, px) res = Finish.over(res, vec4(sc * sa, sa)) let ks = Finish.drop(p, c, vec2(R, R), R, vec2(0.0, 0.08 * R), 0.10 * R) * 0.8 * sa res = Finish.over(res, vec4(0.0, 0.0, 0.0, ks)) // The edge: bright silver knurling, the ridges turning with the knob, // lit on its upper side and greyer toward its foot. let up = smoothstep(-1.0, 1.0, -nk.y) let nr = floor(R * 3.2) let ridge = 0.5 + 0.5 * cos(nr * (ang - ve)) var kc = mix(vec3(0.30, 0.305, 0.32), vec3(0.88, 0.885, 0.90), pow(ridge, 1.5) * (0.35 + 0.65 * up)) kc = kc * (0.62 + 0.38 * up) kc = mix(kc, vec3(0.010, 0.010, 0.012), Finish.band(rl - R, 0.0, px, px)) // A thin dark groove between the knurling and the cap. let rt = Finish.snap(0.78 * R, px) let gw = max(px, Finish.snap(0.035 * R, px)) kc = mix(kc, vec3(0.06, 0.062, 0.068), Finish.cover(rl - rt, px)) // The cap: light spun aluminium, the lathe's fan of light along the // light's axis over fine concentric rings, the upper left brightest. let rc = rt - gw let t = clamp((p.y - (c.y - rc)) / max(2.0 * rc, 1.0), 0.0, 1.0) var top = mix(vec3(0.800, 0.805, 0.815), vec3(0.560, 0.565, 0.580), smoothstep(0.0, 1.0, t)) let fan = cos(2.0 * (atan2(q.y, q.x) - atan2(-0.94, -0.35))) let rings = au_noise(vec2(rl * 3.0, 0.37)) - 0.5 + 0.6 * (au_noise(vec2(rl * 0.9, 9.1)) - 0.5) top = top * (1.0 + smoothstep(0.0, 1.5, rl) * (0.16 * fan + 0.10 * rings)) let sp = (q - vec2(-0.30 * rc, -0.35 * rc)) / max(0.45 * rc, 0.8) top = min(top + vec3(0.10, 0.10, 0.10) * exp(-dot(sp, sp)) + vec3(0.03, 0.03, 0.03) * hover * on, vec3(1.0, 1.0, 1.0)) top = mix(top, vec3(0.97, 0.975, 0.98), Finish.band(rl - rc, px, px * 2.0, px) * smoothstep(0.2, 0.9, -nk.y) * 0.7) kc = mix(kc, top, Finish.cover(rl - rc, px)) // A small dark centre where the cap was held on the lathe. kc = mix(kc, vec3(0.22, 0.225, 0.235), Finish.cover(rl - max(0.07 * rc, px * 1.2), px)) // The index: a dark line on the cap toward the value, red while held. let vd = au_dir(ve) let pl = dot(q, vd) let pa = abs(q.x * vd.y - q.y * vd.x) let pw = max(0.06 * R, px * 1.1) let ptr = Finish.cover(pa - pw, px) * Finish.cover(abs(pl - 0.58 * rc) - 0.32 * rc, px) kc = mix(kc, mix(vec3(0.07, 0.072, 0.08), vec3(0.85, 0.08, 0.05), held), ptr * (0.5 + 0.5 * on)) let kl = Finish.lum(kc) kc = mix(kc, vec3(kl, kl, kl) * 0.6 + vec3(0.03, 0.03, 0.035), disabled) let ka = Finish.cover(rl - R, px) res = Finish.over(res, vec4(kc * ka, ka)) let fr = Finish.ring_out(rl - rb, 0.0, px, px) * 0.9 + Finish.glow(rl - rb - px, 0.7) * 0.3 * (1.0 - Finish.cover(rl - rb, px)) return Finish.over(res, vec4(0.227, 0.612, 1.0, 1.0) * clamp(fr, 0.0, 1.0) * focus * on) } // A DIAL is a tachometer: a polished chrome bezel round a black face, a // scale of ten with a tick at every half, numerals 0 to 10 in a squared // dash type, the red line over the last fifth (its ticks and numerals red // too), and a red needle on a black hub, its light lying on the face round // it. The needle stands at the value; held, its glow swells. A faint glass // reflection lies over the upper face. Angles from straight down, // clockwise, the gap at the bottom. let au_tach = fn(p: vec2, c: vec2, rr: float, px: float, slide: float, gap: float, hover: float, held: float, focus: float, disabled: float) -> vec4 { let q = p - c let rl = length(q) let nk = q / max(rl, 0.001) let on = 1.0 - disabled let rb = Finish.snap(rr, px) let bw = Finish.snap(max(0.10 * rb, 2.0), px) let F = rb - bw - Finish.snap(max(0.025 * rb, px), px) // The gauge's shadow on the ground. let sh = Finish.drop(p, c, vec2(rb, rb), rb, vec2(0.0, 2.0), 2.4) * 0.8 * (1.0 - smoothstep(rb + 4.0, rb + 8.0, rl)) var res = vec4(0.0, 0.0, 0.0, sh) res = Finish.over(res, au_chrome_ring(p, c, rb - bw, rb, px, hover)) // The step inside the bezel, and the face. let sa = Finish.cover(rl - (rb - bw) - px * 0.5, px) res = Finish.over(res, vec4(0.008, 0.008, 0.010, 1.0) * sa) let one = PI / 180.0 let sweep = 2.0 * PI - gap * one let start = gap * one * 0.5 let ve = start + sweep * slide let ang = au_angle(q) let tp = (ang - start) / max(sweep, 0.01) let inscale = step(-0.02, tp) * step(tp, 1.02) let zone = step(0.795, tp) * inscale let tf = rl / max(F, 1.0) var face = mix(vec3(0.090, 0.091, 0.096), vec3(0.030, 0.030, 0.033), smoothstep(0.0, 1.0, tf)) // A lighter sweep of the face's own satin under the hub, as on the // pictures' gauges, and the bezel's shadow under its top edge. let lw = (q - vec2(0.0, 0.35 * F)) / vec2(0.75 * F, 0.45 * F) face = face + vec3(0.040, 0.041, 0.044) * exp(-dot(lw, lw)) face = face * (1.0 - 0.55 * Finish.inset(p, c, vec2(F, F), F, vec2(0.0, 1.5), 2.0)) // The red line: a band at the rim of the face over the last fifth. let zr = Finish.cover(abs(rl - 0.90 * F) - 0.06 * F, px) * zone let zc = mix(vec3(0.90, 0.07, 0.05), vec3(0.55, 0.02, 0.02), smoothstep(0.84, 0.96, tf)) face = mix(face, zc, zr * (0.55 + 0.45 * on)) // The scale: a tick at every half, the whole numbers longer. let j = clamp(floor(tp * 20.0 + 0.5), 0.0, 20.0) let aj = start + sweep * j / 20.0 let dj = au_dir(aj) let along = dot(q, dj) let across = abs(q.x * dj.y - q.y * dj.x) let major = 1.0 - step(0.5, j - 2.0 * floor(j * 0.5 + 0.01)) let tw = mix(max(0.012 * F, px * 0.6), max(0.024 * F, px), major) let t0 = mix(0.85 * F, 0.77 * F, major) let tick = Finish.cover(across - tw, px) * Finish.cover(abs(along - (t0 + 0.955 * F) * 0.5) - (0.955 * F - t0) * 0.5, px) * inscale let tz = step(0.795, j / 20.0) let ink = mix(vec3(0.93, 0.94, 0.95), vec3(1.0, 0.24, 0.18), tz) face = mix(face, ink, tick * (0.45 + 0.55 * on)) // The numerals, upright, set on a circle inside the ticks. let i = clamp(floor(tp * 10.0 + 0.5), 0.0, 10.0) let ai = start + sweep * i / 10.0 let hgt = 0.15 * F let nc = c + au_dir(ai) * (0.62 * F) let nd = au_number(p, nc, hgt, i, max(0.13 * hgt, px * 1.3)) let nz = step(7.5, i) face = mix(face, mix(vec3(0.93, 0.94, 0.95), vec3(1.0, 0.24, 0.18), nz), Finish.cover(nd, px) * (0.45 + 0.55 * on)) let fa = Finish.cover(rl - F, px) res = Finish.over(res, vec4(face * fa, fa)) // The needle: a tapered red blade from a short tail behind the hub to // the ticks, its shadow down and to the right, a glow round it. let vd = au_dir(ve) let nl = dot(q, vd) let na = abs(q.x * vd.y - q.y * vd.x) let u = clamp((nl + 0.18 * F) / (1.10 * F), 0.0, 1.0) let hw = mix(0.038 * F, 0.010 * F, u) + px * 0.25 let blade = max(na - hw, max(-0.18 * F - nl, nl - 0.92 * F)) let qs = q - vec2(0.8, 1.4) let nls = dot(qs, vd) let nas = abs(qs.x * vd.y - qs.y * vd.x) let us = clamp((nls + 0.18 * F) / (1.10 * F), 0.0, 1.0) let bs = max(nas - mix(0.038 * F, 0.010 * F, us), max(-0.18 * F - nls, nls - 0.92 * F)) res = Finish.over(res, vec4(0.0, 0.0, 0.0, 0.55 * Finish.cover(bs, 1.2) * fa)) let gx = max(blade, 0.0) let glow = (0.40 * exp(-(gx * gx) / (2.0 * 0.9 * 0.9)) + 0.18 * exp(-(gx * gx) / (2.0 * 2.4 * 2.4))) * (1.0 - Finish.cover(blade, px)) * fa * on * (0.8 + 0.5 * held) res = Finish.over(res, vec4(1.0, 0.12, 0.06, 1.0) * clamp(glow, 0.0, 1.0)) var nc2 = mix(vec3(1.0, 0.20, 0.12), vec3(0.75, 0.04, 0.03), smoothstep(0.0, 1.0, na / max(hw, 0.01))) nc2 = mix(nc2, vec3(1.0, 0.62, 0.55), (1.0 - smoothstep(0.0, 0.5, na / max(hw, 0.01))) * 0.5) let ng = Finish.lum(nc2) nc2 = mix(nc2, vec3(ng, ng, ng) * 0.7, disabled) let ba = Finish.cover(blade, px) res = Finish.over(res, vec4(nc2 * ba, ba)) // The hub: a black dome in a thin chrome ring, a highlight up and left. let hr = Finish.snap(max(0.12 * F, 2.5), px) res = Finish.over(res, au_chrome_ring(p, c, hr - max(px * 1.5, 0.025 * F), hr, px, hover)) let hq = (q - vec2(-0.3 * hr, -0.35 * hr)) / max(0.45 * hr, 0.5) let hc = mix(vec3(0.10, 0.10, 0.11), vec3(0.02, 0.02, 0.022), smoothstep(-1.0, 1.0, q.y / max(hr, 0.5))) + vec3(0.30, 0.30, 0.32) * exp(-dot(hq, hq)) let hd = rl - (hr - max(px * 1.5, 0.025 * F)) let ha = Finish.cover(hd, px) res = Finish.over(res, vec4(hc * ha, ha)) // The glass: a faint pale crescent over the upper face. let gq = (q - vec2(-0.1 * F, -0.55 * F)) / vec2(0.85 * F, 0.38 * F) let gls = exp(-dot(gq, gq) * 1.5) * smoothstep(-0.1, 0.4, -q.y / max(F, 1.0)) * 0.05 * fa res = vec4(res.rgb + vec3(gls, gls, gls) * res.a, res.a) let fr = Finish.ring_out(rl - rb, 0.0, px, px) * 0.9 + Finish.glow(rl - rb - px, 0.8) * 0.35 * (1.0 - Finish.cover(rl - rb, px)) return Finish.over(res, vec4(0.227, 0.612, 1.0, 1.0) * clamp(fr, 0.0, 1.0) * focus * on) } // The quad runs 10 points past the box, room for the knob's 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 = au_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 au_knurl(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 = au_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 au_tach(p, c, dia * 0.5 - 1.0, px, self.slide_pos, self.gap, self.hover, self.drag, self.focus, self.disabled) } // ---------------------------------------------------------------- fields // A FIELD is a well of dark carbon weave behind a polished silver frame, as // the pictures draw every entry box. The frame is two device pixels inside // the box's edge: the outer row a darker silver, the inner one bright where // it faces the light (along the top and the foot), and inside it a black // line where the floor drops away. The floor is the carbon weave held dark, // so the words and the placeholder keep their contrast on it, under the // frame's shadow along its top. Under // the hand the floor lifts a little; focused, the frame takes the blue and // a blue light lies along its inside. A text field clips what it holds, so // everything keeps inside its own box. let au_field = fn(p: vec2, size: vec2, o: vec2, px: float, fill: vec3, hover: float, focus: float, disabled: float) -> vec4 { let lo = vec2(au_snap(0.0, o.x, px), au_snap(0.0, o.y, px)) let hi = vec2(au_snap(size.x - px * 0.49, o.x, px), au_snap(size.y - px * 0.49, o.y, px)) let c = (lo + hi) * 0.5 let r = 3.0 let d = Material.sd_box(p, c, (hi - lo) * 0.5, r) let t = clamp((p.y - lo.y) / max(hi.y - lo.y, 1.0), 0.0, 1.0) let live = 1.0 - disabled // The floor. var fl = au_carbon(p + o, 2.5, 0.50 + 0.10 * hover * live) * (1.0 - 0.35 * disabled) + fill * 0.25 let fd = d + px * 2.0 fl = fl * (1.0 - 0.75 * Finish.inset(p, c, (hi - lo) * 0.5 - vec2(px * 2.0, px * 2.0), r - px * 2.0, vec2(0.0, 1.2), 1.4)) fl = mix(fl, vec3(0.0, 0.0, 0.0), Finish.band(fd, 0.0, px, px) * 0.85) // The frame: a polished silver band two device pixels wide. let o1 = Finish.band(d, 0.0, px, px) let o2 = Finish.band(d, px, px * 2.0, px) let lit = max(1.0 - smoothstep(0.0, 0.25, t), smoothstep(0.75, 1.0, t)) var s1 = mix(vec3(0.240, 0.244, 0.254), vec3(0.460, 0.466, 0.480), lit) var s2 = mix(vec3(0.400, 0.405, 0.418), vec3(0.720, 0.726, 0.738), lit) s1 = mix(s1, vec3(0.22, 0.42, 0.70), focus * live * 0.8) s2 = mix(s2, vec3(0.55, 0.80, 1.0), focus * live * 0.85) s1 = s1 * (1.0 - 0.45 * disabled) s2 = s2 * (1.0 - 0.45 * disabled) var col = mix(fl, s2, o2) col = mix(col, s1, o1) // The focus light along the frame's inside. let depth = max(-fd, 0.0) col = col + vec3(0.10, 0.36, 0.80) * exp(-depth / 1.6) * Finish.cover(fd, px) * focus * live * 0.6 let a = Finish.cover(d, px) return vec4(col * a, a) } // Text fields: the framed well, 26 points tall. 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 = au_px(self.draw_pass.dpi_factor) return au_field(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, vec3(0.030, 0.031, 0.034), self.hover, self.focus, self.disabled) } // A field well (a box that holds an input between a leading and a trailing // slot) is the same framed well. mod.widgets.FieldWell.draw_bg.pixel = fn() { let px = au_px(self.draw_pass.dpi_factor) return au_field(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, vec3(0.030, 0.031, 0.034), 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 = #f4fbff mod.widgets.TextInputFlat.draw_text.color = #dcdde0 mod.widgets.TextInputFlat.draw_text.color_empty = #868a90 mod.widgets.TextInputFlat.draw_text.color_disabled = #63666b // The number field: the same framed well, its stepper a pair of small caps // standing in it. mod.widgets.NumberField.draw_bg.pixel = fn() { let px = au_px(self.draw_pass.dpi_factor) return au_field(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, vec3(0.030, 0.031, 0.034), 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 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 = au_px(self.draw_pass.dpi_factor) let o = self.rect_pos let p = self.pos * self.rect_size // Two small caps, one over the other, three device pixels in from the // frame and a device pixel of floor between them. Under the hand a cap // lifts; pressed, it sinks. let x0 = au_snap(0.0, o.x, px) + px let xr = au_snap(self.rect_size.x - px * 0.49, o.x, px) - px * 4.0 let yt = au_snap(0.0, o.y, px) + px * 4.0 let yb = au_snap(self.rect_size.y - px * 0.49, o.y, px) - px * 4.0 let mid = au_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 = au_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.20, 0.20, 0.21, 1.0), 1.0) res = Finish.over(res, au_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.20, 0.20, 0.21, 1.0), 1.0)) let ink = mix(vec3(0.80, 0.81, 0.83), 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(au_seg(p, vec2(cx - 2.5, cu + 1.25), vec2(cx, cu - 1.25)), au_seg(p, vec2(cx, cu - 1.25), vec2(cx + 2.5, cu + 1.25))) let dd2 = min(au_seg(p, vec2(cx - 2.5, cd - 1.25), vec2(cx, cd + 1.25)), au_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 engraved divider and the chevron, laid on a cap. let au_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.0, 0.0, 0.0, 1.0) * dark) res = Finish.over(res, vec4(0.42, 0.43, 0.45, 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.85, 0.86, 0.88), vec3(0.36, 0.37, 0.4), disabled), 1.0), 1.0) return Finish.over(res, sdf.result) } // A choice box: a graphite cap with a panel of dark brushed steel let into // its face, a black seam round the panel, the chevron zone at its right. // Open, the cap is held down. let au_choice = fn(p: vec2, o: vec2, lo: vec2, hi: vec2, px: float, hover: float, down: float, disabled: float, focus: float) -> vec4 { let r = 3.5 var res = au_cap(p, lo, hi, r, px, hover, down, disabled, focus, 1.0, vec3(-0.03, -0.03, -0.03), vec4(0.20, 0.20, 0.21, 1.0), 1.0) let c = (lo + hi) * 0.5 let h = (hi - lo) * 0.5 let wd = Material.sd_box(p, c, h - vec2(2.0, 2.0), max(r - 1.5, 1.0)) var wv = au_dark_steel(p + o, px, 0.4 + 0.8 * hover) + vec3(0.012, 0.012, 0.013) wv = wv * (1.0 - 0.5 * down) * (1.0 - 0.5 * disabled) wv = wv * (1.0 - 0.6 * Finish.inset(p, c, h - vec2(2.0, 2.0), max(r - 1.5, 1.0), vec2(0.0, 1.0), 1.2)) wv = mix(wv, vec3(0.0, 0.0, 0.0), Finish.band(wd, 0.0, px, px) * 0.8) let wa = Finish.cover(wd, px) res = Finish.over(res, vec4(wv * wa, wa)) return au_chevron_zone(res, p, lo, hi, px, disabled) } // The drop-down: the choice box, 26 points, its chevron zone 18 points. 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)) } mod.widgets.DropDownFlat.draw_bg.pixel = fn() { let m = 7.0 let px = au_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(au_snap(0.0, o.x, px), au_snap(0.0, o.y, px)) let hi = vec2(au_snap(self.rect_size.x, o.x, px), au_snap(self.rect_size.y, o.y, px)) return au_choice(p, o, lo, hi, px, self.hover, max(self.down, self.active), self.disabled, self.focus) } // The combo box: the same choice box; its list opens from the chevron zone. 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 = au_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(au_snap(0.0, o.x, px), au_snap(0.0, o.y, px)) let hi = vec2(au_snap(self.rect_size.x, o.x, px), au_snap(self.rect_size.y, o.y, px)) return au_choice(p, o, lo, hi, px, self.hover, 0.0, self.disabled, self.focus) } // Open lists: a plate of dark brushed graphite in a silver edge, rows that // lift under the hand, and the chosen row lit blue -- the one fill in this // family -- with a pale line along its top. let au_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.120, 0.122, 0.130), vec3(0.075, 0.076, 0.082), p.y / max(size.y, 1.0)) let tx = au_plate_tex(p, px) col = col + vec3(tx, tx, tx) col = mix(col, vec3(0.50, 0.51, 0.53), Finish.band(d, px, px * 2.0, px) * step(p.y, c.y) * step(3.0, p.y)) col = mix(col, vec3(0.45, 0.46, 0.48), Finish.band(d, 0.0, px, px) * 0.5) col = mix(col, vec3(0.010, 0.010, 0.012), Finish.band(d, 0.0, px, px) * 0.5) let a = Finish.cover(d, px) return vec4(col * a, a) } let au_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.045 * hover * (1.0 - disabled) let t = clamp(p.y / max(size.y, 1.0), 0.0, 1.0) let sel = mix(vec3(0.200, 0.470, 0.820), vec3(0.120, 0.320, 0.620), t) let fill = vec4(mix(sel, vec3(0.55, 0.76, 1.0), 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 = au_px(self.draw_pass.dpi_factor) return au_menu(self.pos * self.rect_size, self.rect_size, px) } mod.widgets.PopupMenuItem.draw_bg.pixel = fn() { let px = au_px(self.draw_pass.dpi_factor) return au_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 = au_px(self.draw_pass.dpi_factor) return au_menu(self.pos * self.rect_size, self.rect_size, px) } mod.widgets.ComboBoxFlat.draw_item.pixel = fn() { let px = au_px(self.draw_pass.dpi_factor) return au_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 // THE DASH WINDOW: the value box of a dial and a count are small displays, // black glass in a polished silver frame, the number white on it, as the // readouts and the screen are. let au_glass_box = fn(p: vec2, lo: vec2, hi: vec2, px: float, r: 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) var col = mix(vec3(0.030, 0.031, 0.034), vec3(0.012, 0.012, 0.014), t) col = col * (1.0 - 0.6 * Finish.inset(p, c, h - vec2(px * 2.0, px * 2.0), max(r - px * 2.0, 0.5), vec2(0.0, 1.0), 1.0)) let lit = max(1.0 - smoothstep(0.0, 0.3, t), smoothstep(0.7, 1.0, t)) col = mix(col, vec3(0.0, 0.0, 0.0), Finish.band(d, px * 2.0, px * 3.0, px)) col = mix(col, mix(vec3(0.34, 0.345, 0.36), vec3(0.64, 0.645, 0.66), lit), Finish.band(d, px, px * 2.0, px)) col = mix(col, mix(vec3(0.18, 0.185, 0.20), vec3(0.36, 0.365, 0.38), lit), Finish.band(d, 0.0, px, px)) // The glass: a faint sheen across its upper half. let u = (p - lo) / max(hi - lo, vec2(1.0, 1.0)) let band = smoothstep(0.0, 0.15, u.x + u.y * 0.6) * (1.0 - smoothstep(0.3, 0.55, u.x + u.y * 0.6)) col = col + vec3(0.025, 0.025, 0.027) * band * Finish.cover(d + px * 3.0, px) let a = Finish.cover(d, px) return vec4(col * a, a) } mod.widgets.RotaryFlat.text_input.draw_bg.pixel = fn() { let px = au_px(self.draw_pass.dpi_factor) let o = self.rect_pos let p = self.pos * self.rect_size let lo = vec2(au_snap(0.0, o.x, px), au_snap(0.0, o.y, px)) let hi = vec2(au_snap(self.rect_size.x - px * 0.49, o.x, px), au_snap(self.rect_size.y - px * 0.49, o.y, px)) return au_glass_box(p, lo, hi, px, 2.5) } mod.widgets.RotaryFlat.text_input.draw_text.color = #f2f5f8 mod.widgets.Rotary.text_input.draw_bg.pixel = mod.widgets.RotaryFlat.text_input.draw_bg.pixel mod.widgets.Rotary.text_input.draw_text.color = #f2f5f8 mod.widgets.RotaryGradientY.text_input.draw_bg.pixel = mod.widgets.RotaryFlat.text_input.draw_bg.pixel mod.widgets.RotaryGradientY.text_input.draw_text.color = #f2f5f8 // ---------------------------------------------------------------- tabs // The segmented control: the whole strip one raised graphite cap; the // chosen answer is pushed in, its legend lit and a little shift light of // three LEDs -- green, amber, red -- burning over 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 = au_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(au_snap(0.0, o.x, px), au_snap(0.0, o.y, px)) let hi = vec2(au_snap(self.rect_size.x, o.x, px), au_snap(self.rect_size.y, o.y, px)) return au_cap(p, lo, hi, 3.5, px, 0.0, 0.0, 0.0, 0.0, 1.0, vec3(0.0, 0.0, 0.0), vec4(0.20, 0.20, 0.21, 1.0), 1.0) } // The chosen answer is a push button pressed in: 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 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 = au_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(au_snap(-2.0 * v, o.x, px) + px * v, au_snap(-2.0 * hz, o.y, px) + px * hz) let hi = vec2(au_snap(self.rect_size.x + 2.0 * v, o.x, px) - px * v, au_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 pressed-in cap: dark graphite, darker under the top edge where // the neighbours stand over it, the walls shaded. var col = mix(vec3(0.060, 0.061, 0.066), vec3(0.090, 0.091, 0.098), smoothstep(0.0, 1.0, t)) let tx = au_plate_tex(p, px) * 0.6 col = col + vec3(tx, tx, tx) let top = 0.75 * exp(-max(p.y - lo.y, 0.0) / (1.3 * 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) 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)) var res = vec4(col, 1.0) * inside // The lit lip along its foot. res = Finish.over(res, vec4(0.34, 0.35, 0.37, 1.0) * step(hi.y - px, p.y) * inside * 0.6) // The neighbours' ends standing over the well. 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 er = step(hi.x, p.x) * (1.0 - step(hi.x + px, p.x)) res = Finish.over(res, vec4(0.36, 0.37, 0.39, 1.0) * (el + er) * ey * hz) 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.36, 0.37, 0.39, 1.0) * eb * v) // The shift light: three LEDs in a row over the legend. let cx = (lo.x + hi.x) * 0.5 - 1.0 let ly = lo.y + max(4.0, hh * 0.17) let lr = vec2(1.6, 1.25) res = au_led(res, p, vec2(cx - 7.0, ly), lr, 1.0, 1.0, px, vec3(0.86, 1.0, 0.88), vec3(0.20, 0.95, 0.30)) res = au_led(res, p, vec2(cx, ly), lr, 1.0, 1.0, px, vec3(1.0, 0.98, 0.84), vec3(1.0, 0.78, 0.12)) return au_led(res, p, vec2(cx + 7.0, ly), lr, 1.0, 1.0, px, vec3(1.0, 0.88, 0.85), vec3(1.0, 0.18, 0.12)) } // Cap labels at the family's 8.5 points, the chosen one included; the // chosen one stands a little lower, under its shift light. 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 = #c9cbcf mod.widgets.SegmentedControlFlat.draw_text_selected.color = #ffffff mod.widgets.SegmentedControlVertical.draw_pill.radius = -1.0 // Tabs: a row of gold-rimmed caps standing side by side; the tab in use is // lit amber from within (`au_gold_tab`). mod.widgets.Tabs.height = 28.0 mod.widgets.Tabs.clip_x = false mod.widgets.Tabs.clip_y = false let au_tab = fn(pos: vec2, size: vec2, o: vec2, dpi: float, hover: float, active: float) -> vec4 { let m = 13.0 let px = au_px(dpi) let p = pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let lo = vec2(au_snap(1.0, o.x, px), au_snap(0.0, o.y, px)) let hi = vec2(au_snap(size.x - 1.0, o.x, px), au_snap(size.y, o.y, px)) return au_gold_tab(p, lo, hi, px, hover, 0.0, active, 0.0, 0.0) } 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 au_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 au_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 au_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) } // Gold legends; the tab in use burns pale amber, in semibold. mod.widgets.Tabs.draw_mark.color = #cfae6c mod.widgets.Tabs.draw_text.color = #cfae6c mod.widgets.Tabs.draw_text_active.color = #ffe2a8 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 FEEDBACK BAR: a window of black glass in its well, behind it a row of // light cells three device pixels wide with a dark device pixel between // them, lit from the start up to the value. In the sheet's own accent the // cells run through the rainbow along the bar, violet to orange, as a // tuning panel's bar does; given a role colour (a success, a warning) every // cell takes that colour. An unlit cell is its own colour at a sixth, dark // glass with the hue still in it. Still: the unknown value is a lit run in // the middle with its ends fading. let au_cells = fn(dst: vec4, p: vec2, lo: vec2, hi: vec2, px: float, pitch: float, cellw: float, value: float, col: vec3, rainbow: float, hot: float, unknown: float) -> vec4 { let span = max(hi.x - lo.x, 1.0) let k = floor((p.x - lo.x) / pitch) let cx = lo.x + (k + 0.5) * pitch let f = (p.x - lo.x) - k * pitch let cell = step(f, cellw) * step(lo.x, p.x) * step(p.x, hi.x - (pitch - cellw)) * step(lo.y, p.y) * step(p.y, hi.y) let t = clamp((cx - lo.x) / span, 0.0, 1.0) var c = mix(col, au_rainbow(t), rainbow) c = mix(c, vec3(1.0, 0.18, 0.12), step(hot, t)) var on = step(t, value) * step(0.0005, value) if unknown > 0.5 { on = smoothstep(0.22, 0.34, t) * (1.0 - smoothstep(0.66, 0.78, t)) } // A lit cell burns toward white down its middle and keeps its hue at // its ends, and its light leaks into the dark gaps beside it. An unlit // cell is dark glass with its hue faintly in it and a glint on its top // row. let ty = clamp((p.y - lo.y) / max(hi.y - lo.y, px), 0.0, 1.0) let mid = exp(-(ty - 0.45) * (ty - 0.45) / 0.08) let litc = mix(c, mix(c, vec3(1.0, 1.0, 1.0), 0.6), mid * 0.75) var dark = c * 0.13 + vec3(0.012, 0.012, 0.014) dark = dark + vec3(0.035, 0.035, 0.038) * (1.0 - step(lo.y + px, p.y)) let inside = step(lo.x, p.x) * step(p.x, hi.x) * step(lo.y, p.y) * step(p.y, hi.y) let leak = vec4(c * 0.25, 0.25) * on * (1.0 - cell) * inside return Finish.over(Finish.over(dst, leak), vec4(mix(dark, litc, on), 1.0) * cell) } // The progress bar: a 14 point well of black glass holding a row of tall // cells, four device pixels wide with two dark ones between. mod.widgets.ProgressBarFlat.height = 14.0 mod.widgets.ProgressBarFlat.draw_bg.thickness = uniform(14.0) mod.widgets.ProgressBar.height = 14.0 mod.widgets.ProgressBar.draw_bg.thickness = uniform(14.0) mod.widgets.ProgressBarFlat.draw_bg.pixel = fn() { let px = au_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 = au_snap((self.rect_size.y - th) * 0.5, o.y, px) let x0 = au_snap(self.rect_size.x * self.start, o.x, px) let x1 = au_snap(self.rect_size.x * self.end, o.x, px) let lo = vec2(x0, y0) let hi = vec2(x1, y0 + au_snap(th, 0.0, px) - px) var res = vec4(0.0, 0.0, 0.0, 0.0) if self.track > 0.5 { res = au_well(p, lo, hi, 1.5, px, vec3(0.016, 0.017, 0.019), 0.0, 0.0, 1.0) } let fill = self.fill_color() // The sheet's own accent asks for the rainbow; any other colour is a // role and lights the cells in its own hue. let dc = fill.rgb - vec3(0.235, 0.769, 1.0) let rainbow = 1.0 - step(0.05, dot(dc, dc)) let v = clamp((self.value - self.start) / max(self.end - self.start, 0.001), 0.0, 1.0) let clo = vec2(lo.x + px * 2.0, lo.y + px * 2.0) let chi = vec2(hi.x - px * 2.0, hi.y - px * 2.0) res = au_cells(res, p, clo, chi, px, px * 6.0, px * 4.0, v, fill.rgb, rainbow, 2.0, step(self.value, -0.001)) return res * self.opacity * fill.a } mod.widgets.ProgressBarFlat.draw_bg.color_primary = #3cc4ff // The level meter: across, the feedback bar's rainbow in cells of four // device pixels and a gap of two, red over its last stretch; standing up, // the classic ladder, green to amber to red, a cell of three device pixels // and a gap of one. Unlit cells are dark glass with their hue in it. The // clip lamp is an LED. 8 points thick. mod.widgets.LevelMeter.height = 22.0 mod.widgets.LevelMeterColumn.width = 8.0 mod.widgets.LevelMeter.draw_bg.pixel = fn() { let px = au_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) 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(au_snap(wlo.x, o.x, px), au_snap(wlo.y, o.y, px)) let hi = vec2(au_snap(whi.x, o.x, px), au_snap(whi.y, o.y, px) - px) var res = au_well(p, lo, hi, 1.0, px, vec3(0.014, 0.015, 0.017), 0.0, 0.0, 1.0) let pad = 2.0 * px let span = max(1.0, bar - pad * 2.0) let pitch = mix(px * mix(4.0, 7.0, step(11.0, across)), px * 4.0, v) let cw = mix(mix(0.67, 0.71, step(11.0, across)), 0.75, v) let cell = step(fract((a - pad) / pitch), cw) let k = floor((a - pad) / pitch) let share = clamp((k + 0.5) * pitch / 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) // The ladder's colours, and the rainbow's. var ladder = vec3(0.30, 0.95, 0.30) ladder = mix(ladder, vec3(1.0, 0.78, 0.18), step(self.hot_at, share)) ladder = mix(ladder, vec3(1.0, 0.20, 0.14), step(self.hot_to, share)) var bow = au_rainbow(share / max(self.hot_to, 0.01)) bow = mix(bow, vec3(1.0, 0.20, 0.14), step(self.hot_to, share)) let hue = mix(bow, ladder, v) let on = step(share, self.level) * step(0.001, self.level) let bt = clamp((b - pad) / max(across - pad * 2.0, px), 0.0, 1.0) let mid = exp(-(bt - 0.45) * (bt - 0.45) / 0.07) let litc = mix(hue, mix(hue, vec3(1.0, 1.0, 1.0), 0.5), mid * 0.6) let unlit = hue * 0.15 + vec3(0.012, 0.012, 0.014) res = Finish.over(res, vec4(mix(unlit, litc, on), 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(hue, 1.0) * hc * cell * inb) if self.lamp > 0.5 { let lc = mix(vec2(bar + 3.0 + across * 0.5, sz.y * 0.5), vec2(sz.x * 0.5, across * 0.5), v) // The clip lamp: a dot on a slim meter, a short red bar standing // across a tall one. let rl = clamp(across * 0.5 - 1.5, 1.0, 1.75) let hs = mix(vec2(rl, rl), mix(vec2(across * 0.38, 1.6), vec2(1.6, across * 0.38), 1.0 - v), step(11.0, across)) res = au_led(res, p, lc, hs, step(0.5, self.over), 0.45, px, vec3(1.0, 0.957, 0.941), vec3(1.0, 0.20, 0.14)) } 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 = au_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.020, 0.021, 0.024, 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.227, 0.612, 1.0, 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, au_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 graphite cap inset // 1 point. mod.widgets.ScrollBar.bar_size = 8.0 mod.widgets.ScrollBar.bar_side_margin = 1.0 mod.widgets.ScrollBar.draw_bg.pixel = fn() { let px = au_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(au_snap(0.0, o.x, px), au_snap(0.0, o.y, px)) let hi = vec2(au_snap(sz.x, o.x, px), au_snap(sz.y, o.y, px)) var res = au_well(p, lo, hi, 2.0, px, vec3(0.030, 0.031, 0.034), 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(au_snap(tlo.x, o.x, px), au_snap(tlo.y, o.y, px)) let shi = vec2(au_snap(thi.x, o.x, px), au_snap(thi.y, o.y, px)) res = Finish.over(res, au_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.20, 0.20, 0.21, 1.0), 1.0)) return res * self.opacity } // A RAISED PANEL is a plate of brushed steel, lighter than the deck, with // two corners cut on the diagonal (the top right and the bottom left), as a // race dash cuts its panels: a firm shadow under it that stays within the // gap to its neighbours, the black outline, the steel (`au_steel`), and a // bevel two device pixels deep lit where it faces up (the top edge and the // cut at the top right) and dark at the foot. let au_panel = fn(p: vec2, size: vec2, o: vec2, px: float, hover: float) -> vec4 { let lo = vec2(au_snap(0.0, o.x, px), au_snap(0.0, o.y, px)) let hi = vec2(au_snap(size.x, o.x, px), au_snap(size.y, o.y, px)) let c = (lo + hi) * 0.5 let h = (hi - lo) * 0.5 let k = Finish.snap(min(10.0, 0.22 * min(h.x, h.y) * 2.0), px) let d = Finish.sd_notch(p, c, h, vec4(1.5, k, 1.5, k)) let sh1 = Finish.drop(p, c, h - vec2(1.0, 0.0), 3.0, vec2(0.0, 1.5), 1.3) * 0.9 let sh2 = Finish.drop(p, c, h - vec2(5.0, 2.0), 3.0, vec2(0.0, 4.5), 4.0) * 0.65 let sh = (1.0 - (1.0 - sh1) * (1.0 - sh2)) * (1.0 - Finish.cover(d, px)) var res = vec4(0.0, 0.0, 0.0, sh) 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) var col = au_steel(p, px, t, u) + vec3(0.012, 0.012, 0.013) * hover // The bevel, lit by the way the edge faces: up (the top and the cut at // the top right) bright, the sides a quiet line, the foot dark. let e = 0.5 * px let gx = Finish.sd_notch(p + vec2(e, 0.0), c, h, vec4(1.5, k, 1.5, k)) - Finish.sd_notch(p - vec2(e, 0.0), c, h, vec4(1.5, k, 1.5, k)) let gy = Finish.sd_notch(p + vec2(0.0, e), c, h, vec4(1.5, k, 1.5, k)) - Finish.sd_notch(p - vec2(0.0, e), c, h, vec4(1.5, k, 1.5, k)) let g = vec2(gx, gy) / max(length(vec2(gx, gy)), 0.0001) let facing = dot(g, vec2(-0.35, -0.94)) let r1 = Finish.band(d, px, px * 2.0, px) let r2 = Finish.band(d, px * 2.0, px * 3.0, px) let lit = smoothstep(0.2, 0.9, facing) col = mix(col, mix(vec3(0.30, 0.305, 0.32), vec3(0.74, 0.75, 0.77), lit), r1 * (0.55 + 0.45 * lit)) col = mix(col, mix(vec3(0.16, 0.162, 0.17), vec3(0.40, 0.405, 0.42), lit), r2 * 0.7) col = mix(col, vec3(0.004, 0.004, 0.006), Finish.band(d, 0.0, px, px)) let a = Finish.cover(d, px) return Finish.over(res, vec4(col * a, a)) } // AN INSET is a hexagon grille sunk in a polished chrome frame: the frame // three and a half points wide, mirroring the room across its width and lit // along its top and foot, a black line where the grille drops away, and the // perforated mesh in it. let au_tray = fn(p: vec2, size: vec2, o: vec2, px: float, r: float) -> vec4 { let lo = vec2(au_snap(0.0, o.x, px), au_snap(0.0, o.y, px)) let hi = vec2(au_snap(size.x, o.x, px), au_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) // The frame: chrome, its profile across the band like a rounded rail. let fw = au_snap(3.5, 0.0, px) let fu = clamp(-d / max(fw, px), 0.0, 1.0) let ny = clamp(fu * 2.0 - 1.0, -1.0, 1.0) let lit = max(1.0 - smoothstep(0.0, 0.2, t), smoothstep(0.85, 1.0, t)) var fc = mix(vec3(0.30, 0.305, 0.32), vec3(0.86, 0.865, 0.875), exp(-(ny + 0.30) * (ny + 0.30) / 0.06)) fc = mix(fc, fc * 0.45, smoothstep(0.2, 1.0, ny)) fc = mix(fc * 0.72, min(fc * 1.08, vec3(1.0, 1.0, 1.0)), lit) // The grille: a hexagon mesh, holes six points apart, its web dark // steel with a fine light on the rim over each hole, a broad soft sheen // across the middle, under the frame's shadow along its top. The web // is held dark enough that light words keep 5:1 on every part of it. 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 fu2 = clamp((p.x - flo.x) / max(fhi.x - flo.x, 1.0), 0.0, 1.0) let webc = vec3(0.262, 0.266, 0.278) + vec3(0.014, 0.014, 0.015) * exp(-(fu2 - 0.55) * (fu2 - 0.55) / 0.05) var pl = au_mesh(p - flo - vec2(3.0, 3.0), 6.0, 1.5, px, webc, 0.16) pl = min(pl, vec3(0.33, 0.335, 0.35)) pl = pl * (1.0 - 0.7 * Finish.inset(p, (flo + fhi) * 0.5, (fhi - flo) * 0.5, max(r - 2.0, 1.0), vec2(0.0, 1.5), 1.8)) pl = mix(pl, vec3(0.010, 0.010, 0.012), Finish.band(fd, 0.0, px, px)) var col = mix(fc, pl, Finish.cover(fd, px)) col = mix(col, vec3(0.008, 0.008, 0.010), Finish.band(d, 0.0, px, px)) let a = Finish.cover(d, px) var res = vec4(col * a, a) // A breath of the frame's light on the ground under its foot. let below = step(hi.y + px, p.y) * (1.0 - a) * smoothstep(0.0, 6.0, min(p.x - lo.x, hi.x - p.x)) return Finish.over(vec4(0.50, 0.51, 0.53, 1.0) * exp(-max(p.y - hi.y - px, 0.0) / 1.4) * 0.14 * below, res) } 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 = au_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 au_tray(p, self.rect_size, self.rect_pos, px, 4.0) } return au_panel(p, self.rect_size, self.rect_pos, px, 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 = au_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) } // A countersunk screw at `c`: a chrome head `r` across in a dark ring, a // slot cut across it at the angle `a`. Premultiplied over `dst`. let au_screw = fn(dst: vec4, p: vec2, c: vec2, r: float, a: float, px: float) -> vec4 { let q = p - c let rl = length(q) var res = Finish.over(dst, vec4(0.0, 0.0, 0.0, 0.55) * Finish.cover(rl - r - px, px)) let nq = q / max(r, 0.5) let nz = sqrt(max(1.0 - dot(nq, nq), 0.0)) let n = normalize(vec3(nq * 0.6, nz + 0.001)) let rf = vec3(2.0 * n.z * n.x, 2.0 * n.z * n.y, 2.0 * n.z * n.z - 1.0) var hc = au_chrome_room(rf, 0.0) * 0.85 let sd = vec2(cos(a), sin(a)) let slot = abs(q.x * sd.y - q.y * sd.x) hc = mix(hc, vec3(0.04, 0.04, 0.045), Finish.cover(slot - max(r * 0.16, px * 0.6), px) * step(rl, r - px)) hc = mix(hc, vec3(0.02, 0.02, 0.025), Finish.band(rl - r, 0.0, px, px)) let ha = Finish.cover(rl - r, px) return Finish.over(res, vec4(hc * ha, ha)) } // THE CARD is a plate of the same brushed steel with rounded corners, held // by a countersunk screw in each corner: the shadow under it, the black // outline, a bevel lit along its top and dark at its foot. A pressable card // lifts under the hand. let au_card = fn(p: vec2, size: vec2, o: vec2, px: float, hover: float) -> vec4 { let lo = vec2(au_snap(0.0, o.x, px), au_snap(0.0, o.y, px)) let hi = vec2(au_snap(size.x, o.x, px), au_snap(size.y, o.y, px)) let c = (lo + hi) * 0.5 let h = (hi - lo) * 0.5 let r = 5.0 let d = Material.sd_box(p, c, h, r) let sh1 = Finish.drop(p, c, h - vec2(1.0, 0.0), r, vec2(0.0, 1.5), 1.3) * 0.9 let sh2 = Finish.drop(p, c, h - vec2(5.0, 2.0), r, vec2(0.0, 4.5), 4.0) * 0.6 let sh = (1.0 - (1.0 - sh1) * (1.0 - sh2)) * (1.0 - Finish.cover(d, px)) var res = vec4(0.0, 0.0, 0.0, sh) let t = clamp((p.y - lo.y) / max(hi.y - lo.y, 1.0), 0.0, 1.0) let u = clamp((p.x - lo.x) / max(hi.x - lo.x, 1.0), 0.0, 1.0) var col = au_steel(p, px, t, u) + vec3(0.012, 0.012, 0.013) * hover let r1 = Finish.band(d, px, px * 2.0, px) let r2 = Finish.band(d, px * 2.0, px * 3.0, px) let up = 1.0 - smoothstep(0.0, r + px * 3.0, p.y - lo.y) let foot = step(hi.y - r - px * 2.0, p.y) col = mix(col, vec3(0.74, 0.75, 0.77), r1 * up) col = mix(col, vec3(0.44, 0.45, 0.47), r2 * up) col = mix(col, vec3(0.40, 0.41, 0.43), r1 * (1.0 - up) * (1.0 - foot) * 0.6) col = mix(col, vec3(0.16, 0.165, 0.175), (r1 + r2) * foot) col = mix(col, vec3(0.004, 0.004, 0.006), Finish.band(d, 0.0, px, px)) let a = Finish.cover(d, px) res = Finish.over(res, vec4(col * a, a)) let si = 5.5 res = au_screw(res, p, vec2(lo.x + si, lo.y + si), 2.2, 0.6, px) res = au_screw(res, p, vec2(hi.x - si, lo.y + si), 2.2, 2.1, px) res = au_screw(res, p, vec2(lo.x + si, hi.y - si), 2.2, 1.3, px) return au_screw(res, p, vec2(hi.x - si, hi.y - si), 2.2, 2.7, px) } mod.widgets.Card.clip_x = false mod.widgets.Card.clip_y = false mod.widgets.Card.padding = mod.turtle.Inset{left: 16.0 right: 16.0 top: 14.0 bottom: 14.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 = au_px(self.draw_pass.dpi_factor) let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) return au_card(p, self.rect_size, self.rect_pos, px, self.hover) } // List rows: 20 points each, every row its own slab of graphite with a // point and a half of the ground between them, the text 10 points in. // Under the hand the slab lifts; the chosen row is cast blue and carries a // lit blue bar at its left end. 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 = au_px(self.draw_pass.dpi_factor) let o = self.rect_pos let p = self.pos * self.rect_size let live = 1.0 - self.disabled let y0 = au_snap(0.0, o.y, px) let y1 = au_snap(self.rect_size.y - 1.5, o.y, px) let x0 = au_snap(0.0, o.x, px) let x1 = au_snap(self.rect_size.x, o.x, px) let t = clamp((p.y - y0) / max(y1 - y0, 1.0), 0.0, 1.0) 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 sel = self.selected var col = mix(vec3(0.165, 0.167, 0.176), vec3(0.115, 0.117, 0.124), t) col = col + vec3(0.030, 0.031, 0.033) * self.hover * live - vec3(0.02, 0.02, 0.02) * self.down col = mix(col, mix(vec3(0.150, 0.215, 0.300), vec3(0.100, 0.150, 0.215), t), sel) let tx = au_plate_tex(p, px) * 0.7 col = col + vec3(tx, tx, tx) col = mix(col, mix(vec3(0.40, 0.405, 0.42), vec3(0.45, 0.60, 0.78), sel), Finish.band(d, px, px * 2.0, px) * (1.0 - step(cb.y, p.y)) * 0.8) col = mix(col, vec3(0.012, 0.012, 0.014), Finish.band(d, 0.0, px, px)) var res = vec4(col, 1.0) * Finish.cover(d, px) let lc = vec2(au_snap(3.5, o.x, px) - px * 0.5, cb.y) let bar = au_led(res, p, lc, vec2(px * 0.5 + 0.01, max((y1 - y0) * 0.24, 2.0)), live, 0.8, px, vec3(0.88, 0.96, 1.0), vec3(0.227, 0.612, 1.0)) return mix(res, bar, sel) } mod.widgets.ListItem.draw_text.color = #dcdde0 mod.widgets.ListItem.draw_text_2.color = #8f9399 // THE START BUTTON: a chip is an illuminated push button of the kind a dash // starts its engine with. A polished chrome bezel, a face of dark blue // glass lit from behind (a pale bloom low in its middle and a blue light // round its rim), and a halo of the same blue on the ground round it, gone // within half the gap. A chip given a role colour burns in that colour. // Under the hand it brightens; pressed, it sinks and dims. mod.widgets.ChipFlat.draw_text.text_style.font_size = 8.5 mod.widgets.ChipFlat.draw_bg.pixel = fn() { let px = au_px(self.draw_pass.dpi_factor) let o = self.rect_pos let p = self.pos * self.rect_size let lo = vec2(au_snap(0.0, o.x, px), au_snap(0.0, o.y, px)) let hi = vec2(au_snap(self.rect_size.x, o.x, px), au_snap(self.rect_size.y, o.y, px) - px) let c = (lo + hi) * 0.5 let h = (hi - lo) * 0.5 let r = min(4.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) // The light's colour: the sheet's blue, or the role's own hue. let l = Finish.lum(self.color.rgb) let ch = length(self.color.rgb - vec3(l, l, l)) let role = smoothstep(0.08, 0.16, ch) * self.color.a let lc = mix(vec3(0.20, 0.58, 1.0), self.color.rgb / max(max(self.color.x, max(self.color.y, self.color.z)), 0.01), role) let on = (0.85 + 0.25 * self.hover) * (1.0 - 0.45 * self.down) // The bezel: two device pixels of chrome, bright on the top and foot. let lit = max(1.0 - smoothstep(0.0, 0.3, t), smoothstep(0.7, 1.0, t)) var col = mix(vec3(0.45, 0.455, 0.47), vec3(0.90, 0.905, 0.915), lit) let gd = d + px * 2.0 // The glass. var gl = mix(lc * 0.10, lc * 0.05, t) + vec3(0.015, 0.016, 0.02) let bq = (p - vec2(c.x, hi.y - h.y * 0.35)) / vec2(max(h.x * 0.8, 1.0), max(h.y * 0.8, 1.0)) gl = gl + mix(lc, vec3(1.0, 1.0, 1.0), 0.3) * exp(-dot(bq, bq)) * 0.22 * on let depth = max(-gd, 0.0) gl = gl + lc * exp(-depth / 1.4) * 0.55 * on gl = mix(gl, mix(lc, vec3(1.0, 1.0, 1.0), 0.5), Finish.band(gd, 0.0, px, px) * 0.8 * on) col = mix(col, gl, Finish.cover(gd, px)) col = mix(col, vec3(0.012, 0.012, 0.016), Finish.band(d, 0.0, px, px)) let a = Finish.cover(d, px) var res = vec4(col * a, a) let x = max(d, 0.0) let halo = (0.40 * exp(-(x * x) / (2.0 * 0.9 * 0.9)) + 0.16 * exp(-(x * x) / (2.0 * 2.0 * 2.0))) * (1.0 - smoothstep(2.5, 4.0, x)) * (1.0 - a) res = Finish.over(vec4(lc, 1.0) * clamp(halo * on, 0.0, 1.0), res) if self.keyed > 0.0 { res = Finish.over(res, vec4(0.227, 0.612, 1.0, 1.0) * Finish.ring_out(d, 0.0, px, px) * self.keyed) } return res } mod.widgets.ChipFlat.draw_text.color = #dff0ff // ---------------------------------------------------------------- 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.32 * a, 0.33 * a, 0.36 * 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.5 * c.a return main + vec4(0.62 * a, 0.36 * a, 0.08 * 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 = au_px(self.draw_pass.dpi_factor) let p = self.pos * self.rect_size let y0 = au_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.010, 0.010, 0.012, 1.0) * dark + vec4(0.26, 0.265, 0.28, 1.0) * light } mod.widgets.Vr.draw_bg.pixel = fn() { let px = au_px(self.draw_pass.dpi_factor) let p = self.pos * self.rect_size let x0 = au_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.010, 0.010, 0.012, 1.0) * dark + vec4(0.26, 0.265, 0.28, 1.0) * light } // ---------------------------------------------------------------- ground // A splitter is a seam cut into the deck: the deck shows through its strip, // and down its middle a device pixel of black over one of light. Under the // hand, held or focused, the seam lights blue along the grip's length. mod.widgets.Splitter.draw_bg.pixel = fn() { let px = au_px(self.draw_pass.dpi_factor) let p = self.pos * self.rect_size let v = self.is_vertical let across = mix(p.y, p.x, v) let along = mix(p.x, p.y, v) let thick = mix(self.rect_size.y, self.rect_size.x, v) let span = mix(self.rect_size.x, self.rect_size.y, v) let o = mix(self.rect_pos.y, self.rect_pos.x, v) let m = au_snap(thick * 0.5, o, px) - px let dark = step(m, across) * (1.0 - step(m + px, across)) let light = step(m + px, across) * (1.0 - step(m + px * 2.0, across)) let res = vec4(0.008, 0.008, 0.010, 1.0) * dark * 0.85 + vec4(0.40, 0.41, 0.43, 1.0) * light * 0.45 let act = max(max(self.hover, self.drag), self.focus) let inbar = 1.0 - step(self.bar_size * 0.5, abs(along - span * 0.5)) return Finish.over(res, vec4(0.227, 0.612, 1.0, 1.0) * (dark + light) * act * inbar) } // The rows of a tree stand on the deck, clear; the open row is a slab of // graphite cast blue, with a lit bar at its left end, as the list's chosen // row. mod.widgets.FileTreeNode.draw_bg.pixel = fn() { let px = au_px(self.draw_pass.dpi_factor) let p = self.pos * self.rect_size let o = self.rect_pos let y0 = au_snap(0.0, o.y, px) let y1 = au_snap(self.rect_size.y, o.y, px) - px let x0 = au_snap(0.0, o.x, px) let x1 = au_snap(self.rect_size.x, o.x, px) let t = clamp((p.y - y0) / max(y1 - y0, 1.0), 0.0, 1.0) 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) var col = mix(vec3(0.150, 0.215, 0.300), vec3(0.100, 0.150, 0.215), t) col = mix(col, vec3(0.45, 0.60, 0.78), Finish.band(d, px, px * 2.0, px) * (1.0 - step(cb.y, p.y)) * 0.8) col = mix(col, vec3(0.012, 0.012, 0.014), Finish.band(d, 0.0, px, px)) var res = vec4(col, 1.0) * Finish.cover(d, px) let lc = vec2(au_snap(3.5, o.x, px) - px * 0.5, cb.y) res = au_led(res, p, lc, vec2(px * 0.5 + 0.01, max((y1 - y0) * 0.24, 2.0)), 1.0, 0.8, px, vec3(0.88, 0.96, 1.0), vec3(0.227, 0.612, 1.0)) return res * self.active } // 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 = #2c2e32 // The window's own ground (`au_ground`): a brushed steel deck between bands // of carbon weave. mod.widgets.Window.show_bg = true mod.widgets.Window.draw_bg.pixel = fn() { let px = au_px(self.draw_pass.dpi_factor) let p = self.pos * self.rect_size return vec4(au_ground(p + self.rect_pos, px, self.rect_size.x, self.rect_size.y), 1.0) } // ---------------------------------------------------------------- badge // The badge is drawn with the screens, as a small dash window (below). // ---------------------------------------------------------------- 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 groove and // the same neon tube as the Slider, lit between two round chrome 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 = 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.RangeSliderFlat.draw_bg.pixel = fn() { let m = 10.0 let px = au_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 = au_snap(self.label_px + (self.rect_size.y - self.label_px) * 0.5, o.y, px) let lo = vec2(au_snap(0.0, o.x, px), cy - 5.5) let hi = vec2(au_snap(self.rect_size.x, o.x, px), cy + 5.5) let col = au_neon_at(o) var res = au_groove(p, o, lo, hi, px, self.disabled) let span = self.rect_size.x - rad * 2.0 let h0 = rad + self.start_pos * span let h1 = rad + self.end_pos * span res = au_tube(res, p, h0, h1, cy, 2.7, px, col, self.disabled) res = au_ball(res, p, vec2(h0, cy), rad, px, self.hot_start, max(self.drag * self.hot_start, self.focus), self.disabled, col) return au_ball(res, p, vec2(h1, cy), rad, px, self.hot_end, max(self.drag * self.hot_end, self.focus), self.disabled, col) } // ---------------------------------------------------------------- switches and meter // THE ROCKER is a round illuminated push button, as a dash has for its // ignition: an 18 point button in a polished chrome bezel, its cap of dark // glass domed toward the light. On, a ring of red light burns round the cap // inside the bezel and the glass glows red from within, its light lying on // the ground round the bezel; off, the ring is dark tinted glass. Pressed, // the cap sinks and dims. It flips on a click and does not drag. mod.widgets.ToggleRocker.draw_bg.size = uniform(18.0) mod.widgets.ToggleRocker.draw_bg.pill_aspect = uniform(1.0) mod.widgets.ToggleRocker.label_walk.margin.left = 25.0 mod.widgets.ToggleRocker.draw_bg.pixel = fn() { let m = 5.0 let px = au_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 R = self.size * 0.5 let c = vec2(au_snap(R, o.x, px), au_snap(self.rect_size.y * 0.5, o.y, px)) let q = p - c let rl = length(q) let live = 1.0 - self.disabled let on = self.active * live let held = max(self.down, self.pressed) let red = vec3(1.0, 0.16, 0.10) let sh = Finish.drop(p, c, vec2(R, R), R, vec2(0.0, 1.0), 1.3) * 0.8 * (1.0 - Finish.cover(rl - R, px)) var res = vec4(0.0, 0.0, 0.0, sh) let x = max(rl - R, 0.0) let halo = (0.38 * exp(-(x * x) / (2.0 * 0.9 * 0.9)) + 0.16 * exp(-(x * x) / (2.0 * 2.0 * 2.0))) * (1.0 - smoothstep(1.4, 2.5, x)) * (1.0 - Finish.cover(rl - R, px)) res = Finish.over(res, vec4(red, 1.0) * clamp(halo * on, 0.0, 1.0)) res = Finish.over(res, au_chrome_ring(p, c, R - 1.8, R, px, self.hover)) // The light ring between the bezel and the cap. let ri = R - 1.8 let rc = R - 3.2 let ringd = abs(rl - (ri + rc) * 0.5) - (ri - rc) * 0.5 let ra = Finish.cover(ringd, px) let rt = clamp((rl - rc) / max(ri - rc, 0.5), 0.0, 1.0) let lit = mix(mix(red, vec3(1.0, 0.85, 0.80), 0.55 * (1.0 - abs(rt - 0.5) * 2.0)), red * 0.8, 0.2) let dark = mix(vec3(0.10, 0.03, 0.03), vec3(0.05, 0.02, 0.02), rt) res = Finish.over(res, vec4(mix(dark, lit, on) * ra, ra)) // The cap: dark glass domed toward the light, sunk when pressed. let t = clamp((p.y - (c.y - rc)) / max(2.0 * rc, 1.0), 0.0, 1.0) var cap = mix(vec3(0.20, 0.205, 0.215), vec3(0.06, 0.061, 0.066), smoothstep(0.0, 1.0, t)) cap = mix(cap, cap * 0.6, held) cap = cap + red * on * (0.30 * exp(-(rl * rl) / max(rc * rc * 0.6, 0.5)) + 0.12) let sp = (q - vec2(-0.3 * rc, -0.42 * rc)) / vec2(max(0.5 * rc, 0.5), max(0.3 * rc, 0.4)) cap = cap + vec3(0.30, 0.30, 0.31) * exp(-dot(sp, sp)) * (1.0 - 0.6 * held) * live cap = cap + vec3(0.03, 0.03, 0.03) * self.hover * live let cg = Finish.lum(cap) cap = mix(cap, vec3(cg, cg, cg) * 0.6, self.disabled) cap = mix(cap, vec3(0.0, 0.0, 0.0), Finish.band(rl - rc, 0.0, px, px) * 0.85) let ca = Finish.cover(rl - rc, px) res = Finish.over(res, vec4(cap * ca, ca)) let fr = Finish.ring_out(rl - R, 0.0, px, px) * 0.9 + Finish.glow(rl - R - px, 0.7) * 0.3 * (1.0 - Finish.cover(rl - R, px)) return Finish.over(res, vec4(0.227, 0.612, 1.0, 1.0) * clamp(fr, 0.0, 1.0) * self.focus * live) } // THE SLIDE SWITCH is a chrome bat-handle toggle on a graphite plate, as a // race car's switch panel has them: a chrome bushing in the middle of the // plate and the lever standing from it toward its state, left off and // right on, a tapered rod of polished chrome ending in a ball, its shadow // on the plate. Dragged, the lever swings through upright (pointing at the // viewer, only its ball over the bushing). A small green LED in the plate's // right corner is lit while it is on. mod.widgets.ToggleSlide.draw_bg.size = uniform(18.0) mod.widgets.ToggleSlide.draw_bg.pill_aspect = uniform(1.89) mod.widgets.ToggleSlide.label_walk.margin.left = 34.0 mod.widgets.ToggleSlide.draw_bg.pixel = fn() { let m = 5.0 let px = au_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 y0 = self.rect_size.y * 0.5 - th * 0.5 let lo = vec2(au_snap(0.0, o.x, px), au_snap(y0, o.y, px)) let hi = vec2(au_snap(tw, o.x, px), au_snap(y0 + th, o.y, px)) let live = 1.0 - self.disabled let on = self.active * live var res = au_cap(p, lo, hi, 3.0, px, 0.0, 0.0, self.disabled, self.focus, 1.0, vec3(-0.07, -0.07, -0.072), vec4(0.17, 0.17, 0.18, 1.0), 0.0) let c = vec2(au_snap((lo.x + hi.x) * 0.5, o.x, px), au_snap((lo.y + hi.y) * 0.5, o.y, px)) // The LED. res = au_led(res, p, vec2(hi.x - 3.5, lo.y + 3.5), vec2(1.2, 1.2), on, 0.8, px, vec3(0.86, 1.0, 0.88), vec3(0.20, 0.95, 0.30)) // The bushing: a chrome nut round a dark bore. res = Finish.over(res, Finish.drop(p, c, vec2(5.0, 5.0), 5.0, vec2(0.0, 0.8), 0.8) * vec4(0.0, 0.0, 0.0, 0.6)) res = Finish.over(res, au_chrome_ring(p, c, 2.4, 5.0, px, self.hover)) let bd = length(p - c) - 2.4 res = Finish.over(res, vec4(0.02, 0.02, 0.025, 1.0) * Finish.cover(bd, px)) // The lever. let kt = mix(self.active, self.drag_pos, self.drag) let s = kt * 2.0 - 1.0 let L = 12.0 * abs(s) let dir = vec2(sign(s + 0.0001), 0.0) let tip = c + dir * L let rb = 3.2 // Its shadow on the plate, down and to the right. let sq = p - vec2(1.0, 1.6) let sdl = min(au_seg(sq, c, tip) - mix(2.2, 1.4, clamp(dot(sq - c, dir) / max(L, 0.5), 0.0, 1.0)), length(sq - tip) - rb) res = Finish.over(res, vec4(0.0, 0.0, 0.0, 0.55 * Finish.cover(sdl, 1.0))) let u = clamp(dot(p - c, dir) / max(L, 0.5), 0.0, 1.0) let rw = mix(2.2, 1.4, u) let rod = au_seg(p, c, tip) - rw let across = clamp(((p - c).y) / max(rw, 0.3), -1.0, 1.0) var ch = mix(vec3(0.95, 0.95, 0.96), vec3(0.25, 0.25, 0.27), smoothstep(-0.6, 0.8, across)) ch = mix(ch, vec3(0.55, 0.555, 0.57), smoothstep(0.6, 1.0, across) * 0.6) ch = mix(ch, vec3(0.03, 0.03, 0.035), Finish.band(rod, 0.0, px, px) * 0.8) let ra = Finish.cover(rod, px) res = Finish.over(res, vec4(ch * ra, ra)) // The ball at its end: a chrome sphere. let bq = (p - tip) / rb let bz = sqrt(max(1.0 - dot(bq, bq), 0.0)) let n = normalize(vec3(bq, bz + 0.001)) let rf = vec3(2.0 * n.z * n.x, 2.0 * n.z * n.y, 2.0 * n.z * n.z - 1.0) var bc = au_chrome_room(rf, 1.0) * (1.0 + 0.1 * self.hover * live) let bg = Finish.lum(bc) bc = mix(bc, vec3(bg, bg, bg) * 0.5, self.disabled) let bdd = length(p - tip) - rb bc = mix(bc, vec3(0.03, 0.03, 0.035), Finish.band(bdd, 0.0, px, px)) let ba = Finish.cover(bdd, px) return Finish.over(res, vec4(bc * ba, ba)) } // THE METER is a gauge in the same family as the dials and the readouts: a // polished chrome frame round black glass, its scale and numbers white, the // red line red, a red needle on a chrome pivot with its glow on the face, a // faint glass sheen. The scale, its divisions and the needle's travel are // the library's own. mod.widgets.NeedleMeter.draw_bg.border_radius = uniform(3.0) 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 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) // The face: black, a little lighter round the pivot. let ft = clamp(r / max(rr * 1.3, 1.0), 0.0, 1.0) var face = mix(vec3(0.075, 0.076, 0.080), vec3(0.022, 0.022, 0.025), ft) let arc = Finish.cover(abs(r - rr) - px * 0.5, px) * on_scale let n = max(floor(self.majors + 0.5), 1.0) * max(floor(self.minors + 0.5), 1.0) let k = floor(clamp(t, 0.0, 1.0) * n + 0.5) let ak = (k / n - 0.5) * sweep let perp = abs(q.x * cos(ak) + q.y * sin(ak)) let mn = max(floor(self.minors + 0.5), 1.0) let major = 1.0 - step(0.5, k - mn * floor(k / mn + 0.0001)) let len = mix(self.tick_minor, self.tick_major, major) let radial = max(rr - len - r, r - rr) let tick = Finish.cover(perp - px * mix(0.5, 1.0, major), px) * Finish.cover(radial, px) * step(0.0, 0.0 - q.y * cos(ak) + q.x * sin(ak)) let inz = step(self.zone, k / n) face = mix(face, mix(vec3(0.92, 0.93, 0.94), vec3(1.0, 0.25, 0.18), inz), max(arc * (1.0 - step(self.zone, t)), 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, vec3(0.90, 0.07, 0.05), zone_t * zone_r) } // The needle: red, its glow on the face round it. 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 nb = max(across - px * 0.7, max(along - (rr + 3.0), 0.0 - along - max(self.pivot_size - px, 0.0))) let gx = max(nb, 0.0) face = mix(face, vec3(1.0, 0.12, 0.06), (0.35 * exp(-(gx * gx) / (2.0 * 0.8 * 0.8)) + 0.12 * exp(-(gx * gx) / (2.0 * 2.0 * 2.0))) * (1.0 - Finish.cover(nb, px))) face = mix(face, vec3(1.0, 0.22, 0.14), Finish.cover(nb, px)) // The pivot: a chrome cap. let pd = r - self.pivot_size let pq = (q - vec2(-0.3, -0.4) * self.pivot_size) / max(self.pivot_size * 0.6, 0.5) let pc = mix(vec3(0.35, 0.35, 0.37), vec3(0.95, 0.95, 0.96), exp(-dot(pq, pq))) face = mix(face, pc, Finish.cover(pd, px)) face = mix(face, vec3(0.02, 0.02, 0.025), Finish.band(pd, 0.0, px, px)) // The frame: two and a half points of polished chrome, bright on the // top and the foot, a black line where the glass meets it. let b = Finish.snap(2.5, px) let hf = max(ho - vec2(b, b), vec2(0.5, 0.5)) let rf = max(r_out - b * 0.5, 0.5) let face_d = Material.sd_box(p, c, hf, rf) face = face * (1.0 - 0.6 * Finish.inset(p, c, hf, rf, vec2(0.0, 2.0), 1.8)) face = mix(face, vec3(0.0, 0.0, 0.0), 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)) face = face + vec3(0.025, 0.025, 0.027) * band let fu = clamp(-outer / max(b, px), 0.0, 1.0) let ny = fu * 2.0 - 1.0 let ty = clamp((p.y - lo.y) / max(hi.y - lo.y, 1.0), 0.0, 1.0) let lit = max(1.0 - smoothstep(0.0, 0.2, ty), smoothstep(0.8, 1.0, ty)) var fc = mix(vec3(0.40, 0.405, 0.42), vec3(0.92, 0.925, 0.935), exp(-(ny + 0.2) * (ny + 0.2) / 0.12)) fc = mix(fc * 0.75, min(fc * 1.12, vec3(1.0, 1.0, 1.0)), lit) var col = mix(fc, face, Finish.cover(face_d, px)) col = mix(col, vec3(0.008, 0.008, 0.010), Finish.band(outer, 0.0, px, px)) let a = Finish.cover(outer, px) return vec4(col * a, a) * self.color.a * self.opacity } // THE SCREENS: a readout's window and the screen are the dash window, black // glass in a polished silver frame (`au_glass_box`); the segments are the // library's, lit white. mod.widgets.ScreenView.draw_bg.border_radius = uniform(3.0) mod.widgets.ScreenView.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * self.rect_size let o = self.rect_pos let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o let hi = vec2(Finish.snap(o.x + self.rect_size.x, px), Finish.snap(o.y + self.rect_size.y, px)) - o var res = au_glass_box(p, lo, hi, px, 3.5) if self.scan > 0.0 { let s = Finish.scan(p.y - lo.y, px, 3.0 * px, self.scan) res = vec4(res.rgb * s, res.a) } return res } mod.widgets.ScreenView.sheen = 0.0 // The badge (a count) is the same small dash window, its number white. mod.widgets.BadgePalette.neutral = #060708 mod.widgets.BadgePalette.on_neutral = #f2f5f8 mod.widgets.BadgeFlat.shape = mod.widgets.BadgeShape.Rounded mod.widgets.BadgeFlat.radius = 2.5 mod.widgets.BadgeFlat.draw_bg.pixel = fn() { let px = au_px(self.draw_pass.dpi_factor) let o = self.rect_pos let p = self.pos * self.rect_size let lo = vec2(au_snap(0.0, o.x, px), au_snap(0.0, o.y, px)) let hi = vec2(au_snap(self.rect_size.x - px * 0.49, o.x, px), au_snap(self.rect_size.y - px * 0.49, o.y, px)) let l = Finish.lum(self.color.rgb) let ch = length(self.color.rgb - vec3(l, l, l)) var res = au_glass_box(p, lo, hi, px, 2.5) // A badge given a role colour keeps it, as a tint in its glass. let d = Material.sd_box(p, (lo + hi) * 0.5, (hi - lo) * 0.5 - vec2(px * 3.0, px * 3.0), 1.0) res = Finish.over(res, vec4(self.color.rgb, 1.0) * Finish.cover(d, px) * smoothstep(0.08, 0.16, ch) * 0.5 * self.color.a) return res } // ---------------------------------------------------------------- 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 au_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 au_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 au_led_lamp_hue = fn(ink: vec3, vivid: float) -> vec3 { let f = au_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 au_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 = au_led_lamp_full(ink) let hue = au_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 = au_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, in a polished chrome 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 // A round lamp: the lens the lamp's size in the middle of its box, its // socket and its light in the room round it. A bar: a short light pipe // three quarters of a round lens thick and at most two of its // thicknesses long, set three points in from the start of its box, // its light gone within two points; so in a row of lamps it never // reaches past its box toward whatever stands after it. let tb = t * 0.75 let blen = min(max(self.rect_size.x - 2.0 * room, tb), 2.0 * tb) let cb = vec2(min(room, 2.0) + 1.0 + blen * 0.5, self.rect_size.y * 0.5) let c = mix(self.rect_size * 0.5, cb, self.bar) let hb = mix(vec2(t * 0.5, t * 0.5), vec2(blen * 0.5, tb * 0.5), self.bar) let reach_px = mix(max(room - 0.5, 2.0), 1.8, self.bar) let res = au_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.045, 0.046, 0.050), reach_px, vec3(0.62, 0.63, 0.65), 2.0) return res * self.opacity } true