use mod.prelude.widgets_internal.* use mod.sdf.Cap // Dark gloss with LED. 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 gl_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 gl_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 gl_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 raised cap: the drop and contact shadows under it, the black outline, // the quiet face gradient, the one top line, the faint side light, the // bottom inner rim. `lift` 1 casts the drop shadow, 0 seats the cap on the // ground with the contact shadow only. `rim` is the bottom inner rim's ink // (the LED's spill on a lit cap). Premultiplied. let gl_cap = fn(p: vec2, lo: vec2, hi: vec2, r: float, px: float, hover: float, down: float, disabled: float, focus: float, lift: float, tint: vec3, rim: vec4, gloss: float) -> vec4 { let c = (lo + hi) * 0.5 let h = (hi - lo) * 0.5 let hh = hi.y - lo.y // The shadow is sized by the cap's height up to a 24 point cap and no // further, so a large knob still throws at most 6 points. let sh_h = min(hh, 24.0) let d = Material.sd_box(p, c, h, r) let up = (1.0 - down) * (1.0 - disabled) // What the cap throws on the ground: a short drop that falls down with // a negative spread, and a tight contact line under the outline. // Two layers read off the reference cap: a firm edge 0.11H down whose // sides show for barely a point, and a soft tail to 0.28H. let drop1 = Finish.drop(p, c, h - vec2(0.05 * sh_h, 0.01 * sh_h), r, vec2(0.0, 0.11 * sh_h), 0.055 * sh_h) * 0.72 let drop2 = Finish.drop(p, c, h - vec2(0.1 * sh_h, 0.06 * sh_h), r, vec2(0.0, 0.14 * sh_h), 0.1 * sh_h) * 0.18 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.85 * (1.0 - 0.6 * disabled) let sh = 1.0 - (1.0 - drop) * (1.0 - contact) var under = vec4(0.0, 0.0, 0.0, sh) // A held cap sits in the ground: a 1 dp lip #474c56 shows on the ground // right under its lower edge. let lip = Finish.ring_out(d, 0.0, px, px) * step(c.y + h.y * 0.5, p.y) * down * (1.0 - disabled) under = Finish.over(under, vec4(0.278, 0.298, 0.337, 1.0) * lip) // The face. let t = clamp((p.y - lo.y) / max(hh, 1.0), 0.0, 1.0) var face = mix(vec3(0.247, 0.255, 0.290), vec3(0.165, 0.173, 0.204), t) + tint // Under the hand the face lifts; a small cap (a check box, a radio, a // thumb, a handle) lifts twice as far, or the change is lost on it. let small = 1.0 - step(23.5, hh) face = face + vec3(0.02, 0.02, 0.022) * (1.0 + small) * hover * up // Held or chosen: sunk like the chosen cell of the segmented strip -- a // floor that falls a little, and a shade under the top edge that fades // over a fifth of the height. `tint` casts a chosen well blue. let shade = 0.5 * (1.0 - smoothstep(0.0, 0.2 * min(hh, 24.0), p.y - lo.y - px)) let held = mix(mix(vec3(0.169, 0.176, 0.204), vec3(0.141, 0.149, 0.173), t) + tint, vec3(0.0, 0.0, 0.0), shade) face = mix(face, held, down * (1.0 - disabled)) face = mix(face, vec3(0.200, 0.208, 0.235), disabled) // Which way the outline faces here: the lines below follow it, so a // round cap gets an arc where a square one gets a straight line. let s = vec2(step(0.0, p.x - c.x) * 2.0 - 1.0, step(0.0, p.y - c.y) * 2.0 - 1.0) let q = abs(p - c) - (h - vec2(r, r)) let qc = max(q, vec2(0.0, 0.0)) var n = vec2(0.0, s.y) if q.x > q.y { n = vec2(s.x, 0.0) } if q.x > 0.0 && q.y > 0.0 { n = qc / max(length(qc), 0.0001) * s } let inner = Finish.band(d, px, px * 2.0, px) // The one top line: 1 dp inside the top outline, full along the top and // gone by the time the edge has turned 60 degrees down. // Under the hand the line brightens to #7a7c84 (+0.06) on a button, // #86888f on a small cap. // Lacquer (`gloss` 1: buttons, thumbs, knob caps, the segmented caps; // 0 keeps the matte cap): a soft sheen across the upper part of a raised // face, +0.036 at the top, its lower edge rising a little toward the // sides and gone by half the height. A held or disabled cap is matte. let uv = clamp((p - lo) / max(hi - lo, vec2(1.0, 1.0)), vec2(0.0, 0.0), vec2(1.0, 1.0)) face = face + vec3(0.036, 0.036, 0.040) * Finish.sheen(uv, 0.0, 0.5, 0.2) * up * gloss // The top line; with gloss a touch brighter (+0.03 at rest). let hl0 = mix(vec3(0.416, 0.424, 0.455), mix(vec3(0.478, 0.486, 0.518), vec3(0.525, 0.533, 0.561), small), hover) let hl1 = mix(vec3(0.447, 0.455, 0.486), mix(vec3(0.498, 0.506, 0.538), vec3(0.545, 0.553, 0.581), small), hover) let hl = mix(hl0, hl1, gloss) face = mix(face, hl, inner * smoothstep(0.5, 0.97, -n.y) * up) // The sides take at most +0.03, over the top quarter only. let side = inner * smoothstep(0.7, 1.0, abs(n.x)) * (1.0 - smoothstep(0.0, 0.25 * hh, p.y - lo.y)) face = face + vec3(0.03, 0.03, 0.03) * side * up // The bottom inner rim keeps the lower edge off the shadow. face = mix(face, rim.rgb, inner * smoothstep(0.5, 0.97, n.y) * rim.a * (1.0 - disabled)) // The outline, black at the top, blacker at the bottom. let ol = mix(vec3(0.039, 0.039, 0.047), vec3(0.016, 0.016, 0.020), t) face = mix(face, mix(ol, vec3(0.102, 0.106, 0.125), disabled), Finish.band(d, 0.0, px, px)) let a = Finish.cover(d, px) var o = Finish.over(under, vec4(face * a, a)) // Focus: a thin ring of the LED's rim colour just outside the outline, // and a 2 point breath of it. let ring = Finish.ring_out(d, 0.0, px, px) * 0.9 + Finish.glow(d - px, 0.7) * 0.3 * (1.0 - Finish.cover(d, px)) o = Finish.over(o, vec4(0.31, 0.506, 0.702, 1.0) * clamp(ring, 0.0, 1.0) * focus * (1.0 - disabled)) return o } // An LED bar or dot laid over `dst`: a white-hot core, a 1 dp saturated rim // and a short halo when lit (`halo` scales it; 0 for a light that shows a // value); a dark lens when not (a bar: one dark glass). `hs` is the half // size; the ends are round. let gl_led = fn(dst: vec4, p: vec2, c: vec2, hs: vec2, lit: float, halo: float, px: float, core: vec3, rimc: vec3) -> vec4 { let d = Finish.sd_pill(p, c, hs) let slot = Finish.cover(d, px) // Unlit: a dark lens, not a hole -- a black rim, near-black glass that // catches a little of the light on its upper half, and on a dot large // enough to show it a small catch-light up and to the left. let tl = clamp((p.y - (c.y - hs.y)) / max(2.0 * hs.y, px), 0.0, 1.0) var lens = mix(vec3(0.165, 0.176, 0.200), vec3(0.071, 0.078, 0.090), smoothstep(0.2, 0.9, tl)) let sp = p - (c + vec2(-0.3 * hs.x, -0.35 * hs.y)) lens = lens + vec3(0.16, 0.17, 0.19) * exp(-dot(sp, sp) / max(2.0 * 0.09 * hs.y * hs.y, 0.001)) * step(1.5, hs.y) // A bar has no room for a rim, a light row and a catch-light: unlit it // is one glass of three device pixels, falling #1e2127 -> #15171a with a // faint cast of the lamp's blue, its top row lifted 0.03. let bar = step(hs.y * 2.5, hs.x) let gc = c + vec2(0.0, px * 0.5) let gd = Finish.sd_pill(p, gc, vec2(hs.x + px * 0.5, px * 1.5)) let gt = clamp((p.y - (gc.y - px * 1.5)) / (px * 3.0), 0.0, 1.0) var glass = mix(vec3(0.118, 0.129, 0.153), vec3(0.082, 0.090, 0.102), gt) glass = mix(glass, vec3(0.31, 0.506, 0.702), 0.08) + vec3(0.03, 0.03, 0.03) * (1.0 - step(gc.y - px * 0.5, p.y)) var o = Finish.over(dst, vec4(0.024, 0.024, 0.031, 1.0) * Finish.ring_out(d, 0.0, px, px) * 0.55 * (1.0 - lit) * (1.0 - bar)) o = Finish.over(o, vec4(lens, 1.0) * slot * (1.0 - lit) * (1.0 - bar)) o = Finish.over(o, vec4(glass, 1.0) * Finish.cover(gd, px) * (1.0 - lit) * bar) // Lit: the halo first, under the core. Two falls: a thin one that is // gone in a device pixel or two, and a wide weak one that lies a little // light on the floor round the lamp. let x = max(d - px, 0.0) let g = (0.45 * exp(-(x * x) / (2.0 * 0.5 * 0.5)) + 0.22 * exp(-(x * x) / (2.0 * 1.4 * 1.4))) * (1.0 - slot) o = Finish.over(o, vec4(rimc, 1.0) * g * 0.8 * halo * lit) // The saturated rim, one device pixel round the core. let rim = Finish.ring_out(d, 0.0, px, px) o = Finish.over(o, vec4(rimc, 1.0) * rim * lit) // A hot top on the core. let hot = 1.0 - smoothstep(0.0, hs.y * 1.2, p.y - (c.y - hs.y)) let col = mix(core, vec3(1.0, 1.0, 1.0), hot * 0.5) o = Finish.over(o, vec4(col, 1.0) * slot * lit) return o } // The lamp under a chosen label -- the chosen segment's, the tab in use's, // the Primary's -- counted in whole device pixels. Its core is two rows // between `xs.x` and `xs.y` (snapped), from the row at `y0` (snapped): the // top row `core`, the lower one tinted toward `glow`, and one light-blue // column at either end. Round it the halo lies on the floor in two layers: // a tight one -- a pale row just above the core (brightest over its // middle), a blue row under it, two blue-grey rows fading after that, and // at the ends a pale fall gone three columns out -- over a soft wide one // that rises three rows above the core, lies faintly along its sides and // runs on down to the foot of the well. What lies above the core stays // under an eighth of its light past the pale row, so a descender keeps its // well. `halo` scales the light round the core; premultiplied over `dst`. let gl_lamp = fn(dst: vec4, p: vec2, xs: vec2, y0: float, px: float, lit: float, halo: float, core: vec3, glow: vec3) -> vec4 { // Which row from the core's top, and how many columns past its ends // (0 inside, 0.5 on the first column out). let k = floor((p.y - y0) / px) let e = max(max(xs.x - p.x, p.x - xs.y), 0.0) / px let hw = max((xs.y - xs.x) * 0.5, px) let dx = clamp(abs(p.x - (xs.x + xs.y) * 0.5) / hw, 0.0, 1.0) let pale = mix(glow, core, 0.5) let dim = mix(glow, vec3(0.56, 0.58, 0.64), 0.45) let m4 = step(-4.5, k) * step(k, -3.5) let m3 = step(-3.5, k) * step(k, -2.5) let m2 = step(-2.5, k) * step(k, -1.5) let m1 = step(-1.5, k) * step(k, -0.5) let body = step(-0.5, k) * step(k, 1.5) let p1 = step(1.5, k) * step(k, 2.5) let p2 = step(2.5, k) * step(k, 3.5) let p3 = step(3.5, k) * step(k, 4.5) let p4 = step(4.5, k) * step(k, 5.5) let p5 = step(5.5, k) * step(k, 6.5) // The soft wide layer. let wide = exp(-(e * e) / (2.0 * 3.4 * 3.4)) * (1.0 - 0.3 * dx * dx) let wa = (0.12 * m4 + 0.22 * m3 + 0.32 * m2 + 0.15 * m1 + 0.20 * body + 0.08 * (p1 + p2 + p3) + 0.25 * p4 + 0.15 * p5) * wide var o = Finish.over(dst, vec4(dim, 1.0) * clamp(wa * halo, 0.0, 1.0) * lit) // The tight layer. let top = exp(-(e * e) / (2.0 * 1.6 * 1.6)) * (1.0 - 0.3 * dx * dx) let side = exp(-(e * e) / (2.0 * 2.2 * 2.2)) let ce = max(e - 1.5, 0.0) let ends = 0.5 * exp(-(ce * ce) / (2.0 * 0.75 * 0.75)) * step(1.0, e) * (1.0 - 0.4 * step(0.5, k)) var a = 0.72 * top * m1 + ends * body a = a + (0.50 * p1 + 0.46 * p2 + 0.36 * p3 + 0.10 * p4) * side let hc = mix(mix(pale, glow, p1), dim, p2 + p3 + p4) o = Finish.over(o, vec4(hc, 1.0) * clamp(a * halo, 0.0, 1.0) * lit) // The core and its two end columns; the lower row takes a little of // the glow's own hue. let hue = glow / max(max(glow.x, glow.y), max(glow.z, 0.001)) let lower = core * mix(vec3(1.0, 1.0, 1.0), hue, 0.35) let inside = 1.0 - step(0.25, e) let endc = step(0.25, e) * (1.0 - step(1.0, e)) let cc = mix(mix(core, lower, step(0.5, k)), mix(core, glow, 0.4), endc) return Finish.over(o, vec4(cc, 1.0) * (inside + 0.95 * endc) * body * lit) } // The three-quarter arc a waiting button turns in place of its label. let gl_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: 24 points on a 13 point line, 12 // 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: 12.0 right: 12.0 top: 3.0 bottom: 3.0} mod.widgets.ButtonFlat.min_height = 24.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: 5.0 right: 5.0 top: 3.0 bottom: 3.0} mod.widgets.ButtonFlatIcon.padding = mod.turtle.Inset{left: 5.0 right: 5.0 top: 3.0 bottom: 3.0} mod.widgets.ButtonFlatterIcon.padding = mod.turtle.Inset{left: 5.0 right: 5.0 top: 3.0 bottom: 3.0} // The caps that carry a lamp lift their label one device pixel, so the lamp // can stand two rows off the bottom rim and still clear the descenders. mod.widgets.ButtonPrimary.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 2.3333 bottom: 3.6667} mod.widgets.ButtonSecondary.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 2.3333 bottom: 3.6667} mod.widgets.ButtonDanger.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 2.3333 bottom: 3.6667} // The short rungs keep their own heights under the row minimum. mod.widgets.ButtonXs.min_height = 0.0 mod.widgets.ButtonSm.min_height = 0.0 // The smallest rung's label one step down, so its cap height is a third of // its cap like every other rung's. mod.widgets.ButtonXs.draw_text.text_style.font_size = 6.5 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 = 7.0 self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m)) } // The cap and its states, shared by every button template; `kind` picks the // variant: 0 seated cap, 1 raised cap, 2 primary (lit LED), 3 secondary (dark // LED slot), 4 tonal (tinted face), 5 danger (red LED), 6 outline, 7 flatter. let gl_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 = 7.0 let px = gl_px(dpi) let p = pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let lo = vec2(gl_snap(0.0, o.x, px), gl_snap(0.0, o.y, px)) let hi = vec2(gl_snap(size.x, o.x, px), gl_snap(size.y, o.y, px)) let c = (lo + hi) * 0.5 let h = (hi - lo) * 0.5 let r = 2.0 var res = vec4(0.0, 0.0, 0.0, 0.0) if kind > 5.5 { // Outline: no face, the black ring cut into the ground with its lip; // under the hand a faint cap shows, held it sinks. let d = Material.sd_box(p, c, h, r) if kind > 6.5 { // Flatter: nothing at rest. if hover + down + focus > 0.001 { res = gl_cap(p, lo, hi, r, px, hover, down, disabled, 0.0, 0.0, vec3(0.0, 0.0, 0.0), vec4(0.196, 0.204, 0.227, 1.0), 1.0) * clamp(max(hover, down), 0.0, 1.0) } } else { var ring = Finish.band(d, 0.0, px, px) if dash > 0.0 { ring = ring * step(0.5, fract((p.x + p.y) / (dash * 2.0))) } let lip = Finish.ring_out(d, 0.0, px, px) * step(c.y + h.y * 0.5, p.y) let inner = Finish.band(d, px, px * 2.0, px) * step(p.y, c.y - h.y * 0.5) res = vec4(0.28, 0.30, 0.34, 1.0) * lip * 0.9 res = Finish.over(res, vec4(0.0, 0.0, 0.0, 1.0) * inner * 0.25) res = Finish.over(res, vec4(0.039, 0.039, 0.047, 1.0) * ring) if hover + down > 0.001 { res = Finish.over(res, gl_cap(p, lo, hi, r, px, hover, down, disabled, 0.0, 0.0, vec3(0.0, 0.0, 0.0), vec4(0.196, 0.204, 0.227, 1.0), 1.0) * clamp(max(hover, down), 0.0, 1.0) * 0.85) } } let fr = Finish.ring_out(d, 0.0, px, px) * 0.9 + Finish.glow(d - px, 0.7) * 0.3 * (1.0 - Finish.cover(d, px)) res = Finish.over(res, vec4(0.31, 0.506, 0.702, 1.0) * clamp(fr, 0.0, 1.0) * focus * (1.0 - disabled)) } else { var tint = vec3(0.0, 0.0, 0.0) var rim = vec4(0.196, 0.204, 0.227, 1.0) let lit = 1.0 - disabled if kind > 3.5 && kind < 4.5 { // The tonal cap: the face a breath toward the LED's blue, no // more than 4 steps of blue over the family's face. tint = vec3(-0.004, 0.0, 0.012) } if kind > 1.5 && kind < 2.5 { // Primary: the LED's spill lies along the bottom inner rim, and // a tight breath of it under the cap. rim = vec4(0.31, 0.506, 0.702, 0.45 * lit) let dg = Material.sd_box(p, c + vec2(0.0, 0.05 * (hi.y - lo.y)), h - vec2(2.0, 0.0), r) let g = exp(-(max(dg, 0.0) * max(dg, 0.0)) / (2.0 * 1.2 * 1.2)) * (1.0 - Finish.cover(dg, px)) res = vec4(0.31, 0.506, 0.702, 1.0) * g * 0.18 * lit * (1.0 - down * 0.5) } if kind > 4.5 { rim = vec4(0.56, 0.22, 0.18, 0.35 * lit) } var lift = 1.0 if kind < 0.5 { lift = 0.0 } res = Finish.over(res, gl_cap(p, lo, hi, r, px, hover, down, disabled, focus, lift, tint, rim, 1.0)) if kind > 1.5 && kind < 3.5 || kind > 4.5 { // The lamp: the chosen segment's lamp (`gl_lamp`), its core two // device pixels whose middle stands 4 points above the bottom // edge, 16 device pixels wide with a light end column either // side; the label (raised one device pixel on these caps) // clears its pale top row by two rows of face. Held, its halo // halves. Unlit (Secondary, or disabled) it is the dark glass. let lc = vec2(c.x, gl_snap(hi.y - 4.0, o.y, px)) let hs = vec2(min(5.5, h.x * 0.4), px) let lw = min(5.3333, h.x * 0.4) let xs = vec2(gl_snap(c.x - lw, o.x, px), gl_snap(c.x + lw, o.x, px)) var on = lit var core = vec3(0.910, 0.918, 0.922) var glow = vec3(0.405, 0.593, 0.752) if kind > 2.5 && kind < 3.5 { on = 0.0 } if kind > 4.5 { // Danger: the same lamp, red. core = vec3(0.97, 0.80, 0.76) glow = vec3(0.86, 0.26, 0.2) } let dark = gl_led(res, p, lc, hs, 0.0, 0.0, px, core, glow) let lamp = gl_lamp(res, p, xs, lc.y - px, px, 1.0, 1.0 - down * 0.5, core, glow) res = mix(dark, lamp, on) } } if loading > 0.001 { // The arc ends two device pixels or more above where a lamp's rim // stands: a smaller ring, lifted one device pixel. let rad = max(min(h.x, h.y) - 7.25, 2.0) res = gl_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(gl_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), 7.0) } mod.widgets.Button.draw_bg.pixel = fn() { return self.host_over(gl_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), 7.0) } mod.widgets.ButtonPrimary.draw_bg.pixel = fn() { return self.host_over(gl_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), 7.0) } mod.widgets.ButtonSecondary.draw_bg.pixel = fn() { return self.host_over(gl_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), 7.0) } mod.widgets.ButtonTertiary.draw_bg.pixel = fn() { return self.host_over(gl_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), 7.0) } mod.widgets.ButtonDanger.draw_bg.pixel = fn() { return self.host_over(gl_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), 7.0) } mod.widgets.ButtonOutline.draw_bg.pixel = fn() { return self.host_over(gl_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), 7.0) } mod.widgets.ButtonFlatter.draw_bg.pixel = fn() { return self.host_over(gl_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), 7.0) } // Label ink per state: caps #d4d7de, the primary's lit legend near white, // every variant dims to #5c5e66 when disabled. mod.widgets.ButtonPrimary.draw_text.color = #f4f6f8 mod.widgets.ButtonPrimary.draw_text.color_hover = #f8f9fa mod.widgets.ButtonPrimary.draw_text.color_down = #f4f6f8 mod.widgets.ButtonPrimary.draw_text.color_focus = #f4f6f8 mod.widgets.ButtonPrimary.draw_text.color_disabled = #5c5e66 mod.widgets.ButtonPrimary.draw_icon.color = #f4f6f8 mod.widgets.ButtonSecondary.draw_text.color = #d4d7de mod.widgets.ButtonSecondary.draw_text.color_hover = #dcdee3 mod.widgets.ButtonSecondary.draw_text.color_down = #c4c7ce mod.widgets.ButtonSecondary.draw_text.color_focus = #d4d7de mod.widgets.ButtonSecondary.draw_icon.color = #d4d7de mod.widgets.ButtonTertiary.draw_text.color = #d4d7de mod.widgets.ButtonTertiary.draw_text.color_hover = #dcdee3 mod.widgets.ButtonTertiary.draw_text.color_down = #c4c7ce mod.widgets.ButtonTertiary.draw_text.color_focus = #d4d7de mod.widgets.ButtonTertiary.draw_icon.color = #d4d7de mod.widgets.ButtonDanger.draw_text.color = #f4f6f8 mod.widgets.ButtonDanger.draw_text.color_hover = #f8f9fa mod.widgets.ButtonDanger.draw_text.color_down = #f4f6f8 mod.widgets.ButtonDanger.draw_text.color_focus = #f4f6f8 mod.widgets.ButtonDanger.draw_icon.color = #f4f6f8 mod.widgets.ButtonOutline.draw_text.color = #d4d7de mod.widgets.ButtonOutline.draw_text.color_hover = #dcdee3 mod.widgets.ButtonOutline.draw_text.color_down = #c4c7ce mod.widgets.ButtonOutline.draw_text.color_focus = #d4d7de mod.widgets.ButtonOutline.draw_text.color_disabled = #5c5e66 mod.widgets.ButtonOutline.draw_icon.color = #d4d7de // The trailing icon takes the cap's ink too, never the drawing's own black. mod.widgets.ButtonFlat.draw_icon_end.color = #d4d7de // ---------------------------------------------------------------- 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 gl_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) var res = vec4(0.28, 0.30, 0.34, 1.0) * Finish.ring_out(d, 0.0, px, px) * step(c.y, p.y) * lip * (1.0 - disabled * 0.6) // The lower half a shade lighter, the first device pixels under the top // outline a shade darker. var col = fill + vec3(0.008, 0.008, 0.009) * step(0.5, t) let top = p.y - lo.y // Whole device-pixel rows (the edges are snapped): the row under the // outline #1a1b20, the next half way to it. col = mix(col, vec3(0.102, 0.106, 0.125), 1.0 - step(px * 2.0, top)) col = mix(col, vec3(0.102, 0.106, 0.125), 0.5 * step(px * 2.0, top) * (1.0 - step(px * 3.0, top))) col = mix(col, mix(vec3(0.039, 0.039, 0.047), vec3(0.024, 0.043, 0.067), t), Finish.band(d, 0.0, px, px)) let a = Finish.cover(d, px) res = Finish.over(res, vec4(col * a, a)) let ring = Finish.ring_out(d, 0.0, px, px) * 0.9 + Finish.glow(d - px, 0.7) * 0.3 * (1.0 - a) res = Finish.over(res, vec4(0.31, 0.506, 0.702, 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(14.0) mod.widgets.CheckBoxFlat.draw_bg.border_radius = uniform(1.0) mod.widgets.CheckBoxFlat.label_walk.margin.left = 14.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 gl_lit_mark = fn(dst: vec4, dk: float, w: float, px: float, on: float, disabled: float, error: float) -> vec4 { let edge = mix(mix(vec3(0.227, 0.655, 0.965), 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.31, 0.506, 0.702), 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 14 point raised cap. Checked, it sinks into a blue well // (the chosen segment's) and its mark is lit. mod.widgets.CheckBoxFlat.draw_bg.pixel = fn() { let m = 5.0 let px = gl_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(gl_snap(x0, o.x, px), gl_snap(y0, o.y, px)) let hi = vec2(gl_snap(x0 + sz, o.x, px), gl_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) let held = max(max(self.down, self.pressed), on) let tint = vec3(-0.006, 0.030, 0.062) * on * (1.0 - self.disabled) var res = gl_cap(p, lo, hi, r, px, self.hover, held, self.disabled, self.focus, 1.0, tint, vec4(0.196, 0.204, 0.227, 1.0), 0.0) // The error: the outline turns the alt red. let d = Material.sd_box(p, c, (hi - lo) * 0.5, r) 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.4 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 dm = min(gl_seg(p, vec2(cm.x - 3.6, cm.y + 0.1), vec2(cm.x - 1.1, cm.y + 2.6)), gl_seg(p, vec2(cm.x - 1.1, cm.y + 2.6), vec2(cm.x + 3.7, cm.y - 2.8))) let cy = gl_snap((lo.y + hi.y) * 0.5, o.y, px) + px * 0.5 let dd = gl_seg(p, vec2(c.x - 3.5, cy), vec2(c.x + 3.5, cy)) let dk = mix(dm - 0.7, dd - px * 1.5, self.mixed) return gl_lit_mark(res, dk, mix(0.7, px * 1.5, self.mixed), px, on, self.disabled, self.error) } // The toggle: a sunk 34 x 18 track with a raised thumb half its length; the // thumb carries an LED bar, lit when on, a dark slot when off. mod.widgets.ToggleFlat.draw_bg.size = uniform(18.0) mod.widgets.ToggleFlat.draw_bg.pill_aspect = uniform(1.89) mod.widgets.ToggleFlat.label_walk.margin.left = 34.0 mod.widgets.ToggleFlat.draw_bg.pixel = fn() { let m = 5.0 let px = gl_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(gl_snap(x0, o.x, px), gl_snap(y0, o.y, px)) let hi = vec2(gl_snap(x0 + tw, o.x, px), gl_snap(y0 + th, o.y, px)) // On, the track's floor takes the chosen segment's blue cast. let on = self.active * (1.0 - self.disabled) var res = gl_well(p, lo, hi, 2.5, px, mix(vec3(0.141, 0.149, 0.176), vec3(0.137, 0.165, 0.204), on), self.focus, self.disabled, 1.0) // The thumb travels half the track. let kt = mix(self.active, self.drag_pos, self.drag) let kw = gl_snap(x0 + tw * 0.5, o.x, px) - lo.x let kx = gl_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 = gl_cap(p, klo, khi, 2.5, px, self.hover, max(self.pressed, self.down), self.disabled, 0.0, 0.5,vec3(0.0, 0.0, 0.0), vec4(0.196, 0.204, 0.227, 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, gl_snap((lo.y + hi.y) * 0.5, o.y, px)) res = gl_led(res, p, lc, vec2(4.0, px), on, 0.8, px, vec3(0.94, 0.984, 1.0), vec3(0.31, 0.506, 0.702)) return res } // The radio: the check box's cap made round. Chosen, it sinks into the blue // well and an LED dot is lit in its middle; not chosen, the cap stays up and // 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(14.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 = 14.0 mod.widgets.RadioButtonFlat.draw_bg.pixel = fn() { let m = 5.0 let px = gl_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(gl_snap(0.0, o.x, px), gl_snap(y0, o.y, px)) let hi = vec2(gl_snap(sz, o.x, px), gl_snap(y0 + sz, o.y, px)) let c = (lo + hi) * 0.5 let on = self.active * (1.0 - self.disabled) let tint = vec3(-0.006, 0.030, 0.062) * on var res = gl_cap(p, lo, hi, sz * 0.5, px, self.hover, max(self.down, on), self.disabled, self.focus, 1.0, tint, vec4(0.196, 0.204, 0.227, 1.0), 0.0) // The dot: 4.5 points, one device pixel lower in the well when lit. let cd = c + vec2(0.0, px * 0.5 * on) res = gl_led(res, p, cd, vec2(2.25 - px, 2.25 - px), on, 0.8, px, vec3(0.88, 0.96, 1.0), vec3(0.31, 0.506, 0.702)) return res } // The radio tab: a segment of a joined strip. At rest a raised cap; chosen, // it sinks, flat and tinted toward the light, its legend lit and the // chosen segment's lamp under it. mod.widgets.RadioButtonTabFlat.draw_bg.vertex = fn() { let m = 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.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 = 24.0 mod.widgets.RadioButtonTabFlat.draw_bg.pixel = fn() { let m = 7.0 let px = gl_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(gl_snap(0.0, o.x, px), gl_snap(0.0, o.y, px)) let hi = vec2(gl_snap(self.rect_size.x, o.x, px), gl_snap(self.rect_size.y, o.y, px)) let sel = self.active let tint = vec3(-0.006, 0.030, 0.062) * sel let res = gl_cap(p, lo, hi, 2.0, px, self.hover, max(self.down, sel), self.disabled, self.focus, 1.0, tint, vec4(0.196, 0.204, 0.227, 1.0), 0.0) // The lamp, centred under the legend (which stands centred, 1.7 points // above the cap's middle): its core 6.5 points under the middle, so a // descender keeps two rows of well above its pale top row. let c = (lo + hi) * 0.5 let xs = vec2(gl_snap(c.x - 5.3333, o.x, px), gl_snap(c.x + 5.3333, o.x, px)) return gl_lamp(res, p, xs, gl_snap(c.y + 6.5, o.y, px), px, sel * (1.0 - self.disabled), 1.0, vec3(0.910, 0.918, 0.922), vec3(0.405, 0.593, 0.752)) } mod.widgets.RadioButtonTabFlat.draw_text.color = #c4c7ce mod.widgets.RadioButtonTabFlat.draw_text.color_hover = #dcdee3 mod.widgets.RadioButtonTabFlat.draw_text.color_active = #fafafa mod.widgets.RadioButtonTabFlat.draw_text.color_disabled = #5c5e66 // ---------------------------------------------------------------- sliders // A lit bar laid in a groove, like the lamp and like the chosen segment's // legend: counted in whole device-pixel rows from `lo.y` (snapped), one // pale core row and one row of the body under it in the middle of the // floor, and on the rows round them a soft halo on the floor -- 15 % of the // core's light on the next row, 7 % on the one after. Past the bar's end // the halo runs on along the floor, falling by half every device pixel, // between `gx.x` and `gx.y` (the floor's ends). `body` is the role colour; the core // is it taken most of the way to white. Premultiplied over `dst`. let gl_lit_bar = fn(dst: vec4, p: vec2, lo: vec2, hi: vec2, gx: vec2, px: float, body: vec3, disabled: float) -> vec4 { let n = max(floor((hi.y - lo.y) / px + 0.5), 1.0) let k = floor((p.y - lo.y) / px) let rows = step(0.0, k) * step(k, n - 1.0) let kc = max(floor(n * 0.5) - 1.0, 0.0) let kb = min(kc + 1.0, n - 1.0) // How far this row stands from the lit pair, in rows. let dr = max(max(kc - k, k - kb), 0.0) let core = mix(body, vec3(1.0, 1.0, 1.0), 0.8) let deep = mix(body, vec3(0.13, 0.14, 0.17), 0.3) var col = mix(core, deep, step(kb - 0.5, k) * step(kc + 0.5, kb)) col = mix(col, mix(body, vec3(0.3, 0.33, 0.38), 0.7), disabled) let lit = step(dr, 0.5) let halo_row = mix(0.0, 0.34, step(0.5, dr) * step(dr, 1.5)) + mix(0.0, 0.16, step(1.5, dr) * step(dr, 2.5)) let inx = step(lo.x, p.x) * step(p.x, hi.x) let past = max(max(p.x - hi.x, lo.x - p.x), 0.0) / px let fall = exp(-max(past - 0.5, 0.0) * 0.76) * (1.0 - inx) * step(gx.x, p.x) * step(p.x, gx.y) let along = mix(0.34, halo_row, step(0.5, dr)) let g = (halo_row * inx + along * fall) * rows * (1.0 - disabled) var res = Finish.over(dst, vec4(deep, 1.0) * g) return Finish.over(res, vec4(col, 1.0) * lit * inx * rows) } let gl_fill = fn(dst: vec4, p: vec2, lo: vec2, hi: vec2, gx: vec2, px: float, disabled: float) -> vec4 { return gl_lit_bar(dst, p, lo, hi, gx, px, vec3(0.231, 0.682, 1.0), disabled) } // A round handle cap: the button's cap made round, with a dimple in its // middle, or a small LED dot there while it is held or focused. let gl_handle = fn(dst: vec4, p: vec2, c: vec2, rad: float, px: float, hover: float, held: float, disabled: float) -> vec4 { let lo = c - vec2(rad, rad) let hi = c + vec2(rad, rad) var res = Finish.over(dst, gl_cap(p, lo, hi, rad, px, hover, 0.0, disabled, 0.0, 1.0, vec3(0.0, 0.0, 0.0), vec4(0.196, 0.204, 0.227, 1.0), 1.0)) let d = Finish.sd_circle(p, c, 1.5) res = Finish.over(res, vec4(0.30, 0.31, 0.35, 1.0) * Finish.ring_out(d, 0.0, px, px) * step(c.y + 0.5, p.y) * (1.0 - held)) res = Finish.over(res, vec4(0.102, 0.106, 0.125, 1.0) * Finish.cover(d, px) * (1.0 - held)) res = gl_led(res, p, c, vec2(1.5 - px, 1.5 - px), held * (1.0 - disabled), 0.6, px, vec3(0.94, 0.984, 1.0), vec3(0.31, 0.506, 0.702)) return res } // The bar slider: a 4 point well under its label row, the lit fill, and a // 14 point round cap riding it. mod.widgets.SliderFlat.clip_x = false mod.widgets.SliderFlat.clip_y = false mod.widgets.SliderFlat.draw_bg.vertex = fn() { let m = 6.0 self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m)) } mod.widgets.SliderFlat.draw_bg.pixel = fn() { let m = 6.0 let px = gl_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 = 7.0 let cy = gl_snap(20.0 + (self.rect_size.y - 20.0) * 0.5, o.y, px) let lo = vec2(gl_snap(0.0, o.x, px), cy - 2.0) let hi = vec2(gl_snap(self.rect_size.x, o.x, px), cy + 2.0) var res = gl_well(p, lo, hi, 2.0, px, vec3(0.141, 0.149, 0.176), 0.0, self.disabled, 1.0) let span = self.rect_size.x - rad * 2.0 let hx = rad + self.slide_pos * span let ox = rad + self.origin_pos * span let f0 = mix(lo.x + px, min(ox, hx), self.arc_origin) let f1 = mix(hx, max(ox, hx), self.arc_origin) res = gl_fill(res, p, vec2(f0, lo.y + px), vec2(f1, hi.y - px), vec2(lo.x + px, hi.x - px), px, self.disabled) let held = max(self.drag, self.focus) return gl_handle(res, p, vec2(hx, cy), rad, px, self.hover, held, self.disabled) } // A slider's value is a text input, and the field row's 24 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 24 points it hung over the ring's bezel, so here it is // its line and two points either side, and clears the ring. mod.widgets.RotaryFlat.text_input.min_height = 0.0 mod.widgets.RotaryFlat.text_input.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 2.0 bottom: 2.0} mod.widgets.Rotary.text_input.min_height = 0.0 mod.widgets.Rotary.text_input.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 2.0 bottom: 2.0} mod.widgets.RotaryGradientY.text_input.min_height = 0.0 mod.widgets.RotaryGradientY.text_input.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 2.0 bottom: 2.0} // The minimal slider: only the slot and the lit value; a slim cap shows // under the hand. mod.widgets.SliderMinimal.draw_bg.pixel = fn() { let px = gl_px(self.draw_pass.dpi_factor) let o = self.rect_pos let p = self.pos * self.rect_size let cy = gl_snap(self.offset_y + (self.rect_size.y - self.offset_y) * 0.5, o.y, px) // Four device pixels: the outline, a core row, a body row, the outline. let lo = vec2(gl_snap(0.0, o.x, px), cy - px * 2.0) let hi = vec2(gl_snap(self.rect_size.x, o.x, px), cy + px * 2.0) var res = gl_well(p, lo, hi, 1.0, px, vec3(0.141, 0.149, 0.176), 0.0, self.disabled, 1.0) let lo2 = min(self.origin_pos, self.slide_pos) let hi2 = max(self.origin_pos, self.slide_pos) let f0 = mix(0.0, lo2, self.arc_origin) * self.rect_size.x let f1 = mix(self.slide_pos, hi2, self.arc_origin) * self.rect_size.x res = gl_fill(res, p, vec2(max(f0, lo.x + px), lo.y + px), vec2(min(f1, hi.x - px), hi.y - px), vec2(lo.x + px, hi.x - px), px, self.disabled) let hx = self.slide_pos * self.rect_size.x let show = max(self.hover, self.drag) let cap = gl_cap(p, vec2(hx - 3.0, cy - 5.0), vec2(hx + 3.0, cy + 5.0), 1.5, px, 0.0, 0.0, self.disabled, 0.0, 1.0, vec3(0.0, 0.0, 0.0), vec4(0.196, 0.204, 0.227, 1.0), 1.0) return Finish.over(res, cap * show) } // The round slider: a label column, then the bar slider's 4 point slot with // the lit fill and the round cap. The slot ends short of the right edge, so // the value the widget writes there stands on the ground, not on the track. mod.widgets.SliderRoundFlat.clip_x = false mod.widgets.SliderRoundFlat.clip_y = false mod.widgets.SliderRoundFlat.draw_bg.vertex = fn() { let m = 6.0 self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m)) } mod.widgets.SliderRoundFlat.draw_bg.pixel = fn() { let m = 6.0 let px = gl_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 = 7.0 let cy = gl_snap(self.rect_size.y * 0.5, o.y, px) let x0 = gl_snap(self.label_size, o.x, px) let lo = vec2(x0, cy - 2.0) let hi = vec2(gl_snap(self.rect_size.x - 37.0, o.x, px), cy + 2.0) var res = gl_well(p, lo, hi, 2.0, px, vec3(0.141, 0.149, 0.176), 0.0, self.disabled, 1.0) let span = hi.x - lo.x - rad * 2.0 let hx = lo.x + rad + self.slide_pos * span res = gl_fill(res, p, vec2(lo.x + px, lo.y + px), vec2(hx, hi.y - px), vec2(lo.x + px, hi.x - px), px, self.disabled) let held = max(self.drag, self.focus) return gl_handle(res, p, vec2(hx, cy), rad, px, self.hover, held, self.disabled) } // The fader: the slider bench's cove cap with a white line ("Cove cap, white // line", a wedge), drawn by the cap engine (mod.sdf.Cap) in this sheet's // charcoal. A narrow level ridge over the middle sixth of the cap carries a // thin line painted across it in the LED's blue; concave coves sweep from the // ridge down to a thin brim at either end; across, the top runs level to the // sides and drops straight over a sharp edge. It is metal under a plain sky: // it shows the sky and the ground reflected, tinted by the charcoal, bright // where a cove faces the light and dark where it turns away, and the painted // line catches the highlight almost white. It rides a 5 point slot lit in the // LED's blue from the stop (or from the origin) to the value, the light's // halo round the lit stretch beside the cap, and it throws a firm shadow down // and a little to the right. The shape and the material are his bench preset // number for number: no perspective, no ribs, no cut, no scale, no well. The // inks are this sheet's where it has them. mod.widgets.SliderFader.cap_size = 26.0 mod.widgets.SliderFader.track_inset = 8.0 mod.widgets.SliderFader.draw_bg.track_size = uniform(5.0) mod.widgets.SliderFader.draw_bg.cap_cross = uniform(0.44) mod.widgets.SliderFader.clip_x = false mod.widgets.SliderFader.clip_y = false // The material, live in the tweaker: the key light in screen space (x right, // y down, z toward the viewer) and its intensity; a strong, broad highlight // and a high gloss sheen; the rim toward grazing; a light occlusion held // close to the foot (the higher the reach, the closer it hugs the ground); // the plain sky's reflection and how close the eye sits over the cap (0 far // above); full metal, so the reflection and the highlight take the body's // charcoal; turned grain and self-light, both off; the exposure in stops and // the highlight roll-off; the cast shadow's strength and its softest edge // (held to the cap's height), the contact shadow's strength and the slot's // inner shadow. mod.widgets.SliderFader.draw_bg.cap_light = uniform(vec3(-0.22, -0.72, 0.66)) mod.widgets.SliderFader.draw_bg.cap_light_intensity = uniform(0.95) mod.widgets.SliderFader.draw_bg.cap_specular = uniform(1.0) mod.widgets.SliderFader.draw_bg.cap_roughness = uniform(0.78) mod.widgets.SliderFader.draw_bg.cap_gloss = uniform(0.82) mod.widgets.SliderFader.draw_bg.cap_rim = uniform(0.9) mod.widgets.SliderFader.draw_bg.cap_ao = uniform(0.22) mod.widgets.SliderFader.draw_bg.cap_ao_reach = uniform(2.1) mod.widgets.SliderFader.draw_bg.cap_reflection = uniform(0.68) mod.widgets.SliderFader.draw_bg.cap_reflection_perspective = uniform(0.35) 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.7) mod.widgets.SliderFader.draw_bg.cap_shadow_blur = uniform(6.0) mod.widgets.SliderFader.draw_bg.cap_contact = uniform(0.5) mod.widgets.SliderFader.draw_bg.cap_inner = uniform(0.7) // The inks. The cap is this sheet's charcoal cap face (its disabled face // when disabled) on the window's ground. The painted line is the LED's blue, // the sheet's value colour, and so is the lit value, which takes the // widget's own `val_color`; the slot takes the widget's own `color`, the // darkest of the sheet's well inks. The light is his warm white and the // shade his grey: the dark theme under this sheet sets them to pure white // and black, which would shade the metal harder than he drew it. Focus // rings the cap in the LED's rim blue. // The body is his charcoal (#1c2527 in his Splash), a step darker than the sheet's outset, so the metal reads near-black with the teal of the bench. mod.widgets.SliderFader.draw_bg.cap_body = uniform(#1c2527) mod.widgets.SliderFader.draw_bg.cap_body_disabled = uniform(theme.color_outset_disabled) mod.widgets.SliderFader.draw_bg.cap_ground = uniform(theme.color_bg_app) mod.widgets.SliderFader.draw_bg.cap_light_ink = uniform(#f4f2ee) mod.widgets.SliderFader.draw_bg.cap_shadow_ink = uniform(#46474c) mod.widgets.SliderFader.draw_bg.cap_mark_ink = uniform(theme.color_val) mod.widgets.SliderFader.draw_bg.cap_focus_ink = uniform(theme.color_focus) // The cove cap's solid at `q`, a point in the cap's own frame (x along the // track, y across it), `hs` the cap's half size and `r` its corner: the // height 0..1 in .x, its gradient per point in .yz, and in .w the // footprint's distance, negative inside. The curves are the bench's, handles // resolved. Along, read from the centre to one end and mirrored: level over // the middle sixth, then a cove falling through 0.86, 0.46, 0.19 and 0.07 to // a thin brim of 0.05 just short of the end, and nothing at it. Across: // level to 0.94 of the half width, then straight down. The edge reaches the // full height within 0.06 of the edge width: a sharp edge. His plan curve is // level at 1, which is no plan. Shape: corner 0.32 and edge width 0.1 of the // smaller half size, blend 0.01, 15 points tall. No ribs and no cut; the // mark is paint (1) across the cap (0) at its middle, 0.5 points wide over // 0.66 of its width, which leaves the solid as it is. let gl_cove_solid = fn(q: vec2, hs: vec2, r: float) -> vec4 { let fp = Cap.sd_cap(q, hs, r, vec2(1.0, 0.0)) let along = Cap.mirror(Cap.curve(abs(q.x) / hs.x, 8.0, vec2(0.0, 1.0), vec2(0.0533, 0.0), vec2(0.0, 0.0), vec2(0.16, 1.0), vec2(0.0, 0.0), vec2(-0.01, 0.0386), vec2(0.19, 0.86), vec2(0.01, -0.0386), vec2(-0.0367, 0.0847), vec2(0.3, 0.46), vec2(0.0367, -0.0847), vec2(-0.06, 0.0557), vec2(0.48, 0.19), vec2(0.06, -0.0557), vec2(-0.0733, 0.0223), vec2(0.72, 0.07), vec2(0.0733, -0.0223), vec2(-0.02, 0.0), vec2(0.94, 0.05), vec2(0.02, 0.0), vec2(0.0, 0.0), vec2(1.0, 0.0)), q.x) let across = Cap.curve(abs(q.y) / hs.y, 3.0, vec2(0.0, 1.0), vec2(0.1567, 0.0), vec2(0.0, 0.0), vec2(0.94, 1.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), 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 edge = Cap.curve(Cap.edge_x(fp.x, hs, 0.1), 3.0, vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(0.06, 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), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 1.0)) let f = Cap.field(q, hs, fp, along, across, edge, vec2(1.0, 0.0), vec4(0.32, 0.1, 0.01, 15.0), vec4(0.0, 0.0, 0.0, 0.0), vec4(1.0, 0.5, 0.35, 0.0), vec4(0.0, 0.5, 0.66, 0.0), vec4(0.0, 0.0, 0.0, 0.0)) return vec4(f.x, f.y, f.z, fp.x) } // How far the fader's quad reaches past its rect, in points: the throw of // the cap's cast shadow from its 15 point height along the key light (about // 12 points from this light), the blur at the shadow's tip (the engine holds // it between 0.35 and 1 times the height), two points for the contact // shadow's tail, and the glow's reach when the cap emits. The cap itself // never leaves the rect: it is no wider than the fader and stops at the // track's ends. let gl_fader_room = fn(lgt: vec3, blur: float, glo: float) -> float { let ln = normalize(lgt) return 15.0 * 0.7 * length(ln.xy) / max(ln.z, 0.25) + clamp(blur, 5.25, 15.0) + 24.0 * step(0.001, glo) + 2.0 } // The fader laid over the ground without painting it: the share of the // ground it darkens away and the light it puts back, premultiplied, exact // for any colour. Cap.on_ground takes the ground away by the darkest // channel's share but puts back only what each channel has above the // ground, so a colour below the ground in one channel and above it in // another comes out short in the channels above it: the slot's dark floor // and the cap's shadow under the LED's halo lost up to 50 levels of blue. let gl_fader_on_ground = fn(col: vec3, gnd: vec3) -> vec4 { let dk = max(gnd - col, vec3(0.0, 0.0, 0.0)) / max(gnd, vec3(0.01, 0.01, 0.01)) let k = clamp(max(dk.x, max(dk.y, dk.z)), 0.0, 1.0) return vec4(max(col - gnd * (1.0 - k), vec3(0.0, 0.0, 0.0)), k) } mod.widgets.SliderFader.draw_bg.vertex = fn() { let m = gl_fader_room(self.cap_light, self.cap_shadow_blur, self.cap_glow) self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m)) } mod.widgets.SliderFader.draw_bg.pixel = fn() { let m = gl_fader_room(self.cap_light, self.cap_shadow_blur, self.cap_glow) let px = gl_px(self.draw_pass.dpi_factor) let sz = self.rect_size let o = self.rect_pos 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 inset = self.inset_px let travel = max(along - inset * 2.0 - self.cap_px, 1.0) let foot = inset + self.cap_px * 0.5 // Track space: `ab.x` along the track from the value's zero (up the // screen when the fader stands), `ab.y` across it. let ab = vec2(mix(p.x, sz.y - p.y, v), mix(p.y, p.x, v)) let dis = self.disabled let awake = 1.0 - dis let ground = self.cap_ground.rgb let light_ink = self.cap_light_ink.rgb let shadow_ink = self.cap_shadow_ink.rgb // The key light on screen for the cap's shading, and turned into track // space for everything laid out along the track. let l = normalize(self.cap_light) let lt = Cap.light(l, v) // The cap's half size: cap_size along the track, cap_cross of the // fader's width across, held to 4 points and to the width as the widget // holds it. let hs = vec2(self.cap_px * 0.5, min(max(across * self.cap_cross, 4.0), across) * 0.5) // The slot's middle on the device pixel grid, so its two walls // antialias alike, and the cap's lower end on it, so the cap keeps one // shape as it moves; 26 points are an odd 39 pixels at 1.5, so the thin // line at its middle then falls on one pixel column instead of two dim // ones. let mid = gl_snap(across * 0.5, mix(o.y, o.x, v), px) let end_free = foot + self.slide_pos * travel - hs.x let se = Finish.snap(mix(o.x + end_free, o.y + sz.y - end_free, v), px) let at = mix(se - o.x, o.y + sz.y - se, v) + hs.x // The ground: the slot, its ends rounded by half its half width, held // track_inset off the fader's ends, and the value on its floor. A value // that starts at the stop starts at the slot's end, so no unlit length // shows below the cap's first position. Lit, the value is the LED: the // engine draws a lit fill at 1.2 times the ink it is handed, which takes // the LED's blue past full and the roll-off spreads the excess into the // other channels, so it is handed the LED divided by that and its core // lands on the sheet's value colour, less what the roll-off takes off // its blue; the same ink lights the halo and the cap's end // wall, as the engine pairs them. Disabled, the LED is out: a dark bar // under the slot's shade and no halo. let tw = min(self.track_size, across) let tlen = max(along - inset * 2.0, 1.0) let tbox = vec4(inset + tlen * 0.5, mid, tlen * 0.5, tw * 0.5) var lo = foot + self.fill_lo * travel var hi = foot + self.fill_hi * travel if self.fill_lo < 0.0005 { lo = inset } if self.fill_hi > 0.9995 { hi = along - inset } let lit = step(0.5, awake) let led = mix(self.val_color.rgb / 1.2, self.val_color_disabled.rgb, dis) let track_col = Cap.slot(ground, ab, tbox, tw * 0.25, 1.0 + lit, lo, hi, at, hs.x, lt, px, 0.0, 0.0, self.color.rgb, led, light_ink, shadow_ink, self.cap_inner, self.cap_ao) // The halo's faint tail fades out over the margin by the distance from // the rect, so the quad's edge never shows on the ground as a box. let past = length(max(abs(p - sz * 0.5) - sz * 0.5, vec2(0.0, 0.0))) var col = mix(ground, track_col, 1.0 - smoothstep(0.0, m, past)) // The cap, worked out only where it, its shadow or its glow can reach. // Held, it sinks half a point, so its shadow shortens. let q = ab - vec2(at, mid) let r = 0.32 * min(hs.x, hs.y) let height = 15.0 let press = self.drag * awake let rise = height - 0.5 * press // Disabled, the cap takes this sheet's disabled face. let body = mix(self.cap_body.rgb, self.cap_body_disabled.rgb, dis) var capc = ground var cov = 0.0 var shd = 0.0 var halo = 0.0 var ring = 0.0 let far = abs(q) - hs if max(far.x, far.y) < m { let s = gl_cove_solid(q, hs, r) let n = Cap.normal(s.yz, height, v) let eye = Cap.view(q, hs, self.cap_reflection_perspective, v) // The line is painted where the cap's own point lies, as the bench // paints it. Held, it lights in the LED's blue, this sheet's sign of // a held cap: the engine lights a mark at 1.15 times its ink, so the // ink is divided by that as the press comes on and the lit line // shows the LED's blue. A disabled cap is matte, its line the dark // LED. let paint = Cap.mark_line(q, hs, vec4(1.0, 0.5, 0.35, 0.0), vec4(0.0, 0.5, 0.66, 0.0), px) let mark_ink = mix(self.cap_mark_ink.rgb / mix(1.0, 1.15, press), self.val_color_disabled.rgb, dis) capc = Cap.shade(n, l, eye, s.x, body, paint, paint * press, 1.0, self.cap_specular * awake, self.cap_roughness, self.cap_gloss * awake, self.cap_rim * awake, self.cap_ao, self.cap_ao_reach, self.cap_reflection, self.cap_metal, self.cap_light_intensity, mark_ink, light_ink, shadow_ink, ground) if self.cap_spun > 0.001 { let grain = Cap.spun(q, hs, lt.xy) * self.cap_spun * (1.0 - paint) capc = Cap.in_shadow(Cap.in_light(capc, clamp(grain, 0.0, 1.0) * 0.55, light_ink), clamp(-grain, 0.0, 1.0) * 0.42, shadow_ink) } if self.cap_glow > 0.001 { capc = Cap.emit(capc, s.x, body, light_ink, self.cap_glow) } // Under the hand the face lifts by what this sheet's small caps lift // (gl_cap's 0.04), laid over the shading so it reads the same on the // lit and the shaded cove. A held or a disabled cap does not lift, // as in gl_cap. capc = capc + vec3(0.04, 0.04, 0.044) * self.hover * (1.0 - press) * awake * (1.0 - paint) // The lit value still showing beside the cap lights the end wall // that faces it, never the line. capc = capc + led * Cap.fill_light(q, hs, s.yz * height, lo, hi, at, tw) * lit * (1.0 - paint) cov = Cap.cover(s.w, px) // Under it: the cast shadow, thrown from the top outline the bench // gives for this cap (its narrow ridge, almost its full width), and // the contact shadow round its foot. A disabled cap sits on the // ground as this sheet seats its disabled caps: no cast shadow, a // weaker contact. let cast_sh = Cap.shadow(q, hs, r, vec3(0.2235, 0.9529, 1.0), lt, rise, self.cap_shadow_blur, self.cap_shadow, px) * awake let contact_sh = Cap.contact(s.w, max(rise * 0.3, 1.0), self.cap_contact * (1.0 - 0.6 * dis)) shd = clamp(cast_sh + contact_sh, 0.0, 1.0) halo = Cap.glow(s.w, hs, self.cap_glow) // Focus: this sheet's ring of the LED's rim blue just outside the // outline, and a two point breath of it. ring = clamp(Finish.ring_out(s.w, 0.0, px, px) * 0.9 + Finish.glow(s.w - px, 0.7) * 0.3 * (1.0 - cov), 0.0, 1.0) * self.focus * awake } col = mix(col, col * shadow_ink, shd * (1.0 - cov)) col = col + body * halo * 0.9 * (1.0 - cov) col = mix(col, capc, cov) col = mix(col, self.cap_focus_ink.rgb, ring) // Toned, both the fader and the ground alike, and laid over the ground. let ev = pow(2.0, self.cap_exposure) return gl_fader_on_ground(Cap.tone(col, ev, self.cap_rolloff), Cap.tone(ground, ev, self.cap_rolloff)) } // ---------------------------------------------------------------- knobs // A knob: a well ring round a raised round cap, the value lit in the ring // as a thin arc without a halo, and an LED bar on the cap for a pointer. // Angles run from straight down, clockwise, the gap at the bottom. let gl_knob = fn(p: vec2, c: vec2, rr: float, px: float, slide: float, origin: float, arc_origin: float, gap: float, hover: float, held: float, focus: float, disabled: float) -> vec4 { // The ring scales with a small knob and stops at a 1.5 point gap and a // 4 point width, so a large dial stays slim instead of a heavy donut. let rc = max(rr / 1.32, rr - 5.5) let ring_in = rc + min(0.08 * rc, 1.5) let ring_out = ring_in + min(0.24 * rc, 4.0) let ring_mid = (ring_in + ring_out) * 0.5 let ring_w = ring_out - ring_in let dr = Finish.sd_ring(p, c, ring_mid, ring_w) let q = p - c let rl = length(q) // The ring well: lip on the ground under its lower outer edge, the // black outline both sides, a dark fill. var res = vec4(0.28, 0.30, 0.34, 1.0) * Finish.ring_out(rl - ring_out, 0.0, px, px) * step(c.y + ring_out * 0.3, p.y) var col = vec3(0.141, 0.149, 0.176) col = mix(col, vec3(0.039, 0.039, 0.047), Finish.band(dr, 0.0, px, px)) let ra = Finish.cover(dr, px) res = Finish.over(res, vec4(col * ra, ra)) // The value arc, lit like the lamp: 1.5 points on the middle of the // ring, a pale core, the accent at its edges and a soft halo on the // ring's floor round it. Angles from straight down, clockwise. let one = PI / 180.0 let sweep = 2.0 * PI - gap * one let start = gap * one * 0.5 let ve = start + sweep * slide let oe = start + sweep * origin let a0 = mix(start, min(oe, ve), arc_origin) let a1 = mix(ve, max(oe, ve), arc_origin) var ang = atan2(-q.x, q.y) if ang < 0.0 { ang = ang + 2.0 * PI } let ac = clamp(ang, a0, a1) let ap = c + vec2(-sin(ac), cos(ac)) * ring_mid let da = length(p - ap) let live = step(0.002, a1 - a0) let ink = mix(vec3(0.227, 0.655, 0.965), vec3(0.227, 0.29, 0.388), disabled) let floor_in = Finish.cover(dr + px, px) let xa = max(da - 0.75, 0.0) let ga = (0.30 * exp(-(xa * xa) / (2.0 * 0.55 * 0.55)) + 0.14 * exp(-(xa * xa) / (2.0 * 1.5 * 1.5))) * floor_in * (1.0 - Finish.cover(da - 0.75, px)) * (1.0 - disabled) * live res = Finish.over(res, vec4(0.227, 0.655, 0.965, 1.0) * ga) let acol = mix(mix(vec3(0.227, 0.655, 1.0), vec3(1.0, 1.0, 1.0), 0.72 * (1.0 - disabled)), ink, smoothstep(0.1, 0.7, da)) res = Finish.over(res, vec4(acol, 1.0) * Finish.cover(da - 0.75, px) * live) // The cap. res = Finish.over(res, gl_cap(p, c - vec2(rc, rc), c + vec2(rc, rc), rc, px, hover, 0.0, disabled, focus, 1.0, vec3(0.0, 0.0, 0.0), vec4(0.196, 0.204, 0.227, 1.0), 1.0)) // A hint of reflection on the lacquered cap: a soft pale spot up and to // the left, inside the outline. let sp = p - (c + vec2(-0.3 * rc, -0.42 * rc)) let rs = 0.3 * rc let refl = exp(-dot(sp, sp) / max(2.0 * rs * rs, 0.001)) * 0.05 * Finish.cover(rl - rc + px * 2.0, px) * (1.0 - disabled) res = Finish.over(res, vec4(0.80, 0.83, 0.88, 1.0) * refl) // The pointer: an LED bar 0.3D long from 0.12D inside the rim. let dir = vec2(-sin(ve), cos(ve)) let per = vec2(-dir.y, dir.x) let lp = vec2(dot(q, dir), dot(q, per)) let len = 0.6 * rc let tip = rc - 0.24 * rc // Lit, a two device pixel core and a one pixel rim, no halo at rest; // a held knob breathes the lamp's halo. res = gl_led(res, lp, vec2(tip - len * 0.5, 0.0), vec2(len * 0.5, px), 1.0 - disabled, 0.5 * held, px, vec3(0.94, 0.984, 1.0), vec3(0.31, 0.506, 0.702)) return res } mod.widgets.RotaryKnob.clip_x = false mod.widgets.RotaryKnob.clip_y = false mod.widgets.RotaryKnob.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.RotaryKnob.draw_bg.pixel = fn() { let m = 5.0 let px = gl_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 gl_knob(p, c, dia * 0.5 - 1.0, px, self.slide_pos, self.origin_pos, self.arc_origin, self.gap, self.hover, self.drag, self.focus, self.disabled) } mod.widgets.RotaryFlat.clip_x = false mod.widgets.RotaryFlat.clip_y = false mod.widgets.RotaryFlat.draw_bg.vertex = fn() { let m = 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.RotaryFlat.draw_bg.pixel = fn() { let m = 5.0 let px = gl_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 gl_knob(p, c, dia * 0.5 - 1.0, px, self.slide_pos, self.origin_pos, self.arc_origin, self.gap, self.hover, self.drag, self.focus, self.disabled) } // ---------------------------------------------------------------- fields // A field well that keeps inside its own box: a text field clips what it // holds, so its right outline takes the box's last whole column and its lip // the last whole row. Focus lights a lamp along the foot of the well: a // pale line with the lamp's rim colour under it and a soft halo rising on // the floor; the outline takes a little of the blue. let gl_field = fn(p: vec2, size: vec2, o: vec2, px: float, fill: vec3, hover: float, focus: float, disabled: float) -> vec4 { let lo = vec2(gl_snap(0.0, o.x, px), gl_snap(0.0, o.y, px)) // The box's far edges rounded DOWN to a whole device pixel; the bottom // outline one pixel above the last row, the lip on it. let hi = vec2(gl_snap(size.x - px * 0.49, o.x, px), gl_snap(size.y - px * 0.49, o.y, px) - px) let c = (lo + hi) * 0.5 let d = Material.sd_box(p, c, (hi - lo) * 0.5, 2.0) // Under the hand the floor lifts 0.025 and the outline takes a fifth of // the lamp's blue. var f = mix(fill + vec3(0.025, 0.025, 0.027) * hover, vec3(0.180, 0.188, 0.220), disabled) var res = gl_well(p, lo, hi, 2.0, px, f, 0.0, disabled, 1.0) let fo = focus * (1.0 - disabled) let ho = hover * (1.0 - disabled) * (1.0 - focus) res = Finish.over(res, vec4(0.31, 0.506, 0.702, 1.0) * Finish.band(d, 0.0, px, px) * (0.45 * fo + 0.2 * ho)) // The foot lamp: the two rows above the bottom outline, inset past the // corners. let y1 = hi.y - px let infloor = Finish.cover(d + px, px) * step(lo.x + 3.0, p.x) * step(p.x, hi.x - 3.0) let dy = max(y1 - px - p.y, 0.0) let g = (0.34 * exp(-(dy * dy) / (2.0 * 0.7 * 0.7)) + 0.14 * exp(-(dy * dy) / (2.0 * 2.0 * 2.0))) * step(p.y, y1 - px) * infloor res = Finish.over(res, vec4(0.31, 0.506, 0.702, 1.0) * g * fo) let rim = step(y1 - px, p.y) * step(p.y, y1) * infloor let core = step(y1 - px * 2.0, p.y) * step(p.y, y1 - px) * infloor res = Finish.over(res, vec4(0.31, 0.506, 0.702, 1.0) * rim * fo) res = Finish.over(res, vec4(0.84, 0.94, 1.0, 1.0) * core * fo) return res } // Text fields: a flat well 24 points tall, -0.10 against the ground. 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 = gl_px(self.draw_pass.dpi_factor) return gl_field(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, vec3(0.141, 0.149, 0.176), self.hover, self.focus, self.disabled) } mod.widgets.TextInputFlat.padding = mod.turtle.Inset{left: 8.0 right: 8.0 top: 3.0 bottom: 3.0} mod.widgets.TextInputFlat.min_height = 24.0 mod.widgets.TextInputFlat.draw_cursor.color = #f0fbff mod.widgets.TextInputFlat.draw_text.color = #d4d7de mod.widgets.TextInputFlat.draw_text.color_empty = #6a6c74 mod.widgets.TextInputFlat.draw_text.color_disabled = #5c5e66 // The number field: the same well, its stepper a pair of small caps joined // by an engraved line. mod.widgets.NumberField.draw_bg.pixel = fn() { let px = gl_px(self.draw_pass.dpi_factor) return gl_field(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, vec3(0.141, 0.149, 0.176), self.hover, self.focus, self.disabled) } mod.widgets.NumberField.spin_width = 14.0 mod.widgets.NumberSpin.width = 14.0 // The widget's own filled triangles are hidden; the face strokes a 5 x 3 // point chevron in a 1 dp line of #9a9ca4 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 = gl_px(self.draw_pass.dpi_factor) let o = self.rect_pos let p = self.pos * self.rect_size // The stepper lies inside the field's well: its own floor is the // field's, set off by an engraved divider (dark, then light) and split // by an engraved line across the middle. The field's outline and lip // stay the field's. let x0 = gl_snap(0.0, o.x, px) let xr = gl_snap(self.rect_size.x - px * 0.49, o.x, px) - px let yt = gl_snap(0.0, o.y, px) + px let yb = gl_snap(self.rect_size.y - px * 0.49, o.y, px) - px * 2.0 let mid = gl_snap(self.rect_size.y * 0.5, o.y, px) let live = 1.0 - self.disabled let inx = step(x0 + px * 2.0, p.x) * step(p.x, xr) let upper = inx * step(yt, p.y) * step(p.y, mid - px) let lower = inx * step(mid + px, p.y) * step(p.y, yb) // Under the hand a half lifts a little; pressed, it sinks with a shade // under its top edge. var res = vec4(1.0, 1.0, 1.0, 1.0) * 0.035 * (upper * max(0.0, self.hot) + lower * max(0.0, -self.hot)) * live let su = 0.45 * (1.0 - smoothstep(0.0, 3.0, p.y - yt)) * upper * max(0.0, self.down) let sd = 0.45 * (1.0 - smoothstep(0.0, 3.0, p.y - mid - px)) * lower * max(0.0, -self.down) res = Finish.over(res, vec4(0.0, 0.0, 0.0, 1.0) * (su + sd) * live) let vin = step(yt + 2.0, p.y) * step(p.y, yb - 2.0) let dark = step(x0, p.x) * step(p.x, x0 + px) * vin let light = step(x0 + px, p.x) * step(p.x, x0 + px * 2.0) * vin let hin = step(x0 + px * 2.0 + 2.0, p.x) * step(p.x, xr - 2.0) let hdark = step(mid - px, p.y) * step(p.y, mid) * hin let hlight = step(mid, p.y) * step(p.y, mid + px) * hin res = Finish.over(res, vec4(0.039, 0.039, 0.047, 1.0) * (dark + hdark)) res = Finish.over(res, vec4(0.271, 0.278, 0.310, 1.0) * (light + hlight) * 0.8) let ink = mix(vec3(0.604, 0.612, 0.643), vec3(0.36, 0.37, 0.4), self.disabled) let cx = (x0 + px * 2.0 + xr) * 0.5 let cu = (yt + mid) * 0.5 let cd = (mid + yb) * 0.5 let du = min(gl_seg(p, vec2(cx - 2.5, cu + 1.5), vec2(cx, cu - 1.5)), gl_seg(p, vec2(cx, cu - 1.5), vec2(cx + 2.5, cu + 1.5))) let dd = min(gl_seg(p, vec2(cx - 2.5, cd - 1.5), vec2(cx, cd + 1.5)), gl_seg(p, vec2(cx, cd + 1.5), vec2(cx + 2.5, cd - 1.5))) let k = min(du, dd) - px * 0.5 return Finish.over(res, vec4(ink, 1.0) * Finish.cover(k, px)) } // The drop-down: a button cap with an 18 point chevron zone at its right, // behind an engraved divider. mod.widgets.DropDownFlat.padding = mod.turtle.Inset{left: 8.0 right: 26.0 top: 3.0 bottom: 3.0} mod.widgets.DropDownFlat.clip_x = false mod.widgets.DropDownFlat.clip_y = false mod.widgets.DropDownFlat.draw_bg.vertex = fn() { let m = 7.0 self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m)) } // The engraved divider and the chevron, laid on a cap or a well. let gl_chevron_zone = fn(dst: vec4, p: vec2, lo: vec2, hi: vec2, px: float, disabled: float) -> vec4 { let dx = hi.x - 18.0 let inside = step(lo.y + 4.0, p.y) * step(p.y, hi.y - 4.0) let dark = step(dx - px, p.x) * step(p.x, dx) * inside let light = step(dx, p.x) * step(p.x, dx + px) * inside var res = Finish.over(dst, vec4(0.039, 0.039, 0.047, 1.0) * dark) res = Finish.over(res, vec4(0.271, 0.278, 0.310, 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.5), 0.75) 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.604, 0.612, 0.643), vec3(0.36, 0.37, 0.4), disabled), 1.0), 0.75) return Finish.over(res, sdf.result) } mod.widgets.DropDownFlat.draw_bg.pixel = fn() { let m = 7.0 let px = gl_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(gl_snap(0.0, o.x, px), gl_snap(0.0, o.y, px)) let hi = vec2(gl_snap(self.rect_size.x, o.x, px), gl_snap(self.rect_size.y, o.y, px)) var res = gl_cap(p, lo, hi, 2.0, px, self.hover, max(self.down, self.active), self.disabled, self.focus, 1.0, vec3(0.0, 0.0, 0.0), vec4(0.196, 0.204, 0.227, 1.0), 0.0) return gl_chevron_zone(res, p, lo, hi, px, self.disabled) } // The combo box: a text well, its list opened from the same chevron zone. mod.widgets.ComboBoxFlat.padding = mod.turtle.Inset{left: 8.0 right: 26.0 top: 3.0 bottom: 3.0} mod.widgets.ComboBoxFlat.input.min_height = 0.0 mod.widgets.ComboBoxFlat.draw_bg.pixel = fn() { let px = gl_px(self.draw_pass.dpi_factor) let p = self.pos * self.rect_size let res = gl_field(p, self.rect_size, self.rect_pos, px, vec3(0.141, 0.149, 0.176), self.hover, self.focus, self.disabled) let lo = vec2(0.0, 0.0) let hi = vec2(gl_snap(self.rect_size.x - px * 0.49, self.rect_pos.x, px), gl_snap(self.rect_size.y - px * 0.49, self.rect_pos.y, px) - px) return gl_chevron_zone(res, p, lo, hi, px, self.disabled) } // Open lists: a flat dark face, a 1 dp top line, rows that lift 0.03 under // the hand, and the chosen row filled with the accent -- the one fill in // this family. let gl_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.173, 0.184, 0.216), vec3(0.157, 0.167, 0.198), p.y / max(size.y, 1.0)) col = mix(col, vec3(0.25, 0.26, 0.29), Finish.band(d, px, px * 2.0, px) * step(p.y, c.y) * step(3.0, p.y)) col = mix(col, vec3(0.039, 0.043, 0.055), Finish.band(d, 0.0, px, px)) let a = Finish.cover(d, px) return vec4(col * a, a) } let gl_row = fn(p: vec2, size: vec2, px: float, hover: float, active: float, disabled: float) -> vec4 { let top = 1.0 - step(px, p.y) var col = vec4(1.0, 1.0, 1.0, 1.0) * 0.035 * hover * (1.0 - disabled) let sel = vec3(0.227, 0.498, 0.753) let fill = vec4(mix(sel, vec3(0.45, 0.66, 0.86), top), 1.0) return mix(col, fill, active) } // The open list is clipped to its own box by the popup that holds it, so // its shadow cannot be drawn from here. mod.widgets.PopupMenuFlat.draw_bg.pixel = fn() { let px = gl_px(self.draw_pass.dpi_factor) return gl_menu(self.pos * self.rect_size, self.rect_size, px) } mod.widgets.PopupMenuItem.draw_bg.pixel = fn() { let px = gl_px(self.draw_pass.dpi_factor) return gl_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 = gl_px(self.draw_pass.dpi_factor) return gl_menu(self.pos * self.rect_size, self.rect_size, px) } mod.widgets.ComboBoxFlat.draw_item.pixel = fn() { let px = gl_px(self.draw_pass.dpi_factor) return gl_row(self.pos * self.rect_size, self.rect_size, px, self.hover, self.active, 0.0) } mod.widgets.ComboBoxFlat.draw_item_text.color_active = #ffffff // ---------------------------------------------------------------- tabs // The segmented control: the whole strip one raised cap; the chosen answer // sinks, flat and tinted toward the light, its legend lit and a lamp under // it. mod.widgets.SegmentedControlFlat.draw_bg.vertex = fn() { let m = 7.0 self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m)) } mod.widgets.SegmentedControlFlat.draw_bg.pixel = fn() { let m = 7.0 let px = gl_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(gl_snap(0.0, o.x, px), gl_snap(0.0, o.y, px)) let hi = vec2(gl_snap(self.rect_size.x, o.x, px), gl_snap(self.rect_size.y, o.y, px)) return gl_cap(p, lo, hi, 2.0, px, 0.0, 0.0, 0.0, 0.0, 1.0, vec3(0.0, 0.0, 0.0), vec4(0.196, 0.204, 0.227, 1.0), 1.0) } // The sunk segment: no top line, a shade falling from the top, the face // tinted #2c3643, and a 1 dp tinted edge either side where it meets its // neighbours. let gl_sunk = fn(p: vec2, lo: vec2, hi: vec2, px: float, hover: float) -> vec4 { let hh = hi.y - lo.y let t = clamp((p.y - lo.y) / max(hh, 1.0), 0.0, 1.0) var col = mix(vec3(0.173, 0.212, 0.263), vec3(0.133, 0.157, 0.192), t) + vec3(0.01, 0.01, 0.01) * hover col = mix(col, vec3(0.0, 0.0, 0.0), 0.5 * (1.0 - smoothstep(0.0, 0.2 * hh, p.y - lo.y))) let edge = (1.0 - step(lo.x + px, p.x)) + step(hi.x - px, p.x) col = mix(col, vec3(0.349, 0.412, 0.471), edge * 0.8) let inside = step(lo.x, p.x) * step(p.x, hi.x) * step(lo.y, p.y) * step(p.y, hi.y) return vec4(col, 1.0) * inside } // The chosen answer is a push button pressed in: Day, Week and Month are // three caps and the chosen one is sunk deep between the others. Its well // runs from under the strip's top outline to its bottom outline, black // under the top edge and shaded along both walls, and the neighbours' ends // stand lit over it. The strip lays the answers 2 points in from its edge; // the quad grows so the well can reach the outline and the neighbours' ends. mod.widgets.SegmentedControlFlat.draw_pill.vertex = fn() { let m = 9.0 self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m)) } // On the column (its pill's `radius`, unused by this face, set to -1 as the // mark) the well keeps to its own segment's height -- the answers above and // below are the neighbours -- and reaches the strip's side outlines instead. mod.widgets.SegmentedControlFlat.draw_pill.pixel = fn() { let m = 9.0 let v = 1.0 - step(0.0, self.radius) let hz = 1.0 - v let px = gl_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(gl_snap(-2.0 * v, o.x, px) + px * v, gl_snap(-2.0 * hz, o.y, px) + px * hz) let hi = vec2(gl_snap(self.rect_size.x + 2.0 * v, o.x, px) - px * v, gl_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 floor, dark and a little blue. var col = mix(vec3(0.173, 0.212, 0.263), vec3(0.133, 0.157, 0.192), t) // The inner shadow: black right under the top edge, gone in about // seven rows; along each wall a shade gone in four; a darker last row. let top = exp(-max(p.y - lo.y, 0.0) / (3.0 * px)) let wl = 0.75 * exp(-max(p.x - lo.x, 0.0) / (2.0 * px)) let wr = 0.75 * exp(-max(hi.x - px - p.x, 0.0) / (2.0 * px)) let foot = 0.35 * step(hi.y - px, p.y) let dk = 1.0 - (1.0 - top) * (1.0 - wl) * (1.0 - wr) * (1.0 - foot) 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)) // Pressed in, the chosen cap throws no shadow: under it the ground (with // the window's grain) comes back a row under the outline, and only the // neighbours' shadows reach in at the sides. let below = step(hi.y + px, p.y) * step(lo.x, p.x) * (1.0 - step(hi.x, p.x)) let bx = smoothstep(0.0, 4.0 * px, p.x - lo.x) * smoothstep(0.0, 4.0 * px, hi.x - p.x) let contact = 0.8 * exp(-max(p.y - hi.y - px, 0.0) / (0.9 * px)) // (The grain as `gl_grain` further down reads it, in window space.) let gi = floor((p + o) / px) let gj = gi + vec2(Finish.hash(gi) * 419.0, Finish.hash(gi + vec2(5.31, 9.17)) * 263.0) let g = (Finish.hash(gj) - Finish.hash(gj + vec2(17.13, 3.71))) * 0.02 var res = vec4(mix(vec3(0.224, 0.231, 0.267) + vec3(g, g, g), vec3(0.0, 0.0, 0.0), contact), 1.0) * below * bx * hz res = Finish.over(res, vec4(col, 1.0) * inside) // The neighbours' ends standing over the well: a lit column on each, // and a fainter one behind it on the left. let ey = step(lo.y + px, p.y) * (1.0 - step(hi.y - px, p.y)) let el = step(lo.x - px, p.x) * (1.0 - step(lo.x, p.x)) let el2 = step(lo.x - px * 2.0, p.x) * (1.0 - step(lo.x - px, p.x)) let er = step(hi.x, p.x) * (1.0 - step(hi.x + px, p.x)) res = Finish.over(res, vec4(0.294, 0.310, 0.345, 1.0) * (el + er) * ey * hz) res = Finish.over(res, vec4(0.259, 0.267, 0.314, 1.0) * el2 * ey * 0.6 * hz) // On the column the answer below stands over the well's foot: its top // line, between the side outlines. let eb = step(hi.y, p.y) * (1.0 - step(hi.y + px, p.y)) * step(lo.x + px, p.x) * (1.0 - step(hi.x - px, p.x)) res = Finish.over(res, vec4(0.294, 0.310, 0.345, 1.0) * eb * v) // The lamp, centred under the word: the widget centres the word by its // advance, which sets the ink about a point left of the middle, so the // lamp stands a point left of it too. The core's top row 8.4 points // under the segment's middle: five rows of lit floor under it before the // well's foot, three rows of well between a descender and the lamp's // pale top row. Round it a broad soft pool on the floor. let c = vec2((lo.x + hi.x) * 0.5 - 1.0, self.rect_size.y * 0.5) let y0 = gl_snap(c.y + 8.4167, o.y, px) let q = vec2((p.x - c.x) / max((hi.x - lo.x) * 0.3, 1.0), (p.y - (y0 + px)) / 3.2) let pool = exp(-dot(q, q)) * 0.16 * inside res = Finish.over(res, vec4(0.405, 0.593, 0.752, 1.0) * pool) let xs = vec2(gl_snap(c.x - 5.3333, o.x, px), gl_snap(c.x + 5.3333, o.x, px)) let lamp = gl_lamp(res, p, xs, y0, px, 1.0, 1.0, vec3(0.910, 0.918, 0.922), vec3(0.405, 0.593, 0.752)) res = mix(res, lamp, inside) // The light reaches the foot of the well and spills over its bottom // edge: the last row of the floor, the outline and the row under it. let ex = max(max(xs.x - p.x, p.x - xs.y), 0.0) / px let gx = exp(-(ex * ex) / (2.0 * 7.0 * 7.0)) * step(lo.x, p.x) * (1.0 - step(hi.x, p.x)) let r0 = step(hi.y - px, p.y) * (1.0 - step(hi.y, p.y)) let r1 = step(hi.y, p.y) * (1.0 - step(hi.y + px, p.y)) let r2 = step(hi.y + px, p.y) * (1.0 - step(hi.y + px * 2.0, p.y)) let spill = (0.34 * r0 + (0.22 * r1 + 0.10 * r2) * hz) * gx return Finish.over(res, vec4(0.56, 0.66, 0.76, 1.0) * spill) } // Cap labels at the family's 8 points, the chosen one included. mod.widgets.SegmentedControlFlat.draw_text.text_style.font_size = 8.0 mod.widgets.SegmentedControlFlat.draw_text_selected.text_style.font_size = 8.0 mod.widgets.SegmentedControlFlat.draw_text.color = #c4c7ce mod.widgets.SegmentedControlFlat.draw_text_selected.color = #fafafa mod.widgets.SegmentedControlVertical.draw_pill.radius = -1.0 // Tabs: joined segments of one strip. Each quiet tab is a raised segment // whose joint with the next is engraved, dark then light; the tab in use // sinks like the chosen segment, its word lit and the lamp under it. mod.widgets.Tabs.height = 26.0 mod.widgets.Tabs.clip_x = false mod.widgets.Tabs.clip_y = false let gl_tab = fn(pos: vec2, size: vec2, o: vec2, dpi: float, hover: float, active: float) -> vec4 { let m = 7.0 let px = gl_px(dpi) let p = pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let lo = vec2(gl_snap(0.0, o.x, px), gl_snap(0.0, o.y, px)) let hi = vec2(gl_snap(size.x, o.x, px), gl_snap(size.y, o.y, px)) let inx = step(lo.x, p.x) * step(p.x, hi.x) var res = gl_cap(p, lo - vec2(4.0, 0.0), hi + vec2(4.0, 0.0), 0.0, px, hover, active, 0.0, 0.0, 1.0, vec3(0.0, 0.0, 0.0), vec4(0.196, 0.204, 0.227, 1.0), 0.0) * inx res = mix(res, Finish.over(res, gl_sunk(p, lo + vec2(0.0, px), hi - vec2(0.0, px), px, hover)), active) // The joint: the black outline on the left, just outside the box, and a // light line inside it; the dark line inside the right edge. let left_out = step(lo.x - px, p.x) * (1.0 - step(lo.x, p.x)) let left_in = step(lo.x, p.x) * (1.0 - step(lo.x + px, p.x)) let right_in = step(hi.x - px, p.x) * (1.0 - step(hi.x, p.x)) let iny = step(lo.y, p.y) * step(p.y, hi.y) res = Finish.over(res, vec4(0.039, 0.039, 0.047, 1.0) * (left_out + right_in) * iny) res = Finish.over(res, vec4(0.29, 0.30, 0.33, 1.0) * left_in * iny * step(lo.y + px, p.y) * step(p.y, hi.y - px) * (1.0 - active)) // Under the sunk tab only, a 1 dp lip #474c56 on the ground. let lip = step(hi.y, p.y) * (1.0 - step(hi.y + px, p.y)) * inx res = Finish.over(res, vec4(0.278, 0.298, 0.337, 1.0) * lip * active) // The tab in use carries the chosen segment's lamp under its word. The // strip sets its words flush left 10 points in and the face cannot know // how long a word is, so the lamp is centred on the first 27 points of // it -- under a one-word name such as Mixer, Effects or Notes it stands // in the middle; under a longer word it stays under its first letters. // The core's top row 7.75 points under the tab's middle, as on a // segment. let lx = lo.x + 23.5 let xs = vec2(gl_snap(lx - 5.3333, o.x, px), gl_snap(lx + 5.3333, o.x, px)) return gl_lamp(res, p, xs, gl_snap((lo.y + hi.y) * 0.5 + 7.75, o.y, px), px, active, 1.0, vec3(0.910, 0.918, 0.922), vec3(0.405, 0.593, 0.752)) } mod.widgets.Tabs.draw_tab.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.Tabs.draw_tab_hover.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.Tabs.draw_tab_active.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.Tabs.draw_bg.pixel = fn() { return vec4(0.0, 0.0, 0.0, 0.0) } mod.widgets.Tabs.draw_tab.pixel = fn() { return gl_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 gl_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 gl_tab(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, 0.0, 1.0) } mod.widgets.Tabs.draw_sep.pixel = fn() { return vec4(0.0, 0.0, 0.0, 0.0) } mod.widgets.Tabs.draw_mark.color = #9a9ca4 mod.widgets.Tabs.draw_text.color = #9a9ca4 // The tab in use answers like the chosen segment: its ink #d4d7de in // semibold, in the sunk blue well; quiet tabs grey regular. mod.widgets.Tabs.draw_text_active.color = #d4d7de mod.widgets.Tabs.draw_text.text_style.font_size = 8.0 mod.widgets.Tabs.draw_text_active.text_style = mod.theme.font_bold{font_size: 8.0} // ---------------------------------------------------------------- feedback // The progress bar: an 8 point well; the value is the lit bar of the // sliders, a pale core row over a body row in the middle of the floor, its // halo on the floor round it. mod.widgets.ProgressBarFlat.height = 8.0 mod.widgets.ProgressBarFlat.draw_bg.pixel = fn() { let px = gl_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 = gl_snap((self.rect_size.y - th) * 0.5, o.y, px) let x0 = gl_snap(self.rect_size.x * self.start, o.x, px) let x1 = gl_snap(self.rect_size.x * self.end, o.x, px) let lo = vec2(x0, y0) let hi = vec2(x1, y0 + gl_snap(th, 0.0, px) - px) var res = vec4(0.0, 0.0, 0.0, 0.0) if self.track > 0.5 { res = gl_well(p, lo, hi, 2.0, px, vec3(0.141, 0.149, 0.176), 0.0, 0.0, 1.0) } let fill = self.fill_color() let ins = 2.0 let fl = vec2(lo.x + ins, lo.y + ins) let span = hi.x - lo.x - ins * 2.0 var f0 = fl.x var f1 = fl.x + span * clamp((self.value - self.start) / max(self.end - self.start, 0.001), 0.0, 1.0) if self.value < 0.0 { // Still: the sweep would need the pass clock, and a window at rest // reads no clock, so the unknown is a segment standing in the // middle with its ends fading. f0 = fl.x + span * 0.3 f1 = fl.x + span * 0.7 } // The fill is lit like the lamp: a pale core row over a row of the // role colour in the middle of the well's floor, the halo on the floor // round it and past its ends. var fade = fill.a * step(0.001, f1 - f0) if self.value < 0.0 { fade = fade * smoothstep(f0, f0 + span * 0.12, p.x) * smoothstep(f1, f1 - span * 0.12, p.x) } let lb = gl_lit_bar(vec4(0.0, 0.0, 0.0, 0.0), p, vec2(f0, lo.y + px), vec2(f1, hi.y - px), vec2(lo.x + px, hi.x - px), px, fill.rgb, 0.0) res = Finish.over(res, lb * fade) return res * self.opacity } mod.widgets.ProgressBarFlat.draw_bg.color_primary = #3baeff // The level meter: cells of 3 dp and a 1 dp black gap on black glass in a // slim well; green, then yellow toward the top, red at the end; unlit cells // stand at a fifth of the lit. The clip lamp is an LED. 8 points thick. mod.widgets.LevelMeter.height = 8.0 mod.widgets.LevelMeterColumn.width = 8.0 mod.widgets.LevelMeter.draw_bg.pixel = fn() { let px = gl_px(self.draw_pass.dpi_factor) let o = self.rect_pos let sz = self.rect_size let p = self.pos * sz let v = self.vertical let along = mix(sz.x, sz.y, v) let across = mix(sz.y, sz.x, v) let a = mix(p.x, sz.y - p.y, v) let b = mix(p.y, p.x, v) let lamp_room = self.lamp * (across + 3.0) let bar = max(4.0, along - lamp_room) // The window: black glass in a 1 dp outline with its lip. let wlo = mix(vec2(0.0, 0.0), vec2(0.0, sz.y - bar), v) let whi = mix(vec2(bar, sz.y), vec2(sz.x, sz.y), v) let lo = vec2(gl_snap(wlo.x, o.x, px), gl_snap(wlo.y, o.y, px)) let hi = vec2(gl_snap(whi.x, o.x, px), gl_snap(whi.y, o.y, px) - px) var res = gl_well(p, lo, hi, 1.0, px, vec3(0.03, 0.035, 0.045), 0.0, 0.0, 1.0) let pad = 2.0 * px let span = max(1.0, bar - pad * 2.0) let pitch = px * 4.0 let cell = step(fract((a - pad) / pitch), 0.75) let share = clamp((a - pad) / span, 0.0, 1.0) let inb = step(pad, a) * step(a, bar - pad) * step(pad, b) * step(b, across - pad) * step(p.y, hi.y - px) var lit_col = vec3(0.498, 0.788, 0.227) lit_col = mix(lit_col, vec3(0.902, 0.788, 0.251), step(self.hot_at, share)) lit_col = mix(lit_col, vec3(0.941, 0.318, 0.227), step(self.hot_to, share)) // The first device pixel of each cell a touch lighter and paler. let edge = step(fract((a - pad) / pitch), 0.25) lit_col = mix(lit_col, mix(lit_col, vec3(0.85, 0.87, 0.8), 0.35), edge) let on = step(share, self.level) * step(0.001, self.level) let unlit = vec3(0.145, 0.153, 0.176) let col = mix(unlit, lit_col, on) res = Finish.over(res, vec4(col, 1.0) * cell * inb * self.opacity) // The hold: one cell at full strength. let hold_at = pad + span * self.hold let hc = step(abs(a - hold_at), pitch * 0.5) * step(0.002, self.hold) res = Finish.over(res, vec4(mix(vec3(0.498, 0.788, 0.227), vec3(0.941, 0.318, 0.227), step(self.hot_to, self.hold)), 1.0) * hc * cell * inb) // The lamp. if self.lamp > 0.5 { let lc = mix(vec2(bar + 3.0 + across * 0.5, sz.y * 0.5), vec2(sz.x * 0.5, across * 0.5), v) // A small red lamp, 3.5 points: a white-hot core, a red rim and a // soft red halo when lit; a dark lens when out. let rl = clamp(across * 0.5 - 1.5, 1.0, 1.75) res = gl_led(res, p, lc, vec2(rl, rl), step(0.5, self.over), 0.45, px, vec3(1.0, 0.957, 0.941), vec3(0.941, 0.318, 0.227)) } return res * self.opacity } // ---------------------------------------------------------------- surfaces // The scroll bar: an 8 point well, the thumb a 6 point cap inset 1 point, // no gloss, no glow. mod.widgets.ScrollBar.bar_size = 8.0 mod.widgets.ScrollBar.bar_side_margin = 1.0 mod.widgets.ScrollBar.draw_bg.pixel = fn() { let px = gl_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(gl_snap(0.0, o.x, px), gl_snap(0.0, o.y, px)) let hi = vec2(gl_snap(sz.x, o.x, px), gl_snap(sz.y, o.y, px)) var res = gl_well(p, lo, hi, 2.0, px, vec3(0.141, 0.149, 0.176), 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(gl_snap(tlo.x, o.x, px), gl_snap(tlo.y, o.y, px)) let shi = vec2(gl_snap(thi.x, o.x, px), gl_snap(thi.y, o.y, px)) res = Finish.over(res, gl_cap(p, slo, shi, 1.5, px, max(self.hover, self.drag), 0.0, 0.0, 0.0, 0.0, vec3(0.0, 0.0, 0.0), vec4(0.196, 0.204, 0.227, 1.0), 0.0)) return res * self.opacity } // The housing's grain: monochrome, one value per device pixel, sd 0.008. // The library's hash nearly repeats every 97 device pixels (97 x 0.1031 is // almost whole), which showed as a lattice over a wide ground; each pixel // is first sent to a far cell picked by the hash of its own, and the grain // is read there, so no shift lines the pattern up again. let gl_grain = fn(p: vec2, o: vec2, px: float) -> float { let i = floor((p + o) / px) let j = i + vec2(Finish.hash(i) * 419.0, Finish.hash(i + vec2(5.31, 9.17)) * 263.0) return (Finish.hash(j) - Finish.hash(j + vec2(17.13, 3.71))) * 0.02 } // A raised panel: a soft drop that stays within the gap to its neighbours, // the black outline, a face that falls 0.02 top to bottom with the ground's // grain, and one top line. let gl_panel = fn(p: vec2, size: vec2, o: vec2, px: float, r: float, hover: float) -> vec4 { let lo = vec2(gl_snap(0.0, o.x, px), gl_snap(0.0, o.y, px)) let hi = vec2(gl_snap(size.x, o.x, px), gl_snap(size.y, o.y, px)) let c = (lo + hi) * 0.5 let h = (hi - lo) * 0.5 let d = Material.sd_box(p, c, h, r) // Two falls: a firm one close under the edge and a soft one that // carries the panel off the ground for about 10 device pixels below // it; to the sides both are gone within 5 points, half the gap. let sh1 = Finish.drop(p, c, h - vec2(1.0, 0.5), r, vec2(0.0, 1.5), 1.4) * 0.5 let sh2 = Finish.drop(p, c, h - vec2(3.5, 2.0), r, vec2(0.0, 3.5), 3.2) * 0.4 let sh = 1.0 - (1.0 - sh1) * (1.0 - sh2) let contact = Finish.drop(p, c, h, r, vec2(0.0, px), px * 0.6) * 0.7 var res = vec4(0.0, 0.0, 0.0, 1.0 - (1.0 - sh) * (1.0 - contact)) let t = clamp((p.y - lo.y) / max(hi.y - lo.y, 1.0), 0.0, 1.0) // The face starts at the ground's own value and falls 0.035, so its top // edge crosses over the ground the way a cap's does. var col = mix(vec3(0.220, 0.227, 0.263), vec3(0.188, 0.196, 0.227), t) + vec3(0.02, 0.02, 0.02) * hover col = col + vec3(1.0, 1.0, 1.0) * gl_grain(p, o, px) col = mix(col, vec3(0.345, 0.353, 0.384), Finish.band(d, px, px * 2.0, px) * step(p.y, lo.y + r + px * 2.0) * step(lo.x + r * 0.5, p.x) * step(p.x, hi.x - r * 0.5)) col = mix(col, vec3(0.055, 0.059, 0.071), Finish.band(d, 0.0, px, px)) let a = Finish.cover(d, px) res = Finish.over(res, vec4(col * a, a)) return res } // A tray: the same box cut into the ground instead, with its lip below. let gl_tray = fn(p: vec2, size: vec2, o: vec2, px: float, r: float) -> vec4 { let lo = vec2(gl_snap(0.0, o.x, px), gl_snap(0.0, o.y, px)) let hi = vec2(gl_snap(size.x, o.x, px), gl_snap(size.y, o.y, px) - px) let c = (lo + hi) * 0.5 let d = Material.sd_box(p, c, (hi - lo) * 0.5, r) var res = gl_well(p, lo, hi, r, px, vec3(0.165, 0.173, 0.204), 0.0, 0.0, 1.0) // The ground grain at half strength. let a = Finish.cover(d + px, px) let g = gl_grain(p, o, px) * 0.5 * a // The rim of the ground shades the top of the tray: black at 0.4 under // the top outline, gone 4 points down. let shade = 0.4 * (1.0 - smoothstep(0.0, 4.0, p.y - lo.y - px)) * a return vec4(res.rgb * (1.0 - shade) + vec3(g, g, g) * res.a, res.a) } mod.widgets.PanelView.clip_x = false mod.widgets.PanelView.clip_y = false mod.widgets.PanelView.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 12.0 bottom: 12.0} mod.widgets.PanelView.draw_bg.vertex = fn() { let m = 12.0 self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m)) } mod.widgets.PanelView.draw_bg.pixel = fn() { let m = 12.0 let px = gl_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 gl_tray(p, self.rect_size, self.rect_pos, px, 4.0) } return gl_panel(p, self.rect_size, self.rect_pos, px, 4.0, 0.0) } // The rounded view keeps the colour its caller gives it, square to the // family: a true 2 point corner and, when it is opaque, a 1 dp outline. mod.widgets.RoundedView.draw_bg.pixel = fn() { let px = gl_px(self.draw_pass.dpi_factor) let p = self.pos * self.rect_size let lo = vec2(self.border_inset.x, self.border_inset.y) let hi = self.rect_size - vec2(self.border_inset.z, self.border_inset.w) let c = (lo + hi) * 0.5 let h = max((hi - lo) * 0.5, vec2(0.5, 0.5)) let r = min(2.0 * max(1.0, self.border_radius), min(h.x, h.y)) let d = Material.sd_box(p, c, h, r) var fill = self.color if self.color_2.x > -0.5 { fill = mix(self.color, self.color_2, mix(self.pos.y, self.pos.x, self.gradient_fill_horizontal)) } var ink = self.border_color if self.border_size < 0.01 { ink = vec4(0.039, 0.039, 0.047, fill.a) } let a = Finish.cover(d, px) var col = mix(fill.rgb, ink.rgb, Finish.band(d, 0.0, max(self.border_size, px), px) * ink.a) return vec4(col * a * fill.a, a * fill.a) + vec4(ink.rgb * ink.a, ink.a) * Finish.band(d, 0.0, max(self.border_size, px), px) * a * (1.0 - fill.a) } // The card: a raised panel of its own; a pressable card lifts under the // hand. mod.widgets.Card.clip_x = false mod.widgets.Card.clip_y = false mod.widgets.Card.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 12.0 bottom: 12.0} mod.widgets.Card.draw_bg.vertex = fn() { let m = 12.0 return self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m)) } mod.widgets.Card.draw_bg.pixel = fn() { let m = 12.0 let px = gl_px(self.draw_pass.dpi_factor) let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) return gl_panel(p, self.rect_size, self.rect_pos, px, 4.0, self.hover) } // List rows: 20 points, the text 10 points in; under the hand the row // lifts 0.03. The chosen row is a shallow well -- the chosen segment's blue // floor, a dark line and a short shade under its top edge, a light lip on // its foot -- with a short lamp standing at its left. mod.widgets.ListItem.height = 20.0 mod.widgets.ListItemOne.height = 20.0 mod.widgets.ListItem.padding = mod.turtle.Inset{left: 10.0 right: 8.0 top: 0.0 bottom: 0.0} mod.widgets.ListItemOne.padding = mod.turtle.Inset{left: 10.0 right: 8.0 top: 0.0 bottom: 0.0} mod.widgets.ListItem.draw_bg.pixel = fn() { let px = gl_px(self.draw_pass.dpi_factor) let o = self.rect_pos let p = self.pos * self.rect_size let live = 1.0 - self.disabled var res = vec4(1.0, 1.0, 1.0, 1.0) * (0.03 * self.hover + 0.02 * self.down) * live let y0 = gl_snap(0.0, o.y, px) let y1 = gl_snap(self.rect_size.y, o.y, px) let top = p.y - y0 let t = clamp(top / max(y1 - y0, 1.0), 0.0, 1.0) var col = mix(vec3(0.161, 0.192, 0.235), vec3(0.137, 0.161, 0.200), t) col = mix(col, vec3(0.0, 0.0, 0.0), 0.35 * (1.0 - smoothstep(0.0, 4.0, top - px))) col = mix(col, vec3(0.039, 0.039, 0.047), 1.0 - step(px, top)) col = mix(col, vec3(0.278, 0.298, 0.337), step(y1 - px, p.y)) var sel = vec4(col, 1.0) let lc = vec2(gl_snap(3.5, o.x, px) - px * 0.5, (y0 + y1) * 0.5) sel = gl_led(sel, p, lc, vec2(px * 0.5 + 0.01, max((y1 - y0) * 0.22, 2.0)), live, 0.8, px, vec3(0.88, 0.96, 1.0), vec3(0.31, 0.506, 0.702)) res = mix(res, Finish.over(sel, res), self.selected) return res } mod.widgets.ListItem.draw_text.color = #d4d7de mod.widgets.ListItem.draw_text_2.color = #7c7e86 // A chip is a small seated cap; its role colour only tints the face. Its // label is a cap label: 8 points. mod.widgets.ChipFlat.draw_text.text_style.font_size = 8.0 mod.widgets.ChipFlat.draw_bg.pixel = fn() { let px = gl_px(self.draw_pass.dpi_factor) let o = self.rect_pos let p = self.pos * self.rect_size let lo = vec2(gl_snap(0.0, o.x, px), gl_snap(0.0, o.y, px)) let hi = vec2(gl_snap(self.rect_size.x, o.x, px), gl_snap(self.rect_size.y, o.y, px) - px) let l = Finish.lum(self.color.rgb) let tint = (self.color.rgb - vec3(l, l, l)) * 0.35 * self.color.a var res = gl_cap(p, lo, hi, 2.0, px, self.hover, self.down, 0.0, 0.0, 0.0, tint, vec4(0.196, 0.204, 0.227, 1.0), 0.0) if self.keyed > 0.0 { let d = Material.sd_box(p, (lo + hi) * 0.5, (hi - lo) * 0.5, 2.0) res = Finish.over(res, vec4(0.31, 0.506, 0.702, 1.0) * Finish.band(d, 0.0, px, px) * self.keyed) } return res } // ---------------------------------------------------------------- ink // Words sit embossed-dark: a 1 dp shadow of black at half strength directly // under every glyph of a label. Read from the glyph one device pixel above, // so it costs one more sample and nothing else. // On a cap the label also sinks with it: held, the glyphs are read one // device pixel higher in the atlas, so they stand one pixel lower in the // well. Small labels come from the grayscale atlas, large ones from the // multi-channel one; both paths carry the shadow and the nudge. mod.widgets.ButtonFlat.draw_text.pixel = fn() { let nudge = dFdy(self.t.xy) * step(0.5, self.down) let c = self.get_color() let dxt = length(dFdx(self.t)) let dyt = length(dFdy(self.t)) if self.texture_index < 0.5 { let tex_size = self.grayscale_texture.size() let scale = (dxt + dyt) * tex_size.x * 0.5 let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y) let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel) let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a) let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel) let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a) } if self.texture_index > 1.5 && self.texture_index < 2.5 { let tex_size = self.msdf_texture.size() let scale = (dxt + dyt) * tex_size.x * 0.5 let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y) let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel) let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a) let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel) let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a) } return self.sample_text_pixel() } // A template made from another before this sheet ran keeps its own copy // of the label's draw, so each cap template takes the literal. mod.widgets.Button.draw_text.pixel = fn() { let nudge = dFdy(self.t.xy) * step(0.5, self.down) let c = self.get_color() let dxt = length(dFdx(self.t)) let dyt = length(dFdy(self.t)) if self.texture_index < 0.5 { let tex_size = self.grayscale_texture.size() let scale = (dxt + dyt) * tex_size.x * 0.5 let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y) let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel) let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a) let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel) let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a) } if self.texture_index > 1.5 && self.texture_index < 2.5 { let tex_size = self.msdf_texture.size() let scale = (dxt + dyt) * tex_size.x * 0.5 let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y) let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel) let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a) let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel) let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a) } return self.sample_text_pixel() } mod.widgets.ButtonPrimary.draw_text.pixel = fn() { let nudge = dFdy(self.t.xy) * step(0.5, self.down) let c = self.get_color() let dxt = length(dFdx(self.t)) let dyt = length(dFdy(self.t)) if self.texture_index < 0.5 { let tex_size = self.grayscale_texture.size() let scale = (dxt + dyt) * tex_size.x * 0.5 let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y) let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel) let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a) let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel) let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a) } if self.texture_index > 1.5 && self.texture_index < 2.5 { let tex_size = self.msdf_texture.size() let scale = (dxt + dyt) * tex_size.x * 0.5 let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y) let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel) let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a) let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel) let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a) } return self.sample_text_pixel() } mod.widgets.ButtonSecondary.draw_text.pixel = fn() { let nudge = dFdy(self.t.xy) * step(0.5, self.down) let c = self.get_color() let dxt = length(dFdx(self.t)) let dyt = length(dFdy(self.t)) if self.texture_index < 0.5 { let tex_size = self.grayscale_texture.size() let scale = (dxt + dyt) * tex_size.x * 0.5 let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y) let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel) let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a) let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel) let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a) } if self.texture_index > 1.5 && self.texture_index < 2.5 { let tex_size = self.msdf_texture.size() let scale = (dxt + dyt) * tex_size.x * 0.5 let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y) let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel) let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a) let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel) let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a) } return self.sample_text_pixel() } mod.widgets.ButtonTertiary.draw_text.pixel = fn() { let nudge = dFdy(self.t.xy) * step(0.5, self.down) let c = self.get_color() let dxt = length(dFdx(self.t)) let dyt = length(dFdy(self.t)) if self.texture_index < 0.5 { let tex_size = self.grayscale_texture.size() let scale = (dxt + dyt) * tex_size.x * 0.5 let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y) let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel) let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a) let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel) let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a) } if self.texture_index > 1.5 && self.texture_index < 2.5 { let tex_size = self.msdf_texture.size() let scale = (dxt + dyt) * tex_size.x * 0.5 let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y) let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel) let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a) let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel) let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a) } return self.sample_text_pixel() } mod.widgets.ButtonDanger.draw_text.pixel = fn() { let nudge = dFdy(self.t.xy) * step(0.5, self.down) let c = self.get_color() let dxt = length(dFdx(self.t)) let dyt = length(dFdy(self.t)) if self.texture_index < 0.5 { let tex_size = self.grayscale_texture.size() let scale = (dxt + dyt) * tex_size.x * 0.5 let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y) let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel) let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a) let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel) let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a) } if self.texture_index > 1.5 && self.texture_index < 2.5 { let tex_size = self.msdf_texture.size() let scale = (dxt + dyt) * tex_size.x * 0.5 let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y) let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel) let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a) let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel) let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a) } return self.sample_text_pixel() } mod.widgets.ButtonOutline.draw_text.pixel = fn() { let nudge = dFdy(self.t.xy) * step(0.5, self.down) let c = self.get_color() let dxt = length(dFdx(self.t)) let dyt = length(dFdy(self.t)) if self.texture_index < 0.5 { let tex_size = self.grayscale_texture.size() let scale = (dxt + dyt) * tex_size.x * 0.5 let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y) let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel) let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a) let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel) let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a) } if self.texture_index > 1.5 && self.texture_index < 2.5 { let tex_size = self.msdf_texture.size() let scale = (dxt + dyt) * tex_size.x * 0.5 let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y) let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel) let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a) let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel) let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a) } return self.sample_text_pixel() } mod.widgets.ButtonFlatter.draw_text.pixel = fn() { let nudge = dFdy(self.t.xy) * step(0.5, self.down) let c = self.get_color() let dxt = length(dFdx(self.t)) let dyt = length(dFdy(self.t)) if self.texture_index < 0.5 { let tex_size = self.grayscale_texture.size() let scale = (dxt + dyt) * tex_size.x * 0.5 let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y) let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel) let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a) let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel) let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a) } if self.texture_index > 1.5 && self.texture_index < 2.5 { let tex_size = self.msdf_texture.size() let scale = (dxt + dyt) * tex_size.x * 0.5 let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y) let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel) let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a) let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel) let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a) } return self.sample_text_pixel() } mod.widgets.ButtonXs.draw_text.pixel = fn() { let nudge = dFdy(self.t.xy) * step(0.5, self.down) let c = self.get_color() let dxt = length(dFdx(self.t)) let dyt = length(dFdy(self.t)) if self.texture_index < 0.5 { let tex_size = self.grayscale_texture.size() let scale = (dxt + dyt) * tex_size.x * 0.5 let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y) let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel) let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a) let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel) let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a) } if self.texture_index > 1.5 && self.texture_index < 2.5 { let tex_size = self.msdf_texture.size() let scale = (dxt + dyt) * tex_size.x * 0.5 let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y) let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel) let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a) let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel) let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a) } return self.sample_text_pixel() } mod.widgets.ButtonSm.draw_text.pixel = fn() { let nudge = dFdy(self.t.xy) * step(0.5, self.down) let c = self.get_color() let dxt = length(dFdx(self.t)) let dyt = length(dFdy(self.t)) if self.texture_index < 0.5 { let tex_size = self.grayscale_texture.size() let scale = (dxt + dyt) * tex_size.x * 0.5 let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y) let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel) let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a) let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel) let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a) } if self.texture_index > 1.5 && self.texture_index < 2.5 { let tex_size = self.msdf_texture.size() let scale = (dxt + dyt) * tex_size.x * 0.5 let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y) let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel) let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a) let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel) let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a) } return self.sample_text_pixel() } mod.widgets.ButtonLg.draw_text.pixel = fn() { let nudge = dFdy(self.t.xy) * step(0.5, self.down) let c = self.get_color() let dxt = length(dFdx(self.t)) let dyt = length(dFdy(self.t)) if self.texture_index < 0.5 { let tex_size = self.grayscale_texture.size() let scale = (dxt + dyt) * tex_size.x * 0.5 let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y) let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel) let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a) let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel) let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a) } if self.texture_index > 1.5 && self.texture_index < 2.5 { let tex_size = self.msdf_texture.size() let scale = (dxt + dyt) * tex_size.x * 0.5 let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y) let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel) let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a) let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel) let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a) } return self.sample_text_pixel() } mod.widgets.ButtonXl.draw_text.pixel = fn() { let nudge = dFdy(self.t.xy) * step(0.5, self.down) let c = self.get_color() let dxt = length(dFdx(self.t)) let dyt = length(dFdy(self.t)) if self.texture_index < 0.5 { let tex_size = self.grayscale_texture.size() let scale = (dxt + dyt) * tex_size.x * 0.5 let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y) let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel) let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a) let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel) let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a) } if self.texture_index > 1.5 && self.texture_index < 2.5 { let tex_size = self.msdf_texture.size() let scale = (dxt + dyt) * tex_size.x * 0.5 let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y) let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel) let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a) let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel) let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a) } return self.sample_text_pixel() } // The same shadow on every label that stands on the ground or a cap; one // literal per template, as the handbook advises. A lit legend (the tab or // segment in use) takes a 2 dp halo of the LED's glow instead. mod.widgets.Label.draw_text.pixel = fn() { let main = self.sample_text_pixel() if self.texture_index > 1.5 && self.texture_index < 2.5 { let dxt = length(dFdx(self.t)) let dyt = length(dFdy(self.t)) let tex_size = self.msdf_texture.size() let scale = (dxt + dyt) * tex_size.x * 0.5 let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y) let ps = clamp(self.t.xy - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel) let c = self.get_color() let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a) } return main } mod.widgets.CheckBoxFlat.draw_text.pixel = fn() { let main = self.sample_text_pixel() if self.texture_index > 1.5 && self.texture_index < 2.5 { let dxt = length(dFdx(self.t)) let dyt = length(dFdy(self.t)) let tex_size = self.msdf_texture.size() let scale = (dxt + dyt) * tex_size.x * 0.5 let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y) let ps = clamp(self.t.xy - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel) let c = self.get_color() let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a) } return main } mod.widgets.RadioButtonFlat.draw_text.pixel = fn() { let main = self.sample_text_pixel() if self.texture_index > 1.5 && self.texture_index < 2.5 { let dxt = length(dFdx(self.t)) let dyt = length(dFdy(self.t)) let tex_size = self.msdf_texture.size() let scale = (dxt + dyt) * tex_size.x * 0.5 let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y) let ps = clamp(self.t.xy - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel) let c = self.get_color() let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a) } return main } mod.widgets.DropDownFlat.draw_text.pixel = fn() { let main = self.sample_text_pixel() if self.texture_index > 1.5 && self.texture_index < 2.5 { let dxt = length(dFdx(self.t)) let dyt = length(dFdy(self.t)) let tex_size = self.msdf_texture.size() let scale = (dxt + dyt) * tex_size.x * 0.5 let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y) let ps = clamp(self.t.xy - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel) let c = self.get_color() let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a) } return main } mod.widgets.SegmentedControlFlat.draw_text.pixel = fn() { let main = self.sample_text_pixel() if self.texture_index > 1.5 && self.texture_index < 2.5 { let dxt = length(dFdx(self.t)) let dyt = length(dFdy(self.t)) let tex_size = self.msdf_texture.size() let scale = (dxt + dyt) * tex_size.x * 0.5 let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y) let ps = clamp(self.t.xy - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel) let c = self.get_color() let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a) } return main } mod.widgets.SegmentedControlFlat.draw_text_selected.pixel = fn() { let main = self.sample_text_pixel() if self.texture_index > 1.5 && self.texture_index < 2.5 { let dxt = length(dFdx(self.t)) let dyt = length(dFdy(self.t)) let tex_size = self.msdf_texture.size() let scale = (dxt + dyt) * tex_size.x * 0.5 let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y) let ex = dFdx(self.t.xy) * 1.5 let ey = dFdy(self.t.xy) * 1.5 let k = vec4(0.0, 0.0, 0.0, 1.0) var g = self.msdf(scale, clamp(self.t.xy + ex, self.t_min + half_texel, self.t_max - half_texel), k) g = g + self.msdf(scale, clamp(self.t.xy - ex, self.t_min + half_texel, self.t_max - half_texel), k) g = g + self.msdf(scale, clamp(self.t.xy + ey, self.t_min + half_texel, self.t_max - half_texel), k) g = g + self.msdf(scale, clamp(self.t.xy - ey, self.t_min + half_texel, self.t_max - half_texel), k) let c = self.get_color() let a = clamp(g * 0.25, 0.0, 1.0) * 0.4 * c.a return main + vec4(0.22 * a, 0.38 * a, 0.56 * a, a) * (1.0 - main.a) } return main } mod.widgets.Tabs.draw_text.pixel = fn() { let main = self.sample_text_pixel() if self.texture_index > 1.5 && self.texture_index < 2.5 { let dxt = length(dFdx(self.t)) let dyt = length(dFdy(self.t)) let tex_size = self.msdf_texture.size() let scale = (dxt + dyt) * tex_size.x * 0.5 let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y) let ps = clamp(self.t.xy - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel) let c = self.get_color() let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a) } return main } mod.widgets.Tabs.draw_text_active.pixel = fn() { let main = self.sample_text_pixel() if self.texture_index > 1.5 && self.texture_index < 2.5 { let dxt = length(dFdx(self.t)) let dyt = length(dFdy(self.t)) let tex_size = self.msdf_texture.size() let scale = (dxt + dyt) * tex_size.x * 0.5 let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y) let ex = dFdx(self.t.xy) * 1.5 let ey = dFdy(self.t.xy) * 1.5 let k = vec4(0.0, 0.0, 0.0, 1.0) var g = self.msdf(scale, clamp(self.t.xy + ex, self.t_min + half_texel, self.t_max - half_texel), k) g = g + self.msdf(scale, clamp(self.t.xy - ex, self.t_min + half_texel, self.t_max - half_texel), k) g = g + self.msdf(scale, clamp(self.t.xy + ey, self.t_min + half_texel, self.t_max - half_texel), k) g = g + self.msdf(scale, clamp(self.t.xy - ey, self.t_min + half_texel, self.t_max - half_texel), k) let c = self.get_color() let a = clamp(g * 0.25, 0.0, 1.0) * 0.4 * c.a return main + vec4(0.22 * a, 0.38 * a, 0.56 * a, a) * (1.0 - main.a) } return main } mod.widgets.SliderMinimal.draw_text.pixel = fn() { let main = self.sample_text_pixel() if self.texture_index > 1.5 && self.texture_index < 2.5 { let dxt = length(dFdx(self.t)) let dyt = length(dFdy(self.t)) let tex_size = self.msdf_texture.size() let scale = (dxt + dyt) * tex_size.x * 0.5 let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y) let ps = clamp(self.t.xy - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel) let c = self.get_color() let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a) } return main } // ---------------------------------------------------------------- field row // A ROW OF FIELDS IS ONE HEIGHT. Left to themselves the library's fields each // come out at a size of their own -- in the widget catalogue's toolbar the // search box, the number beside it and the drop down after it stood at three // different heights -- and a sheet that reshapes the text box alone widens // that gap instead of closing it. So the box metrics are stated ONCE here and // handed to each field below, and a field added to the library later joins // the row by being named here rather than by having these numbers copied // into it. // Fields and buttons share the one 24 point row. let field_height = 24.0 let field_pad_v = 3.0 let field_radius = 1.0 // 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 = gl_px(self.draw_pass.dpi_factor) let p = self.pos * self.rect_size let y0 = gl_snap(self.rect_size.y * 0.5 - px, self.rect_pos.y, px) let dark = step(y0, p.y) * step(p.y, y0 + px) let light = step(y0 + px, p.y) * step(p.y, y0 + px * 2.0) return vec4(0.039, 0.039, 0.047, 1.0) * dark + vec4(0.271, 0.278, 0.310, 1.0) * light } mod.widgets.Vr.draw_bg.pixel = fn() { let px = gl_px(self.draw_pass.dpi_factor) let p = self.pos * self.rect_size let x0 = gl_snap(self.rect_size.x * 0.5 - px, self.rect_pos.x, px) let dark = step(x0, p.x) * step(p.x, x0 + px) let light = step(x0 + px, p.x) * step(p.x, x0 + px * 2.0) return vec4(0.039, 0.039, 0.047, 1.0) * dark + vec4(0.271, 0.278, 0.310, 1.0) * light } // ---------------------------------------------------------------- ground // Plain views stop clipping so a shadow can fall past their edge; scroll // views keep clipping their content. mod.widgets.View.clip_x = false mod.widgets.View.clip_y = false mod.widgets.Window.pass.clear_color = #393b44 // The window's own ground: #393b44 with the family's grain, monochrome, one // value per device pixel, sd 0.008, peaks inside +/-0.02. The panels carry // the same grain in the same window space, so it runs on under them. mod.widgets.Window.show_bg = true mod.widgets.Window.draw_bg.pixel = fn() { let px = gl_px(self.draw_pass.dpi_factor) let p = self.pos * self.rect_size let g = gl_grain(p, self.rect_pos, px) return vec4(vec3(0.224, 0.231, 0.267) + vec3(g, g, g), 1.0) } // ---------------------------------------------------------------- badge // A count is a small seated dark cap, never a light pill: face #2c2f37, // the black outline, the one top line, ink #d4d7de. mod.widgets.BadgePalette.neutral = #2c2f37 mod.widgets.BadgePalette.on_neutral = #d4d7de mod.widgets.BadgeFlat.shape = mod.widgets.BadgeShape.Rounded mod.widgets.BadgeFlat.radius = 2.0 mod.widgets.BadgeFlat.draw_bg.pixel = fn() { let px = gl_px(self.draw_pass.dpi_factor) let o = self.rect_pos let p = self.pos * self.rect_size // The box's edges rounded DOWN to a device pixel, so the bottom and // right outline rows stay inside the quad. let lo = vec2(gl_snap(0.0, o.x, px), gl_snap(0.0, o.y, px)) let hi = vec2(gl_snap(self.rect_size.x - px * 0.49, o.x, px), gl_snap(self.rect_size.y - px * 0.49, o.y, px)) let c = (lo + hi) * 0.5 let h = (hi - lo) * 0.5 let r = min(2.0, min(h.x, h.y)) let d = Material.sd_box(p, c, h, r) let t = clamp((p.y - lo.y) / max(hi.y - lo.y, 1.0), 0.0, 1.0) var col = self.color.rgb + vec3(0.012, 0.012, 0.014) * (1.0 - t) col = mix(col, vec3(0.416, 0.424, 0.455), Finish.band(d, px, px * 2.0, px) * step(p.y, lo.y + r + px * 2.0) * step(lo.x + r * 0.5, p.x) * step(p.x, hi.x - r * 0.5) * self.color.a) col = mix(col, vec3(0.039, 0.039, 0.047), Finish.band(d, 0.0, px, px)) let fa = max(self.color.a, Finish.band(d, 0.0, px, px)) let a = Finish.cover(d, px) * fa return vec4(col * a, a) } // ---------------------------------------------------------------- states // Every held state is written as a plain value that cuts in at once (the // library writes some as snap() values, which a state held as a default -- // the states page -- never applies). Live, the cut is the same as the snap. mod.widgets.ButtonFlat.animator.hover.on.from = {all: Snap} mod.widgets.ButtonFlat.animator.hover.on.apply = {draw_bg: {down: 0.0 hover: 1.0} draw_text: {down: 0.0 hover: 1.0}} mod.widgets.ButtonFlat.animator.hover.down.from = {all: Snap} mod.widgets.ButtonFlat.animator.hover.down.apply = {draw_bg: {down: 1.0 hover: 1.0} draw_text: {down: 1.0 hover: 1.0}} mod.widgets.CheckBoxFlat.animator.hover.down.from = {all: Snap} mod.widgets.CheckBoxFlat.animator.hover.down.apply = {draw_bg: {down: 1.0 hover: 1.0} draw_text: {down: 1.0 hover: 1.0}} mod.widgets.RadioButtonFlat.animator.hover.down.from = {all: Snap} mod.widgets.RadioButtonFlat.animator.hover.down.apply = {draw_bg: {down: 1.0 hover: 1.0} draw_text: {down: 1.0 hover: 1.0}} mod.widgets.TextInputFlat.animator.hover.on.from = {all: Snap} mod.widgets.TextInputFlat.animator.hover.on.apply = {draw_bg: {down: 0.0 hover: 1.0} draw_text: {down: 0.0 hover: 1.0}} mod.widgets.TextInputFlat.animator.hover.down.from = {all: Snap} mod.widgets.TextInputFlat.animator.hover.down.apply = {draw_bg: {down: 1.0 hover: 1.0} draw_text: {down: 1.0 hover: 1.0}} mod.widgets.DropDownFlat.animator.hover.on.from = {all: Snap} mod.widgets.DropDownFlat.animator.hover.on.apply = {draw_bg: {down: 0.0 hover: 1.0} draw_text: {down: 0.0 hover: 1.0}} mod.widgets.DropDownFlat.animator.hover.down.from = {all: Snap} mod.widgets.DropDownFlat.animator.hover.down.apply = {draw_bg: {down: 1.0 hover: 1.0} draw_text: {down: 1.0 hover: 1.0}} // ---------------------------------------------------------------- band // THE BAND is the sheet's own slider with two thumbs: the same track, the // same lit value and the same thumb as the Slider, the value lit between // the two thumbs. The widget's grab geometry is set to the thumbs' own, so // what is drawn is what is dragged. mod.widgets.RangeSliderFlat.clip_x = false mod.widgets.RangeSliderFlat.clip_y = false mod.widgets.RangeSliderFlat.track_inset = 0.0 mod.widgets.RangeSliderFlat.handle_size = 14.0 mod.widgets.RangeSliderFlat.draw_bg.vertex = fn() { let m = 6.0 self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m)) } mod.widgets.RangeSliderFlat.draw_bg.pixel = fn() { let m = 6.0 let px = gl_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 = 7.0 let cy = gl_snap(self.label_px + (self.rect_size.y - self.label_px) * 0.5, o.y, px) let lo = vec2(gl_snap(0.0, o.x, px), cy - 2.0) let hi = vec2(gl_snap(self.rect_size.x, o.x, px), cy + 2.0) var res = gl_well(p, lo, hi, 2.0, px, vec3(0.141, 0.149, 0.176), 0.0, self.disabled, 1.0) let span = self.rect_size.x - rad * 2.0 let h0 = rad + self.start_pos * span let h1 = rad + self.end_pos * span res = gl_fill(res, p, vec2(h0, lo.y + px), vec2(h1, hi.y - px), vec2(lo.x + px, hi.x - px), px, self.disabled) res = gl_handle(res, p, vec2(h0, cy), rad, px, self.hot_start, max(self.drag * self.hot_start, self.focus), self.disabled) return gl_handle(res, p, vec2(h1, cy), rad, px, self.hot_end, max(self.drag * self.hot_end, self.focus), self.disabled) } // ---------------------------------------------------------------- switches and meter // THE SWITCHES are the sheet's own toggle, in proportion with the // labels and the controls beside them: the rocker and the slide switch // take the toggle's face, its size and shape, its quad and its label // gap (the library's own faces for them are flat boxes sized for // another face). The rocker turns on a click; the slide switch follows a // drag, as the toggle does. mod.widgets.ToggleRocker.draw_bg.size = uniform(18.0) mod.widgets.ToggleSlide.draw_bg.size = uniform(18.0) mod.widgets.ToggleRocker.draw_bg.pill_aspect = uniform(1.89) mod.widgets.ToggleSlide.draw_bg.pill_aspect = uniform(1.89) mod.widgets.ToggleRocker.label_walk.margin.left = 34.0 mod.widgets.ToggleSlide.label_walk.margin.left = 34.0 mod.widgets.ToggleRocker.draw_bg.pixel = mod.widgets.ToggleFlat.draw_bg.pixel mod.widgets.ToggleSlide.draw_bg.pixel = mod.widgets.ToggleFlat.draw_bg.pixel // THE METER sits in the same window as the readouts and the screen: the // library's display housing drawn round its face -- the bezel ring, a shade // of whatever ground it is set in with a device pixel of dark at its edge; // the face with the surround's shadow falling across it from the top and a // device pixel of black where the glass meets the bezel; the glass's quiet // diagonal band and the light line inside its top edge; the lip of light on // the ground under it -- with the same corner, so the meter and the // readouts read as one family. The scale, the red zone, the needle and its // pivot are the library's own. mod.widgets.NeedleMeter.draw_bg.border_radius = uniform(theme.corner_radius * 0.5) mod.widgets.NeedleMeter.draw_bg.color_bezel = uniform(theme.color_d_05) mod.widgets.NeedleMeter.draw_bg.color_lip = uniform(theme.color_u_15) mod.widgets.NeedleMeter.draw_bg.pixel = fn() { let p = self.pos * self.rect_size let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let lo = vec2(Finish.snap(self.rect_pos.x, px), Finish.snap(self.rect_pos.y, px)) - self.rect_pos let hi = vec2(Finish.snap(self.rect_pos.x + self.rect_size.x, px), Finish.snap(self.rect_pos.y + self.rect_size.y, px) - px) - self.rect_pos let c = (lo + hi) * 0.5 let ho = (hi - lo) * 0.5 let r_out = min(self.border_radius, min(ho.x, ho.y)) let outer = Material.sd_box(p, c, ho, r_out) let b = Finish.snap(2.0, px) let hf = max(ho - vec2(b, b), vec2(0.5, 0.5)) let rf = max(r_out - b, 0.0) let face_d = Material.sd_box(p, c, hf, rf) let pv = self.pivot() let rr = self.scale_radius() let q = p - pv let r = length(q) let ang = atan2(q.x, 0.0 - q.y) let sweep = self.sweep * 0.017453293 let t = ang / sweep + 0.5 let on_scale = step(-0.0001, t) * step(t, 1.0001) var face = self.color.rgb // The scale: a hairline arc and a tick at every division. let arc = Finish.cover(abs(r - rr) - px * 0.5, px) * on_scale let n = max(floor(self.majors + 0.5), 1.0) * max(floor(self.minors + 0.5), 1.0) let k = floor(clamp(t, 0.0, 1.0) * n + 0.5) let ak = (k / n - 0.5) * sweep let perp = abs(q.x * cos(ak) + q.y * sin(ak)) let mn = max(floor(self.minors + 0.5), 1.0) let major = 1.0 - step(0.5, k - mn * floor(k / mn + 0.0001)) let len = mix(self.tick_minor, self.tick_major, major) let radial = max(rr - len - r, r - rr) let tick = Finish.cover(perp - px * mix(0.5, 1.0, major), px) * Finish.cover(radial, px) * step(0.0, 0.0 - q.y * cos(ak) + q.x * sin(ak)) face = mix(face, self.color_scale.rgb, max(arc, tick)) if self.zone < 0.999 { let zone_t = Finish.cover((self.zone - t) * sweep * r, px) * Finish.cover((t - 1.0) * sweep * r, px) let zone_r = Finish.cover(max(rr + px - r, r - rr - 2.5), px) face = mix(face, self.color_zone.rgb, zone_t * zone_r) } // The needle, from under the pivot cover to just past the scale. let na = self.needle_angle(self.value) let nd = vec2(sin(na), 0.0 - cos(na)) let along = dot(q, nd) let across = abs(q.x * nd.y - q.y * nd.x) let needle = Finish.cover(across - px * 0.5, px) * Finish.cover(along - (rr + 3.0), px) * Finish.cover(0.0 - along - max(self.pivot_size - px, 0.0), px) face = mix(face, self.color_needle.rgb, needle) let pd = r - self.pivot_size face = mix(face, mix(self.color_needle.rgb, self.color.rgb, 0.35), Finish.cover(pd, px)) face = mix(face, face * 0.55, Finish.band(pd, 0.0, px, px)) // The housing: the surround's shadow across the face from the top, the // black device pixel at the glass's edge, and the glass over it all. let insh = Finish.inset(p, c, hf, rf, vec2(0.0, 2.0), 1.8) face = face * (1.0 - 0.55 * insh) face = mix(face, face * 0.4, Finish.band(face_d, 0.0, px, px)) let u = (p - (c - hf)) / max(hf * 2.0, vec2(1.0, 1.0)) let diag = u.x + u.y * 0.6 let band = smoothstep(0.0, 0.12, diag) * (1.0 - smoothstep(0.28, 0.5, diag)) let top = Finish.cover(abs(p.y - (c.y - hf.y) - px * 1.5) - px * 0.5, px) * 0.6 let ga = clamp(0.03 * (band + top), 0.0, 1.0) * Finish.cover(face_d + px, px) face = face * (1.0 - ga) + vec3(ga, ga, ga) let bz = self.color_bezel var col = vec4(bz.rgb * bz.a, bz.a) col = Finish.over(col, vec4(0.0, 0.0, 0.0, 0.45 * Finish.band(outer, 0.0, px, px))) col = mix(col, vec4(face, 1.0), Finish.cover(face_d, px)) col = col * Finish.cover(outer, px) let lip = Finish.ring_out(outer, 0.0, px, px) * step(hi.y, p.y) let la = self.color_lip.a * lip return Finish.over(vec4(self.color_lip.rgb * la, la), col) * self.color.a * self.opacity } // ---------------------------------------------------------------- lamps // LAMPS: an LED, as the hardware in his pictures has them. The emitter is // the light: at full current its middle burns toward white and its rim is // its most saturated colour; at lower current the middle is no longer // white, the colour deeper, the glow smaller; out, it is dark tinted glass, // its colour barely there. Round it a bezel: a raised ring lit from the // upper left, or a socket cut into the surface, its upper wall in shadow // and a catch light on its lower rim. Over the lens one crisp highlight of // a dome. Its light is added, never painted: a tight hot halo and a short // bloom in its own hue, screened over whatever is there and gone in about // two radii; on a pale ground, where light can barely brighten, its hue is // also laid round it as a tint that keeps the ground's luminance (the // library's halo rule: at most 0.45 one device pixel out, halving every // 0.4 of its reach, gone at the reach), so it never darkens or greys its // ground. A bar is the same light drawn out into a light pipe. let gl_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 gl_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 gl_led_lamp_hue = fn(ink: vec3, vivid: float) -> vec3 { let f = gl_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 gl_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 = gl_led_lamp_full(ink) let hue = gl_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 = gl_led_lamp_add(res, mix(hue, white, 0.15 * pw) * halo * pw * hwin * win, ground) } // THE BEAD: a glossy dome of coloured glass. Its colour deepens toward // the top; light pools low inside, where it comes through the dome; lit, // its middle burns toward white at full current and its rim is its most // saturated colour. let top = smoothstep(-1.0, 0.6, q.y) let pool = exp(-(q.y - 0.55) * (q.y - 0.55) / 0.09 - q.x * q.x / 0.30) var on = mix(hue * mix(0.55, 0.8, L), mix(b, hue, 0.5), top) on = mix(on, min(mix(b, hue, 0.4) + vec3(0.35, 0.35, 0.35), white), pool * 0.5) on = mix(on, vec3(1.0, 0.99, 0.975), exp(-t * t / 0.20) * smoothstep(0.5, 1.0, L) * 0.85) on = mix(on, hue * (0.82 + 0.18 * L), smoothstep(0.62, 1.0, t)) on = on * (0.42 + 0.58 * L) // Out: dark tinted glass on a dark ground, a grey bead on a pale one, // its colour barely there, the same dome, a catch light along its lower // rim. let offc = mix(mix(vec3(0.085, 0.09, 0.115), ground * 0.62, pale), b * 0.4, 0.12) var off = mix(offc * 0.62, offc * 1.08, top) off = mix(off, min(offc + vec3(0.18, 0.18, 0.18), white), pool * 0.35) off = off * mix(1.0, 0.75, smoothstep(0.75, 1.0, t)) let catchl = smoothstep(0.62, 0.9, t) * (1.0 - smoothstep(0.9, 1.0, t)) * smoothstep(0.2, 0.9, nrm.y) off = off + vec3(0.30, 0.30, 0.32) * catchl * (1.0 - 0.5 * pale) var col = mix(off, min(on, white), smoothstep(0.0, 0.35, L)) // One crisp highlight: a white oval across its upper third. let e = length(vec2(q.x / 0.62, (q.y + 0.46) / 0.30)) let spec = (1.0 - smoothstep(0.55, 1.0, e)) * mix(1.0, 0.3, clamp((q.y + 0.76) / 0.6, 0.0, 1.0)) col = mix(col, white, spec * mix(0.8, 0.55, L) * smoothstep(0.9, 2.2, rt)) // Out, a faint seam where the dome meets its bezel; lit, none: the rim // is the colour. col = mix(col, col * 0.7, Finish.band(d, 0.0, px * 0.6, px) * 0.6 * (1.0 - smoothstep(0.0, 0.25, L))) col = min(col, white) let a_l = Finish.cover(d, px) return Finish.over(res, vec4(col * a_l, a_l)) } // The Lamp widget and the bar lamp: the lamp above, here in a glossy dark ring with its own soft shadow. // The halo's room is held in the lamp's own box (the library reserves a // lens thickness either side once a halo is asked for), so the light is // never cut at its edge. mod.widgets.Lamp.halo = 0.3 mod.widgets.Lamp.halo_reach = 1.0 mod.widgets.Lamp.draw_bg.vertex = fn() { let m = 2.0 self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m)) } mod.widgets.Lamp.draw_bg.pixel = fn() { let m = 2.0 let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let reach = clamp(self.reach, 0.0, 1.0) let t = self.rect_size.y / (1.0 + 2.0 * reach) let room = t * reach let c = self.rect_size * 0.5 let half_len = max((self.rect_size.x - 2.0 * room) * 0.5, t * 0.5) // The lens is the lamp's size; its socket stands round it in the room. let hb = vec2(mix(t * 0.5, half_len, self.bar), t * 0.5) let res = gl_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.224, 0.231, 0.267), max(room - 0.5, 2.0), vec3(0.20, 0.21, 0.245), 1.0) return res * self.opacity } true