use mod.prelude.widgets_internal.* use mod.sdf.Cap // Bling: white Carrara marble set in polished gold, after the "marble / bling // light" pictures. Every face is drawn in true points (one device pixel is // `px`), and every shadow and light falls OUTSIDE the layout box: the quad // is grown in `vertex`, the templates that throw a shadow stop clipping, and // no padding is spent on either. Three materials make every control: // MARBLE, a white stone with soft grey veins that meander, part and rejoin // at several scales (broad soft clouds, veins of varied width, a few fine // dark threads), calm under the content and stronger toward the window's // frame; GOLD, polished, a round bead that catches a bright specular line on // the side toward the light (from the top, a little from the left) and a // warm darker band on the side away from it; and GEMS, cut stones with flat // facets, a deep coloured interior and a few crisp star glints, set in gold. // Lamps are LEDs as gems in gold settings. Small gold scrolls stand only in // the corners of the surfaces and of the window, never over words. // ---------------------------------------------------------------- helpers // One device pixel in points. let mb_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 mb_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 mb_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) } // Which way the outline of a rounded box faces at p. let mb_normal = fn(p: vec2, c: vec2, h: vec2, r: float) -> vec2 { 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 } return n } // Anything drawn in a grown quad fades out over its last points, so a soft // shadow never ends on a hard edge where the quad stops. let mb_fade = fn(pos: vec2, size: vec2, m: float) -> float { let q = pos * (size + vec2(2.0 * m, 2.0 * m)) let e = min(min(q.x, q.y), min(size.x + 2.0 * m - q.x, size.y + 2.0 * m - q.y)) return smoothstep(0.0, 3.0, e) } // A seed from a box's size, so two faces of different sizes show different // stone (never the position: a face that scrolls must not change). let mb_seed = fn(size: vec2) -> vec2 { return vec2(fract(size.x * 0.1373) * 997.0 + size.y * 3.7, fract(size.y * 0.2113) * 613.0 + size.x * 1.3) } // ---------------------------------------------------------------- palette // The stone's mean white (#efeee9), for a face that fades to it. let mb_ground = fn() -> vec3 { return vec3(0.937, 0.933, 0.914) } // The warm grey-brown every shadow is cast in; laid at a coverage. let mb_shade = fn() -> vec3 { return vec3(0.25, 0.20, 0.13) } // The ink, dark grey-brown (#3a2e1c). let mb_ink = fn() -> vec3 { return vec3(0.227, 0.180, 0.110) } // The key light in the screen plane, toward the light: from the top, a // little from the left. let mb_lv = fn() -> vec2 { return vec2(-0.419, -0.908) } // The stones, measured on the pictures: emerald (#079846 lit, #0b5028 in // the deep), ruby, a golden citrine, sapphire, amethyst, a white diamond, // and the two smoky quartzes of the second and third buttons. let mb_emerald = fn() -> vec3 { return vec3(0.03, 0.56, 0.27) } let mb_ruby = fn() -> vec3 { return vec3(0.76, 0.06, 0.16) } let mb_citrine = fn() -> vec3 { return vec3(0.88, 0.55, 0.12) } // The citrine as a cut stone shows it: its body a golden yellow (what the // gem renderer absorbs to). let mb_citrine_gem = fn() -> vec3 { return vec3(0.98, 0.78, 0.16) } let mb_sapphire = fn() -> vec3 { return vec3(0.14, 0.32, 0.86) } let mb_amethyst = fn() -> vec3 { return vec3(0.56, 0.26, 0.82) } let mb_diamond = fn() -> vec3 { return vec3(0.86, 0.875, 0.905) } let mb_champagne = fn() -> vec3 { return vec3(0.66, 0.53, 0.37) } let mb_smoky = fn() -> vec3 { return vec3(0.40, 0.36, 0.30) } // ---------------------------------------------------------------- gold // Polished gold by the light it catches, 0..1, measured on the pictures' // bars: the deep brown-gold of a face turned away (#2e2008), the warm darker // band (#5a4214), the shade (#8a6a2c), the body (#b8964e), the bright // (#e2c886) and the specular white-gold (#fff4cc). let mb_gold = fn(l: float) -> vec3 { let x = clamp(l, 0.0, 1.0) var c = mix(vec3(0.180, 0.125, 0.031), vec3(0.353, 0.259, 0.078), smoothstep(0.0, 0.2, x)) c = mix(c, vec3(0.541, 0.416, 0.173), smoothstep(0.2, 0.4, x)) c = mix(c, vec3(0.722, 0.588, 0.306), smoothstep(0.4, 0.6, x)) c = mix(c, vec3(0.886, 0.784, 0.525), smoothstep(0.6, 0.8, x)) return mix(c, vec3(1.0, 0.957, 0.800), smoothstep(0.8, 1.0, x)) } // What polished metal reflects, by how far a face is turned toward the key // light (`e` -1..1, 0 facing the eye): the body flat on, a warm darker band // just past flat on the side away, a narrow bright specular strip on the // side toward it. let mb_env = fn(e: float) -> float { let x = clamp(e, -1.0, 1.0) var l = 0.5 + 0.30 * x l = l - 0.24 * exp(-(x + 0.42) * (x + 0.42) / 0.035) l = l + 0.48 * exp(-(x - 0.55) * (x - 0.55) / 0.018) return clamp(l, 0.0, 1.0) } // A round bead of gold: `u` 0 at its outer edge to 1 at its inner, `n` the // way its outline faces. Its outer half faces out and its inner half in: // the side toward the light carries the bright specular line, the side // away the warm dark band; its crest always holds a polished streak, and // both its edges roll away dark, so a wide rim reads chunky. let mb_bead = fn(u: float, n: vec2) -> float { let t = cos(clamp(u, 0.0, 1.0) * PI) var l = mb_env(t * dot(n, mb_lv()) * 0.95) l = l + 0.12 * exp(-t * t / 0.05) return l - 0.20 * smoothstep(0.80, 1.0, abs(t)) } // A gold rim `w` points wide inside the outline at distance `d` (negative // inside), its light varied a little along its run so a long rim is never // one flat colour. let mb_rim = fn(d: float, w: float, n: vec2, p: vec2) -> vec3 { let u = clamp(-d / max(w, 0.01), 0.0, 1.0) let l = mb_bead(u, n) + 0.06 * (Finish.noise(p / 6.0) - 0.5) return mb_gold(l) } // A DOUBLE RIM `w` points wide: a broad outer bead (0.6 of it), a dark // groove, and a slimmer inner bead, as the pictures' field frames. let mb_rim2 = fn(d: float, w: float, n: vec2, p: vec2, px: float) -> vec3 { let wo = w * 0.60 let wg = max(w * 0.12, px) let wi = max(w - wo - wg, 0.3) let jit = 0.06 * (Finish.noise(p / 6.0) - 0.5) let co = mb_gold(mb_bead(clamp(-d / wo, 0.0, 1.0), n) + jit) let ci = mb_gold(mb_bead(clamp((-d - wo - wg) / wi, 0.0, 1.0), n) + jit) var col = mix(co, mb_gold(0.06), Finish.cover(d + wo, px)) return mix(col, ci, Finish.cover(d + wo + wg, px)) } // A gold face: a polished bar, flat in the middle with a soft reflection of // a bright window across its upper third, its border `bw` rolling over to // the outline (`d` its distance, `n` the way it faces). let mb_goldface = fn(p: vec2, c: vec2, h: vec2, d: float, n: vec2, bw: float) -> vec3 { let y = (p.y - c.y) / max(h.y, 1.0) let edge = 1.0 - smoothstep(0.0, bw, -d) var e = -0.16 * y e = mix(e, dot(n, mb_lv()) * 0.85, edge) var l = mb_env(e) l = l + 0.11 * exp(-(y + 0.42) * (y + 0.42) / 0.04) * (1.0 - edge) l = l + 0.04 * (Finish.noise(p / vec2(14.0, 3.0)) - 0.5) return mb_gold(l) } // One gold wire of an ornament: its coverage and light from the signed // distance `e` across its middle, `hw` half its width, `n` the way +e faces // and `lv` the light in the wire's own frame. let mb_wire = fn(e: float, hw: float, n: vec2, lv: vec2, px: float) -> vec2 { let a = Finish.cover(abs(e) - hw, px) let t = clamp(e / max(hw, 0.01), -1.0, 1.0) return vec2(a, mb_env(t * dot(n, lv) * 0.95)) } let mb_wire_max = fn(a: vec2, b: vec2) -> vec2 { if b.x > a.x { return b } return a } // A spiral wire about `c`, `pitch` apart, from radius `r0` to `r1`. let mb_curl = fn(m: vec2, c: vec2, pitch: float, r0: float, r1: float, hw: float, lv: vec2, px: float) -> vec2 { let q = m - c let r = length(q) let ang = atan2(q.y, q.x) let e = (fract(r / pitch - ang / (2.0 * PI) + 0.5) - 0.5) * pitch let w = smoothstep(r0 - 0.3, r0 + 0.5, r) * (1.0 - smoothstep(r1 - 0.6, r1 + 0.2, r)) let g = mb_wire(e, hw, q / max(r, 0.01), lv, px) return vec2(g.x * w, g.y) } // A vine wire along an edge: a wave `amp` either side of `y0`, a wavelength // `wl` long, from `x0` to `x1`, thinning and calming as it runs. let mb_vine = fn(m: vec2, x0: float, x1: float, y0: float, amp: float, wl: float, hw: float, lv: vec2, px: float) -> vec2 { let k = 2.0 * PI / wl let run = clamp((m.x - x0) / max(x1 - x0, 1.0), 0.0, 1.0) let fade = smoothstep(x0, x0 + 1.0, m.x) * (1.0 - smoothstep(x1 - 2.0, x1, m.x)) let A = amp * (1.0 - 0.5 * run) let yc = y0 + A * sin((m.x - x0) * k) let dy = A * k * cos((m.x - x0) * k) let nl = sqrt(1.0 + dy * dy) let g = mb_wire((m.y - yc) / nl, hw * mix(1.0, 0.65, run), vec2(-dy, 1.0) / nl, lv, px) return vec2(g.x * fade, g.y) } // The scroll of one corner at scale `s`, in corner space (`m` points along // the edges from the corner; folded about the diagonal, so both edges carry // the same vine): a volute round the corner's eye (where a small stone is // set), a vine along each edge with a curl hanging from each crest and a // bud closing it, a short arc across the corner. `lv` the light in corner // space. All of it within nine points of the edges and the corner. let mb_corner = fn(m0: vec2, s: float, lv0: vec2, px: float) -> vec2 { var m = m0 var lv = lv0 if m.y > m.x { m = vec2(m0.y, m0.x) lv = vec2(lv0.y, lv0.x) } var g = mb_curl(m, vec2(7.0, 7.0) * s, 1.45 * s, 2.0 * s, 4.4 * s, 0.48 * s, lv, px) g = mb_wire_max(g, mb_vine(m, 11.0 * s, 40.0 * s, 5.9 * s, 1.3 * s, 9.0 * s, 0.50 * s, lv, px)) g = mb_wire_max(g, mb_curl(m, vec2(15.6 * s, 8.3 * s), 0.85 * s, 0.25 * s, 1.95 * s, 0.36 * s, lv, px)) g = mb_wire_max(g, mb_curl(m, vec2(24.6 * s, 4.3 * s), 0.75 * s, 0.25 * s, 1.5 * s, 0.32 * s, lv, px)) g = mb_wire_max(g, mb_curl(m, vec2(33.6 * s, 7.4 * s), 0.65 * s, 0.2 * s, 1.3 * s, 0.30 * s, lv, px)) // A bud at the vine's end. let bd = length(m - vec2(41.0 * s, 5.9 * s)) - 0.95 * s let bn = (m - vec2(41.0 * s, 5.9 * s)) / max(length(m - vec2(41.0 * s, 5.9 * s)), 0.01) g = mb_wire_max(g, vec2(Finish.cover(bd, px), mb_env(dot(bn, lv) * 0.8))) return g } // ---------------------------------------------------------------- marble // THE VEINS' FIELD: value noise laid along the veins' general drift `dv` // (a unit direction; the drift runs from the upper right down to the lower // left), `sa` points along and `sb` across, the coordinates first warped by // two more fields (`warp` how far), so its level lines wander, branch and // rejoin like the veins of a stone rather than running as parallel strokes. // Its value in .x and its gradient's size per point in .y, measured a point // either way: a vein's distance in points is its value over that. let mb_vf = fn(q: vec2, dv: vec2, sa: float, sb: float, warp: float, seed: vec2) -> float { var s = vec2(dot(q, dv) / sa, dot(q, vec2(-dv.y, dv.x)) / sb) + seed if warp > 0.001 { let w = vec2(Finish.fbm(s * 0.55 + vec2(4.3, 1.7)), Finish.fbm(s * 0.55 + vec2(9.1, 6.2))) - vec2(0.5, 0.5) s = s + w * warp } return Finish.fbm(s) } let mb_vfg = fn(q: vec2, dv: vec2, sa: float, sb: float, warp: float, seed: vec2) -> vec2 { let f = mb_vf(q, dv, sa, sb, warp, seed) let fx = mb_vf(q + vec2(1.0, 0.0), dv, sa, sb, warp, seed) let fy = mb_vf(q + vec2(0.0, 1.0), dv, sa, sb, warp, seed) return vec2(f, max(length(vec2(fx - f, fy - f)), 0.00015)) } // THE GROUND'S MARBLE at `q` (points in window space), white Carrara: // - a polished white with broad pale grey clouds drifting along the run; // - the main veins, a level line of the warped field: where the field is // flat they spread into a soft diffuse band, everywhere a darker inner // band round a soft core of varied width broken into dabs, fading in and // out along their run, grey with stretches warmed to an old gold; // - forks, where the next level runs close by and parts from it; // - a finer network that branches off them, and a few fine threads close // by them. // `k` the veins' strength: calm under the content, full at the frame. let mb_marble = fn(q: vec2, px: float, k: float) -> vec3 { let white = vec3(0.966, 0.963, 0.952) let dv = vec2(-0.62, 0.785) let cl = mb_vf(q, dv, 600.0, 260.0, 1.6, vec2(1.3, 8.2)) var col = mix(white, vec3(0.872, 0.868, 0.855), smoothstep(0.48, 0.82, cl) * (0.5 + 0.5 * k) * 0.6) let cl2 = mb_vf(q, dv, 180.0, 90.0, 1.2, vec2(6.1, 2.4)) col = mix(col, vec3(0.900, 0.896, 0.884), smoothstep(0.55, 0.85, cl2) * 0.4 * k) // The main veins. let n1 = mb_vfg(q, dv, 430.0, 190.0, 2.2, vec2(0.0, 0.0)) let v = abs(n1.x - 0.5) let d1 = v / n1.y let pres = smoothstep(0.25, 0.7, mb_vf(q, dv, 160.0, 90.0, 0.8, vec2(21.0, 4.0))) let tone = smoothstep(0.45, 0.75, Finish.noise(q / 380.0 + vec2(31.0, 7.0))) let dab = 0.6 + 0.4 * Finish.noise(q / 7.0 + vec2(3.0, 1.0)) let vc = mix(vec3(0.52, 0.51, 0.48), vec3(0.66, 0.55, 0.32), tone) col = mix(col, vec3(0.80, 0.795, 0.77), clamp(exp(-v / 0.020) * (0.25 + 0.75 * pres) * 0.6 * k, 0.0, 1.0)) col = mix(col, vec3(0.70, 0.685, 0.65), clamp(exp(-d1 / 3.5) * pres * 0.5 * k, 0.0, 1.0)) let wn = Finish.noise(q / 70.0 + vec2(1.7, 3.3)) let hw = 0.4 + 2.2 * smoothstep(0.3, 1.0, wn) * pres let core = (1.0 - smoothstep(hw - 0.5, hw + 1.2, d1)) * pres * dab col = mix(col, vc, clamp(core * 0.85 * k, 0.0, 1.0)) // A fork: the next level, close by, where the vein frays. let dc = abs(n1.x - 0.53) / n1.y let fray = smoothstep(0.5, 0.8, Finish.noise(q / 140.0 + vec2(8.0, 1.0))) * pres col = mix(col, vec3(0.66, 0.65, 0.62), clamp((1.0 - smoothstep(0.0, 1.1, dc)) * fray * 0.55 * k, 0.0, 1.0)) // The branches: a finer network shown off the main veins and in a few // patches of its own. let n2 = mb_vfg(q, vec2(-0.3, 0.95), 210.0, 130.0, 1.4, vec2(7.3, 2.9)) let d2 = abs(n2.x - 0.5) / n2.y let near = exp(-d1 / 40.0) * (0.3 + 0.7 * pres) var m2 = clamp(near + smoothstep(0.66, 0.9, Finish.noise(q / 300.0 + vec2(11.0, 5.0))) * 0.5, 0.0, 1.0) m2 = m2 * smoothstep(0.2, 0.6, mb_vf(q, dv, 90.0, 60.0, 0.6, vec2(2.0, 9.0))) // Where a field is nearly flat its level lines close into small rings, // which no marble has: a branch or a thread fades out there. m2 = m2 * smoothstep(0.35 / 130.0, 0.9 / 130.0, n2.y) col = mix(col, vec3(0.80, 0.795, 0.77), clamp(exp(-abs(n2.x - 0.5) / 0.015) * m2 * 0.35 * k, 0.0, 1.0)) col = mix(col, vec3(0.60, 0.59, 0.56), clamp((1.0 - smoothstep(-0.2, 1.0, d2 - 0.25)) * m2 * 0.55 * k * dab, 0.0, 1.0)) // The fine threads, close by the veins. let n3 = mb_vfg(q, dv, 90.0, 60.0, 0.9, vec2(3.1, 17.7)) let d3 = abs(n3.x - 0.5) / n3.y let m3 = clamp(exp(-d1 / 14.0) * 0.8 * pres, 0.0, 1.0) * smoothstep(0.3, 0.6, Finish.noise(q / 40.0 + vec2(5.0, 2.0))) * smoothstep(0.4 / 60.0, 1.0 / 60.0, n3.y) col = mix(col, vec3(0.50, 0.49, 0.46), Finish.cover(d3 - 0.2, px) * m3 * 0.5 * k) // The polish: a crystalline grain, a device pixel fine. let g = Finish.grain(q / px, 0.006) return col + vec3(g, g, g) } // THE STONE OF A FACE: the same marble, lighter work for a small face: a // soft cloud, and now and then a soft vein with its diffuse band, fading // in and out; `k` its strength, kept low under the words a face carries. let mb_stone = fn(q: vec2, px: float, k: float) -> vec3 { let white = vec3(0.968, 0.965, 0.955) let dv = vec2(-0.62, 0.785) let cl = mb_vf(q, dv, 260.0, 120.0, 1.2, vec2(2.3, 7.1)) var col = mix(white, vec3(0.895, 0.890, 0.875), smoothstep(0.5, 0.85, cl) * 0.6 * k) let n = mb_vfg(q, dv, 300.0, 140.0, 1.6, vec2(4.1, 9.3)) let v = abs(n.x - 0.5) let d = v / n.y let pres = smoothstep(0.3, 0.7, Finish.noise(q / 90.0 + vec2(13.0, 2.0))) col = mix(col, vec3(0.83, 0.825, 0.80), clamp(exp(-v / 0.02) * 0.5 * k, 0.0, 1.0)) let hw = 0.3 + 1.0 * Finish.noise(q / 40.0 + vec2(5.1, 1.2)) col = mix(col, vec3(0.62, 0.605, 0.57), (1.0 - smoothstep(hw - 0.4, hw + 1.0, d)) * pres * 0.6 * k) let g = Finish.grain(q / px, 0.005) return col + vec3(g, g, g) } // ---------------------------------------------------------------- gems // A stone's colour by the light a facet sends back, 0..1: its deep // interior (the colour squared, darker), its body, a paler fire and white. let mb_gemcol = fn(ink: vec3, v: float) -> vec3 { let deep = ink * ink * 0.55 let bright = mix(ink, vec3(1.0, 1.0, 1.0), 0.5) var c = mix(deep, ink, smoothstep(0.0, 0.5, v)) c = mix(c, bright, smoothstep(0.5, 0.86, v)) return mix(c, vec3(1.0, 1.0, 1.0), smoothstep(0.88, 1.0, v)) } // A star glint: a hot white core and two crossed rays `s` points long, a // device pixel thin, and a fainter diagonal pair. let mb_glint = fn(q: vec2, s: float, px: float) -> float { let ax = abs(q.x) let ay = abs(q.y) let w = max(0.045 * s, 0.35 * px) let ray = exp(-ay / w) * max(1.0 - ax / s, 0.0) + exp(-ax / w) * max(1.0 - ay / s, 0.0) let dg = vec2(abs(q.x + q.y), abs(q.x - q.y)) * 0.7071 let diag = exp(-dg.x / w) * max(1.0 - dg.y / (0.45 * s), 0.0) + exp(-dg.y / w) * max(1.0 - dg.x / (0.45 * s), 0.0) let core = exp(-(ax * ax + ay * ay) / max(0.025 * s * s, px * px)) return clamp(ray * ray * 0.95 + diag * diag * 0.45 + core, 0.0, 1.0) } // A WHITE DIAMOND, a round brilliant seen from above, radius `R`, turned by // `spin`. Its facets are flat planes with straight edges, each ONE tone out // of four (near black, a grey-blue, a light grey, near white), set so that // neighbours jump between them, nudged by the key light and a little // chance: the regular octagonal table (about half the stone across), eight // star facets standing on its edges, eight kites from the table's corners // to the girdle, and sixteen upper girdle facets in pairs along the rim. // The table is one flat facet too, a clear glass a breath under white. A // few white facets flash a pale spectral colour (orange, blue or green); // every facet edge is a crisp hairline (light on a dark facet, grey-blue on // a light one) and the girdle a bright line. Laid out in one sector (an // eighth, its middle on a table edge's middle) folded about that middle, // so every edge is a straight line. A small stone keeps the table and eight // crown facets. let mb_dia_tone = fn(L: float) -> vec3 { var c = vec3(0.13, 0.14, 0.18) c = mix(c, vec3(0.44, 0.48, 0.57), step(0.25, L)) c = mix(c, vec3(0.79, 0.81, 0.86), step(0.5, L)) return mix(c, vec3(0.985, 0.99, 1.0), step(0.75, L)) } // How much a facet facing the screen direction `d` turns to the key light. let mb_facing = fn(d: float) -> float { return cos(d) * mb_lv().x + sin(d) * mb_lv().y } let mb_brilliant_white = fn(q: vec2, R: float, spin: float, px: float) -> vec3 { let st = PI / 4.0 let t2 = 0.41421356 let r = length(q) / max(R, 0.3) let a = atan2(q.y, q.x) - spin + 8.0 * PI let k = floor(a / st) let am = (a - k * st) - st * 0.5 let side = step(0.0, am) let par = k - 2.0 * floor(k * 0.5) let th = k * st + st * 0.5 + spin let x = r * cos(am) let y = abs(r * sin(am)) // The table's edge (x = tc, its corner at vy), the star's apex at sx, // the kite's foot on the girdle at (gx, gy). let tc = 0.48965 let vy = 0.20282 let sx = 0.80 let gx = 0.92388 let gy = 0.38268 let ystar = vy * (sx - x) / (sx - tc) let csg = (gx - sx) * y - gy * (x - sx) let kb = k + side - 8.0 * floor((k + side) / 8.0) let small = step(R, 6.0) var L = 0.5 var fid = 0.0 var e = abs(x - tc) * R if x < tc { // The table: one flat facet, clear and bright. L = 2.0 fid = 100.0 } else { if small > 0.5 { // A small stone: eight crown facets, alternately lit and dark. L = mix(0.86, 0.30, par) + 0.15 * mb_facing(th) fid = 20.0 + k e = min(e, (st * 0.5 - abs(am)) * r * R) } else { if y < ystar { // A star facet. L = mix(0.86, 0.42, par) + 0.15 * mb_facing(th) + 0.12 * (Finish.hash(vec2(20.0 + k, 7.7)) - 0.5) fid = 20.0 + k } else { if csg > 0.0 { // A kite, its tone stepping round the stone. let k4 = kb - 4.0 * floor(kb * 0.25) var kbase = 0.16 kbase = mix(kbase, 0.66, step(0.5, k4)) kbase = mix(kbase, 0.40, step(1.5, k4)) kbase = mix(kbase, 0.88, step(2.5, k4)) L = kbase + 0.16 * mb_facing(kb * st + spin) + 0.12 * (Finish.hash(vec2(40.0 + kb, 3.3)) - 0.5) fid = 40.0 + kb } else { // An upper girdle facet. L = mix(0.30 + 0.2 * par, 0.80 - 0.25 * par, side) + 0.15 * mb_facing(th + (side * 2.0 - 1.0) * st * 0.25) + 0.2 * (Finish.hash(vec2(60.0 + 2.0 * k + side, 9.1)) - 0.5) fid = 60.0 + 2.0 * k + side e = min(e, y * R) } } let nst = sqrt(vy * vy + (sx - tc) * (sx - tc)) e = min(e, abs((y - ystar) * (sx - tc)) / nst * R + 9.0 * step(sx + 0.02, x)) let nsg = sqrt((gx - sx) * (gx - sx) + gy * gy) e = min(e, abs(csg) / nsg * R + 9.0 * step(y, ystar)) } } L = L + 0.03 * (r - 0.6) var col = mb_dia_tone(L) // The table's own tone: a clear glass a breath under white. col = mix(col, vec3(0.915, 0.928, 0.962), step(1.5, L)) // Fire: a few lit facets flash orange, blue or green. let hf = Finish.hash(vec2(fid, 13.3)) let hue = floor(Finish.hash(vec2(fid, 5.1)) * 3.0) var fcol = vec3(1.0, 0.72, 0.42) fcol = mix(fcol, vec3(0.55, 0.75, 1.0), step(0.5, hue)) fcol = mix(fcol, vec3(0.62, 0.95, 0.66), step(1.5, hue)) col = mix(col, col * (0.62 + 0.38 * fcol), step(0.93, hf) * step(0.75, L) * step(L, 1.5) * (1.0 - step(R, 6.0))) // The facet edges: crisp hairlines, light along a dark facet and a // grey-blue along a light one; the girdle a bright line. let line = 1.0 - smoothstep(0.0, px * 0.9, e) let lum = dot(col, vec3(0.3, 0.5, 0.2)) let ecol = mix(min(col + vec3(0.32, 0.32, 0.32), vec3(1.0, 1.0, 1.0)), vec3(0.56, 0.60, 0.69), step(0.7, lum)) col = mix(col, ecol, line * 0.6) col = mix(col, vec3(1.0, 1.0, 1.0), smoothstep(0.955, 0.99, r) * 0.75) return col } // The star glints of a white diamond, at facet corners that turn with it: // a table corner, a star's apex and a kite's foot; one on a small stone. let mb_dia_glints = fn(q: vec2, R: float, spin: float, px: float) -> float { let st = PI / 4.0 let a1 = spin + 7.0 * st let a2 = spin + 3.5 * st let a3 = spin + 1.0 * st var g = mb_glint(q - vec2(cos(a1), sin(a1)) * (0.53 * R), max(0.55 * R, 2.6), px) if R > 6.0 { g = g + mb_glint(q - vec2(cos(a2), sin(a2)) * (0.80 * R), 0.28 * R, px) * 0.85 g = g + mb_glint(q - vec2(cos(a3), sin(a3)) * (0.96 * R), 0.22 * R, px) * 0.7 } return clamp(g, 0.0, 1.0) } // A ROUND BRILLIANT in colour (the emerald knobs): the octagonal table // showing the pavilion through it as wedges that alternate dark and light, // eight star facets, eight kites and sixteen upper girdle facets, each facet // a flat light by how it tilts to the key light and a little chance; // hairlines of light along the facet edges. A white stone is drawn as the // diamond above. `q` from the centre. Colour only. let mb_brilliant = fn(q: vec2, R: float, ink: vec3, spin: float, px: float) -> vec3 { if max(ink.x, max(ink.y, ink.z)) - min(ink.x, min(ink.y, ink.z)) < 0.15 { return mb_brilliant_white(q, R, spin, px) } let lv = mb_lv() let r = length(q) / max(R, 0.5) let a = atan2(q.y, q.x) - spin + 8.0 * PI let st = PI / 4.0 let k = floor(a / st) let am = (a - k * st) - st * 0.5 let fa = abs(am) / (st * 0.5) let roct = r * cos(am) / cos(st * 0.5) let rstar = mix(0.80, 0.53, fa) let rug = mix(0.80, 1.0, fa) // A white stone is all brilliance, light facets beside near black ones; // a coloured one keeps its colour, its facets closer together. let chroma = max(ink.x, max(ink.y, ink.z)) - min(ink.x, min(ink.y, ink.z)) let colored = smoothstep(0.15, 0.4, chroma) let base = mix(0.64, 0.42, colored) let amp = mix(0.62, 0.36, colored) var v = 0.5 var edge = 10.0 var hid = 0.0 if roct < 0.53 { // The pavilion seen through the table: sixteen wedges in three // rings, alternating, each its own small chance. let wk = floor(a / (st * 0.5)) let ring = floor(r / 0.18) let alt = fract((wk + ring) * 0.5) * 2.0 hid = wk * 7.0 + ring v = base + amp * ((alt - 0.5) * 0.95 + 0.4 * (Finish.hash(vec2(hid, 3.1)) - 0.5)) v = mix(v, base + 0.1, smoothstep(0.07, 0.0, r)) let ew = abs(fract(a / (st * 0.5) + 0.5) - 0.5) * (st * 0.5) * r * R let er = abs(fract(r / 0.18 + 0.5) - 0.5) * 0.18 * R edge = min(min((0.53 - roct) * R, ew), er) } else { var dir = k * st + st * 0.5 var tilt = 0.45 var id = 20.0 + k if r >= rstar && r < rug { let kb = k + step(0.0, am) dir = kb * st tilt = 0.62 id = 40.0 + kb } if r >= rug { let side = step(0.0, am) dir = k * st + st * 0.5 + (side * 2.0 - 1.0) * st * 0.25 tilt = 0.85 id = 60.0 + 2.0 * k + side } // The two halves of a facet pair either side of its middle line // tilt apart a little, so the crown reads as a star of light and // dark triangles. let half = step(0.0, am) * 2.0 - 1.0 let face = tilt * dot(vec2(cos(dir + spin + half * st * 0.2), sin(dir + spin + half * st * 0.2)), lv) hid = id * 2.0 + step(0.0, am) v = base + amp * (0.95 * face + 0.22 * half + 0.5 * (Finish.hash(vec2(hid, 7.7)) - 0.5)) edge = min(min(abs(roct - 0.53), abs(r - rstar) * 0.85), abs(r - rug) * 0.85) * R if r >= rug { edge = min(edge, abs(am) * r * R) } } v = clamp(v, 0.0, 1.0) var col = mb_gemcol(ink, v) // A white stone runs from a near black grey through a cool grey to white. var wc = mix(vec3(0.17, 0.18, 0.22), vec3(0.60, 0.62, 0.68), smoothstep(0.0, 0.5, v)) wc = mix(wc, vec3(1.0, 1.0, 1.0), smoothstep(0.5, 0.92, v)) col = mix(wc, col, colored) // A white stone's fire: a few bright facets throw a spectral tint. let fh = Finish.hash(vec2(hid, 13.3)) let fire = mix(vec3(1.0, 0.86, 0.62), vec3(0.66, 0.80, 1.0), step(0.5, fract(fh * 7.0))) col = mix(col, col * fire, (1.0 - colored) * step(0.86, fh) * smoothstep(0.55, 0.8, v) * 0.3) let line = 1.0 - smoothstep(0.0, max(px * 0.9, 0.035 * R), edge) col = mix(col, mix(col, vec3(1.0, 1.0, 1.0), 0.5), line * mix(0.4, 0.3, colored)) return col } // ---------------------------------------------------------------- the gem renderer // THE GEMS ARE RAY TRACED. A cut stone is a convex polyhedron (every facet a // plane n.p <= D), seen from above by an eye several stone radii over it. A // view ray is refracted in at the crown (a diamond bends light by 2.42), // bounces off the facets inside a few times by total internal reflection, // and where it can leave it is refracted out with a slightly different index // per colour channel, so the colours part a little: the stone's fire. What // leaves is lit by a studio (below): soft boxes of varied brightness with // dark seams, the key light brighter at the upper left, spokes of light and // shade over the eye. A coloured stone takes its colour by absorbing along // the path inside it (Beer's law). Each pass is bounded: a few bounces, each // testing one folded wedge of planes. Units: the girdle's radius (a step // cut's half its smaller side) is 1, z toward the eye. // A ROUND BRILLIANT, eight-fold: a table 0.565 out at its corners; eight // bezel facets at 34.5 degrees reaching the girdle; eight stars at 22.5 // degrees on the table's edges; sixteen upper girdle facets at 42 degrees; // below, eight pavilion mains at 40.8 degrees and sixteen lower girdle // facets at 42.5. Folded by its symmetry into one wedge (0 the middle of a // bezel, 22.5 degrees the middle of a star), where one plane of each family // is the one that counts, so a ray is tested against six planes and the // girdle's cylinder, not fifty-seven. let mb_gfold = fn(v: vec2) -> vec2 { let st = PI / 4.0 let a = atan2(v.y, v.x) let k = floor(a / st + 0.5) return vec2(k, step(0.0, a - k * st) * 2.0 - 1.0) } let mb_gto = fn(v: vec3, f: vec2) -> vec3 { let a = -f.x * PI / 4.0 let c = cos(a) let s = sin(a) return vec3(v.x * c - v.y * s, (v.x * s + v.y * c) * f.y, v.z) } let mb_gfrom = fn(v: vec3, f: vec2) -> vec3 { let a = f.x * PI / 4.0 let c = cos(a) let s = sin(a) let y = v.y * f.y return vec3(v.x * c - y * s, v.x * s + y * c, v.z) } // One plane's exit: kept in `best` (normal, distance) when it is nearer. let mb_gplane = fn(best: vec4, p: vec3, d: vec3, n: vec3, dd: float) -> vec4 { let nd = dot(n, d) let t = (dd - dot(n, p)) / max(nd, 0.00001) let take = step(0.00001, nd) * step(t, best.w) return mix(best, vec4(n, t), take) } // The crown over (x, y): its outward normal and its height. let mb_gcrown = fn(xy: vec2) -> vec4 { let f = mb_gfold(xy) let w = mb_gto(vec3(xy.x, xy.y, 0.0), f) var best = vec4(0.0, 0.0, 1.0, 0.298967) let zb = (0.566406 - 0.566406 * w.x) / 0.824126 best = mix(best, vec4(0.566406, 0.0, 0.824126, zb), step(zb, best.w)) let zs = (0.475967 - 0.353553 * w.x - 0.146447 * w.y) / 0.923880 best = mix(best, vec4(0.353553, 0.146447, 0.923880, zs), step(zs, best.w)) let zu = (0.669131 - 0.656273 * w.x - 0.130541 * w.y) / 0.743145 best = mix(best, vec4(0.656273, 0.130541, 0.743145, zu), step(zu, best.w)) return vec4(mb_gfrom(best.xyz, f), best.w) } // Where a ray inside the brilliant leaves it: the outward normal there and // the distance. The wedge is taken where the ray is guessed to leave and // the guess refined (`passes` in all); each pass can only shorten the // distance. // Every loop of the tracer counts to a number it is handed, not to one // written in it: a counted loop is compiled once and run, where a written // one is copied out pass by pass, and the copies made a page of stones take // seconds to compile. let mb_gexit = fn(p: vec3, d: vec3, passes: float) -> vec4 { var best = vec4(0.0, 0.0, 1.0, 1000.0) var g = p + d * 0.6 for i in 0..passes { let f = mb_gfold(g.xy) let pw = mb_gto(p, f) let dw = mb_gto(d, f) var loc = vec4(0.0, 0.0, 1.0, 1000.0) loc = mb_gplane(loc, pw, dw, vec3(0.0, 0.0, 1.0), 0.298967) loc = mb_gplane(loc, pw, dw, vec3(0.566406, 0.0, 0.824126), 0.566406) loc = mb_gplane(loc, pw, dw, vec3(0.353553, 0.146447, 0.923880), 0.475967) loc = mb_gplane(loc, pw, dw, vec3(0.656273, 0.130541, 0.743145), 0.669131) loc = mb_gplane(loc, pw, dw, vec3(0.653421, 0.0, -0.756995), 0.653421) loc = mb_gplane(loc, pw, dw, vec3(0.654071, 0.169154, -0.737277), 0.675590) // The girdle, a cylinder of radius 1. let a = max(dot(dw.xy, dw.xy), 0.000001) let b = dot(pw.xy, dw.xy) let c = dot(pw.xy, pw.xy) - 1.0 let tc = (sqrt(max(b * b - a * c, 0.0)) - b) / a let hc = pw.xy + dw.xy * tc let hn = hc / max(length(hc), 0.0001) loc = mix(loc, vec4(hn.x, hn.y, 0.0, tc), step(0.000001, dot(dw.xy, dw.xy)) * step(tc, loc.w)) best = mix(best, vec4(mb_gfrom(loc.xyz, f), loc.w), step(loc.w, best.w)) g = p + d * best.w } return vec4(best.xyz, clamp(best.w, 0.0, 4.0)) } // THE STUDIO, by the direction `e` leaving the stone (in the stone's frame, // `spin` turning it back to the room's): rows of soft boxes from 18 degrees // up to 72, nine degrees tall and fourteen wide, every row half a box // along, each box dim, bright or very bright by chance, with dark seams; the // key side (upper left) brighter; over 72 degrees a ceiling of spokes of // light and shade radiating from the eye, the eye itself a small dark patch // straight overhead (`eyed` how dark); below the boxes broad light and dark // panels round the horizon, lighter where the light comes up from under the // stone. let mb_genv = fn(e: vec3, spin: float, eyed: float) -> float { let d2r = PI / 180.0 let az = atan2(e.y, e.x) + spin let el = atan2(e.z, length(e.xy)) let rf = (el - 18.0 * d2r) / (9.0 * d2r) let ri = floor(rf) let fe = rf - ri let u = az / (14.0 * d2r) + ri * 0.5 let ci = floor(u) let fa = u - ci let seam = clamp((0.5 - abs(fa - 0.5) - 0.12) / 0.05, 0.0, 1.0) * clamp((0.5 - abs(fe - 0.5) - 0.10) / 0.06, 0.0, 1.0) let h = Finish.hash(vec2(ci, ri + 3.0)) let key = 0.7 + 0.6 * clamp(cos(az + 135.0 * d2r), 0.0, 1.0) var b = 0.3 b = mix(b, 1.4, step(0.22, h)) b = mix(b, 3.0, step(0.55, h)) var v = 0.47 + b * key * seam // Below the boxes, broad panels round the horizon, light and dark. let pl = az / (20.0 * d2r) + 0.25 let low = mix(0.35, 1.5, step(pl - floor(pl), 0.5)) * (0.8 + 0.4 * clamp(-e.z, 0.0, 1.0)) v = mix(v, low, step(el, 18.0 * d2r)) // The ceiling: spokes of light and shade radiating from the eye. let sp = az / (15.0 * d2r) let ceil = mix(0.55, 2.2, step(sp - floor(sp), 0.5)) v = mix(v, ceil, step(72.0 * d2r, el)) return mix(v, mix(v, 0.25, eyed), step(0.996, e.z)) } // A direction refracted through a surface of normal `n` (facing against // it), the indices' ratio `eta`; .w 0 where it cannot pass (total internal // reflection). let mb_grefr = fn(d: vec3, n: vec3, eta: float) -> vec4 { let c = -dot(d, n) let k = 1.0 - eta * eta * (1.0 - c * c) let thru = step(0.0, k) return vec4((eta * d + (eta * c - sqrt(max(k, 0.0))) * n) * thru, thru) } let mb_grefl = fn(d: vec3, n: vec3) -> vec3 { return d - 2.0 * dot(d, n) * n } // What a stone of the colour `ink` absorbs per unit of path inside it: the // channels its hue lacks, and evenly as much as it is dark (a smoky stone). let mb_gabsorb = fn(ink: vec3) -> vec3 { let mx = max(max(ink.x, ink.y), max(ink.z, 0.02)) let dk = -log(mx) * 0.5 return (vec3(1.0, 1.0, 1.0) - ink / mx) * 1.6 + vec3(dk, dk, dk) } // A stone's body colour for the renderer: a colourless ink (the sheet's // diamond white) absorbs nothing. let mb_gink = fn(ink: vec3) -> vec3 { let ch = max(ink.x, max(ink.y, ink.z)) - min(ink.x, min(ink.y, ink.z)) return mix(vec3(1.0, 1.0, 1.0), ink, step(0.15, ch)) } // A ROUND BRILLIANT at `q` (points from its centre), radius `R`, turned by // `spin`; `ink` its body colour (white: a diamond, no absorption), `nb` the // internal bounces followed (four for a big stone, two for a small one). // Colour only, tone mapped. let mb_gem = fn(q: vec2, R: float, ink: vec3, spin: float, nb: float) -> vec3 { // Outside the girdle there is no stone: nothing is traced there, so no // ray can run off to infinity in a pixel the stone does not cover. var res = vec3(0.5, 0.5, 0.5) if dot(q, q) < R * R * 1.1 { let cs = cos(-spin) let sn = sin(-spin) let x = (q.x * cs - q.y * sn) / max(R, 0.3) let y = (q.x * sn + q.y * cs) / max(R, 0.3) let cr = mb_gcrown(vec2(x, y)) var p = vec3(x, y, cr.w) let n = cr.xyz // The eye eight radii over the stone and a little off its axis, in the // stone's frame. let eye = vec3(0.10 * cs + 0.18 * sn, 0.10 * sn - 0.18 * cs, 8.0) var d = normalize(p - eye) let f0 = 0.172395 let tint = step(0.15, max(ink.x, max(ink.y, ink.z)) - min(ink.x, min(ink.y, ink.z))) let fi = (f0 + (1.0 - f0) * pow(1.0 - clamp(-dot(d, n), 0.0, 1.0), 5.0)) * mix(1.0, 0.4, tint) var col = vec3(1.0, 1.0, 1.0) * (fi * mb_genv(mb_grefl(d, n), spin, 1.0)) var thr = vec3(1.0, 1.0, 1.0) * (1.0 - fi) d = mb_grefr(d, n, 1.0 / 2.419).xyz let absorb = mb_gabsorb(ink) for b in 0..nb { let h = mb_gexit(p, d, 2.0) p = p + d * h.w thr = thr * exp(-absorb * h.w) let nh = h.xyz let fe = f0 + (1.0 - f0) * pow(1.0 - clamp(dot(d, nh), 0.0, 1.0), 5.0) let rr = mb_grefr(d, -nh, 2.414) let rg = mb_grefr(d, -nh, 2.419) let rb = mb_grefr(d, -nh, 2.425) let lit = vec3(mb_genv(normalize(rr.xyz + vec3(0.0, 0.0, 0.00001)), spin, 1.0) * rr.w, mb_genv(normalize(rg.xyz + vec3(0.0, 0.0, 0.00001)), spin, 1.0) * rg.w, mb_genv(normalize(rb.xyz + vec3(0.0, 0.0, 0.00001)), spin, 1.0) * rb.w) col = col + thr * (1.0 - fe) * lit thr = thr * mix(1.0, fe, rg.w) d = mb_grefl(d, nh) } col = col + thr * 0.6 res = vec3(1.0, 1.0, 1.0) - exp(-col * 1.5) } return res } // The same with `ns` samples along the device pixel's diagonal, for a big // stone. let mb_gem_aa = fn(q: vec2, R: float, ink: vec3, spin: float, px: float, nb: float, ns: float) -> vec3 { var acc = vec3(0.0, 0.0, 0.0) for k in 0..ns { let t = (float(k) + 0.5) / max(ns, 1.0) - 0.5 acc = acc + mb_gem(q + vec2(t, t) * px, R, ink, spin, nb) } return acc / max(ns, 1.0) } // A STEP CUT (an emerald cut, or a baguette when long and narrow), its // girdle the box `g` either side of its middle with its corners cut `cc` // (units of its half height or width, whichever is less): round the table // two crown steps, at 42 and 26 degrees, 0.22 and 0.18 wide; below, three // pavilion steps at 55, 45 and 35 degrees, 0.25, 0.25 wide and the rest. // Folded by its two mirrors into one quarter, where three directions count // (its long side, its short side and the cut corner), six planes each. // `dens` scales what the stone absorbs along its path (a pale quartz less // than an emerald), `veil` darkens its table for words laid on it. let mb_sdir = fn(best: vec4, p: vec3, d: vec3, u: vec2, gg: float) -> vec4 { var b = best // The girdle's side. b = mb_gplane(b, p, d, vec3(u.x, u.y, 0.0), gg) // The crown steps. b = mb_gplane(b, p, d, vec3(u.x * 0.669131, u.y * 0.669131, 0.743145), 0.669131 * gg) b = mb_gplane(b, p, d, vec3(u.x * 0.438371, u.y * 0.438371, 0.898794), 0.438371 * (gg - 0.22) + 0.898794 * 0.198089) // The pavilion steps. b = mb_gplane(b, p, d, vec3(u.x * 0.819152, u.y * 0.819152, -0.573576), 0.819152 * gg) b = mb_gplane(b, p, d, vec3(u.x * 0.707107, u.y * 0.707107, -0.707107), 0.707107 * (gg - 0.25) + 0.707107 * 0.357037) b = mb_gplane(b, p, d, vec3(u.x * 0.573576, u.y * 0.573576, -0.819152), 0.573576 * (gg - 0.5) + 0.819152 * 0.607037) return b } // The crown's height over a quarter point `w` for one direction: the lower // of its two steps. let mb_scrown = fn(best: vec4, w: vec2, u: vec2, gg: float) -> vec4 { var b = best let s = dot(w, u) let z1 = (0.669131 * gg - 0.669131 * s) / 0.743145 b = mix(b, vec4(u.x * 0.669131, u.y * 0.669131, 0.743145, z1), step(z1, b.w)) let z2 = (0.438371 * (gg - 0.22) + 0.898794 * 0.198089 - 0.438371 * s) / 0.898794 return mix(b, vec4(u.x * 0.438371, u.y * 0.438371, 0.898794, z2), step(z2, b.w)) } // Where a ray inside the step cut leaves it: the quarter taken where it is // guessed to leave, refined (`passes` in all). let mb_sexit = fn(p: vec3, d: vec3, g: vec2, gd: float, passes: float) -> vec4 { let ud = vec2(0.707107, 0.707107) var best = vec4(0.0, 0.0, 1.0, 1000.0) var gq = p + d * 0.6 for i in 0..passes { let s2 = vec2(step(0.0, gq.x) * 2.0 - 1.0, step(0.0, gq.y) * 2.0 - 1.0) let pw = vec3(p.x * s2.x, p.y * s2.y, p.z) let dw = vec3(d.x * s2.x, d.y * s2.y, d.z) var loc = vec4(0.0, 0.0, 1.0, 1000.0) loc = mb_gplane(loc, pw, dw, vec3(0.0, 0.0, 1.0), 0.285881) loc = mb_sdir(loc, pw, dw, vec2(1.0, 0.0), g.x) loc = mb_sdir(loc, pw, dw, vec2(0.0, 1.0), g.y) loc = mb_sdir(loc, pw, dw, ud, gd) best = mix(best, vec4(loc.x * s2.x, loc.y * s2.y, loc.z, loc.w), step(loc.w, best.w)) gq = p + d * best.w } return best } let mb_gstep = fn(q: vec2, hh: vec2, ink: vec3, nb: float, veil: float, dens: float) -> vec3 { // Outside its box there is no stone: nothing is traced there. var stone = vec3(0.5, 0.5, 0.5) if abs(q.x) < hh.x + 1.0 && abs(q.y) < hh.y + 1.0 { let sc = max(min(hh.x, hh.y), 0.5) let g = hh / sc let cc = 0.12 let gd = (g.x + g.y - cc) * 0.707107 let ud = vec2(0.707107, 0.707107) let x = q / sc // The crown hit, in the quarter. let sg = vec2(step(0.0, x.x) * 2.0 - 1.0, step(0.0, x.y) * 2.0 - 1.0) let w = abs(x) var cr = vec4(0.0, 0.0, 1.0, 0.285881) cr = mb_scrown(cr, w, vec2(1.0, 0.0), g.x) cr = mb_scrown(cr, w, vec2(0.0, 1.0), g.y) cr = mb_scrown(cr, w, ud, gd) var p = vec3(x.x, x.y, cr.w) let n = vec3(cr.x * sg.x, cr.y * sg.y, cr.z) let eye = vec3(0.08 * g.x, -0.15, 3.0 + g.x) var d = normalize(p - eye) let f0 = 0.172395 let fi = (f0 + (1.0 - f0) * pow(1.0 - clamp(-dot(d, n), 0.0, 1.0), 5.0)) * 0.4 var col = vec3(1.0, 1.0, 1.0) * (fi * mb_genv(mb_grefl(d, n), 0.0, 0.0)) var thr = vec3(1.0, 1.0, 1.0) * (1.0 - fi) d = mb_grefr(d, n, 1.0 / 2.419).xyz let absorb = mb_gabsorb(ink) * dens for b in 0..nb { let best = mb_sexit(p, d, g, gd, 2.0) let tw = clamp(best.w, 0.0, 4.0) p = p + d * tw thr = thr * exp(-absorb * tw) let nh = best.xyz let fe = f0 + (1.0 - f0) * pow(1.0 - clamp(dot(d, nh), 0.0, 1.0), 5.0) let rr = mb_grefr(d, -nh, 2.414) let rg = mb_grefr(d, -nh, 2.419) let rb = mb_grefr(d, -nh, 2.425) let lit = vec3(mb_genv(normalize(rr.xyz + vec3(0.0, 0.0, 0.00001)), 0.0, 0.0) * rr.w, mb_genv(normalize(rg.xyz + vec3(0.0, 0.0, 0.00001)), 0.0, 0.0) * rg.w, mb_genv(normalize(rb.xyz + vec3(0.0, 0.0, 0.00001)), 0.0, 0.0) * rb.w) col = col + thr * (1.0 - fe) * lit thr = thr * mix(1.0, fe, rg.w) d = mb_grefl(d, nh) } col = col + thr * 0.6 let lit_out = vec3(1.0, 1.0, 1.0) - exp(-col * 1.35) // Over the table a veil of the stone's deep colour, `veil` strong, so // words laid on it read; the steps round it keep their sparkle. let mx = max(max(ink.x, ink.y), max(ink.z, 0.02)) let deep = ink / mx * 0.40 * min(mx * 1.6, 1.0) + vec3(0.02, 0.02, 0.02) stone = mix(lit_out, deep, veil * step(0.999, n.z)) } return stone } // A tiny pavé diamond, radius `rad`, `q` from its centre, `h` its chance // (a few of them catch the light and sparkle). Premultiplied. let mb_pave = fn(q: vec2, rad: float, h: float, px: float) -> vec4 { let t = length(q) / max(rad, 0.1) let ang = atan2(q.y, q.x) let sec = step(0.5, fract(ang / (PI / 4.0) + h * 3.0)) var col = mix(vec3(0.985, 0.985, 0.99), vec3(0.66, 0.67, 0.71), smoothstep(0.4, 1.0, t)) col = col * (0.88 + 0.16 * sec) let sq = q / max(rad, 0.1) + vec2(0.28, 0.34) col = mix(col, vec3(1.0, 1.0, 1.0), exp(-dot(sq, sq) / 0.08) * 0.9) col = mix(col, vec3(0.42, 0.34, 0.20), Finish.band(length(q) - rad, 0.0, max(px * 0.7, 0.10 * rad), px) * 0.45) let a = Finish.cover(length(q) - rad, px) var res = vec4(min(col, vec3(1.0, 1.0, 1.0)) * a, a) if h > 0.86 { let gl = mb_glint(q + vec2(0.2, 0.25) * rad, rad * 2.6, px) * 0.85 res = Finish.over(res, vec4(gl, gl, gl, gl)) } return res } // MILGRAIN: a line of tiny bright beads `at` points inside the outline of a // rounded box, `pitch` apart, the jeweller's pavé edge of a frame. Over the // colour `col` already worked out there. let mb_milgrain = fn(col: vec3, p: vec2, c: vec2, h: vec2, r: float, at: float, rad: float, pitch: float, px: float) -> vec3 { let d = Material.sd_box(p, c, h, r) + at let n = mb_normal(p, c, h, r) var s = p.x if abs(n.x) > abs(n.y) { s = p.y } let t = (fract(s / pitch) - 0.5) * pitch let bd = length(vec2(t, d)) - rad let a = Finish.cover(bd, px) let k = clamp((t * 0.6 - d * 0.8) / max(rad, 0.1), -1.0, 1.0) let bc = mix(vec3(0.60, 0.58, 0.55), vec3(1.0, 0.995, 0.985), k * 0.5 + 0.5) return mix(col, mix(bc, vec3(0.42, 0.33, 0.16), Finish.band(bd, 0.0, px * 0.6, px) * 0.4), a) } // A row of pavé diamonds along x from `x0` to `x1` on the line `cy`, `pitch` // apart, in a gold seat. Premultiplied, over `dst`. let mb_pave_row = fn(dst: vec4, p: vec2, x0: float, x1: float, cy: float, rad: float, pitch: float, px: float) -> vec4 { let cell = floor((p.x - x0) / pitch) let cx = x0 + (cell + 0.5) * pitch let inside = step(x0, cx - rad * 0.5) * step(cx + rad * 0.5, x1) let h = Finish.hash(vec2(cell, 5.3)) return Finish.over(dst, mb_pave(p - vec2(cx, cy), rad, h, px) * inside) } // ---------------------------------------------------------------- light // LIGHT is added, never painted: screened over what is there (what lies // under a point taken as this face over the stone) and rolled off as // 1 - e^-light, so it brightens and never clips or darkens. let mb_lit = fn(res: vec4, light: vec3) -> vec4 { let est = res.rgb + mb_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) } // LAMPS: an LED as a gem in a gold setting. 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, with a catch light on its lower rim. Its lens is faintly // faceted, a cut stone. Round it a polished gold bezel (a round bead lit // from the top), or a socket cut into what is under it. 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, gone in about two radii; on a pale // ground, where light can barely brighten, its hue is laid round it as a // tint that keeps the ground's luminance (at most 0.45 one device pixel // out, halving every 0.4 of its reach, gone at the reach). A bar is the // same light drawn out into a light pipe. let mb_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) } let mb_lamp_full = fn(ink: vec3) -> vec3 { return ink / max(max(ink.x, max(ink.y, ink.z)), 0.01) } let mb_lamp_hue = fn(ink: vec3, vivid: float) -> vec3 { let f = mb_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`, `lit` its // current 0..1, `ink` its colour, `ground` what shows through, `room` how // far its light may reach, `raised` its setting (0 a socket cut in what is // under it; 1 a gold bezel). Premultiplied over `dst`. let mb_lamp = fn(dst: vec4, p: vec2, c: vec2, hb: vec2, lit: float, ink: vec3, vivid: float, px: float, ground: vec3, room: float, 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.32 * rt, 0.7, 2.4) let ds = d - bw let L = clamp(lit, 0.0, 1.0) let b = mb_lamp_full(ink) let hue = mb_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) 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 setting throws on the ground: a soft shadow, down. let shd = Finish.drop(p, c, hb + vec2(bw, bw), rt + bw, vec2(0.0, 0.2 * rt + 0.3), 0.28 * rt + px) * mix(0.16, 0.30, raised) * (1.0 - 0.6 * pw) res = Finish.over(res, vec4(mb_shade() * shd, shd)) if L > 0.001 && pale > 0.001 { 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 setting: a polished gold bead round the lens, 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(1.0 - d / max(bw, 0.01), 0.0, 1.0) let gold = mb_gold(mb_bead(u, nrm)) 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 = mix(sc, gold, clamp(raised, 0.0, 1.0)) sc = mix(sc, sc * 0.55, Finish.band(d, -px, 0.0, px) * 0.8) res = Finish.over(res, vec4(sc * a_s, a_s)) // The light: a tight hot halo and a short bloom in its hue, falling on // the setting 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 = mb_lamp_add(res, mix(hue, white, 0.15 * pw) * halo * pw * hwin * win, ground) } // THE STONE: a glossy dome of coloured glass, faintly faceted. Its // colour deepens toward the top; light pools low inside; 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) let fac = Finish.hash(vec2(floor((atan2(q.y, q.x) + PI) / (PI / 4.0)), floor(t * 2.2) + 3.0)) var on = mix(hue * mix(0.55, 0.8, L), mix(b, hue, 0.5), top) on = on * (0.86 + 0.28 * fac * smoothstep(0.3, 0.6, t)) 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.995, 0.985), exp(-t * t / 0.26) * smoothstep(0.5, 1.0, L) * 0.95) 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, its colour barely there, the same facets, a // catch light along its lower rim. let offc = mix(vec3(0.075, 0.068, 0.060), b * 0.40, 0.16) var off = mix(offc * 0.62, offc * 1.15, top) * (0.85 + 0.35 * fac * smoothstep(0.3, 0.6, t)) off = mix(off, min(offc + vec3(0.18, 0.18, 0.18), white), pool * 0.35) 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.28, 0.26) * catchl var col = mix(off, min(on, white), smoothstep(0.0, 0.35, L)) 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)) col = min(col, white) let a_l = Finish.cover(d, px) return Finish.over(res, vec4(col * a_l, a_l)) } let mb_led = fn(dst: vec4, p: vec2, c: vec2, rd: float, lit: float, ink: vec3, px: float) -> vec4 { return mb_lamp(dst, p, c, vec2(rd, rd), lit, ink, 0.6, px, mb_ground(), 3.0 * rd + 2.5, 1.0) } // ---------------------------------------------------------------- shadows // What a piece of jewellery standing on the stone throws: a soft warm // shadow falling a little down, drawn in a point from the sides, half gone // two points under the foot and gone by five (within half of the eight // point gap to a neighbour), and a close contact shadow. Held, the piece // sits down and most of it goes. Returns the coverage. let mb_shadow = fn(p: vec2, c: vec2, h: vec2, r: float, px: float, hover: float, down: float, dis: float) -> float { let sink = 1.0 - 0.7 * down let hs = vec2(max(h.x - 1.0, 0.5), h.y) let amb = Finish.drop(p, c, hs, min(r, min(hs.x, hs.y)), vec2(0.0, (1.3 + 0.3 * hover) * sink), 1.1 + 0.2 * hover + px * 0.5) * (0.30 + 0.05 * hover) * sink * (1.0 - 0.8 * dis) let con = Finish.drop(p, c, h, r, vec2(0.0, 0.45 + px * 0.5), 0.45 + px * 0.5) * mix(0.30, 0.15, down) * (1.0 - 0.6 * dis) return 1.0 - (1.0 - amb) * (1.0 - con) } // The warm gold light the focus frame spills outward: a tint one device // pixel out, gone within 2.5 points. Premultiplied, laid under the face. let mb_leak = fn(d: float, a: float) -> vec4 { let s = 1.1 let g = 0.55 * exp(-(max(d, 0.0) * max(d, 0.0)) / (2.0 * s * s)) * (1.0 - a) return vec4(vec3(0.95, 0.74, 0.30) * g, g) } // Focus over a gold rim: the rim burns brighter, toward the white-gold. let mb_focus_rim = fn(col: vec3, d: float, w: float, px: float, fo: float) -> vec3 { if fo < 0.001 { return col } let band = Finish.band(d, 0.0, w, px) return mix(col, min(col * 1.25 + vec3(0.10, 0.08, 0.02), vec3(1.0, 0.97, 0.86)), band * fo * 0.8) } // A JEWEL BOX: the shape every raised control is cut from: a face set in a // polished gold rim `rw` wide, sunk a little under the rim (a soft shade // under its upper edge and a dark device pixel where face meets gold), the // whole throwing its shadow on the stone. `face` is the colour the caller // worked out for the face at this point. Held, the box sits half a point // lower. Premultiplied, over the shadow and focus spill it lays first. // The same box; `dbl` 1 gives it the double rim. let mb_box2 = fn(p: vec2, lo: vec2, hi: vec2, r: float, rw: float, px: float, face: vec3, hover: float, down: float, dis: float, focus: float, lift: float, dbl: float) -> vec4 { let live = 1.0 - dis let sink = 0.8 * down * live let c0 = (lo + hi) * 0.5 let h = (hi - lo) * 0.5 let c = c0 + vec2(0.0, sink) let sc = mb_shadow(p, c0, h, r, px, hover, down, dis) * lift var res = vec4(mb_shade() * sc, sc) let d = Material.sd_box(p, c, h, r) let a = Finish.cover(d, px) let fo = focus * live if fo > 0.001 { res = Finish.over(res, mb_leak(d, a) * fo) } let n = mb_normal(p, c, h, r) var col = face let hi_in = h - vec2(rw, rw) let ri = max(r - rw, 0.0) let insh = Finish.inset(p, c, hi_in, ri, vec2(0.0, 1.0), 1.2) col = mix(col, col * 0.80, clamp(insh, 0.0, 1.0) * (0.45 + 0.35 * down * live) * Finish.cover(d + rw, px)) col = mix(col, vec3(0.30, 0.21, 0.07), Finish.band(d, rw, rw + px, px) * 0.55) let rim = Finish.band(d, 0.0, rw, px) var gold = mix(mb_rim(d, rw, n, p), mb_rim2(d, rw, n, p, px), dbl) gold = mix(gold, mix(vec3(0.80, 0.77, 0.70), gold, 0.35), 0.6 * dis) col = mix(col, gold, rim) col = mb_focus_rim(col, d, rw, px, fo) return Finish.over(res, vec4(col * a, a)) } // The jewel box with the single rim. let mb_box = fn(p: vec2, lo: vec2, hi: vec2, r: float, rw: float, px: float, face: vec3, hover: float, down: float, dis: float, focus: float, lift: float) -> vec4 { return mb_box2(p, lo, hi, r, rw, px, face, hover, down, dis, focus, lift, 0.0) } // ---------------------------------------------------------------- buttons // Every button is a jewel box: 26 points on a 12 point line, 12 points // either side, a 3.5 point corner, its gold rim about four points wide. Its shadow and its light fall outside // the box. mod.widgets.ButtonFlat.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 4.0 bottom: 4.0} mod.widgets.ButtonFlat.min_height = 26.0 mod.widgets.ButtonFlat.icon_walk.width = 14.0 mod.widgets.ButtonIcon.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 4.0 bottom: 4.0} mod.widgets.ButtonFlatIcon.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 4.0 bottom: 4.0} mod.widgets.ButtonFlatterIcon.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 4.0 bottom: 4.0} // The size rungs keep heights of their own. mod.widgets.ButtonXs.min_height = 18.0 mod.widgets.ButtonSm.min_height = 22.0 mod.widgets.ButtonLg.min_height = 30.0 mod.widgets.ButtonXl.min_height = 34.0 mod.widgets.ButtonFlat.draw_bg.border_radius = uniform(3.5) 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 = 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)) } // The button and its variants share one face; `kind` picks it: 0 a bar of // polished gold (the plain, small and large buttons), 1 white marble in a // gold rim (the default button), 2 an emerald baguette (primary), 3 a // champagne quartz (secondary), 4 a smoky quartz (tertiary), 5 a ruby // (danger), 6 an outline of gold wire, 7 no face (a gold wash under the // hand). A cut stone catches a star glint at its upper right corner, // brighter under the hand. // A JEWEL BUTTON'S STONE (kinds 2 to 5) at this pixel, traced in the face // the button below lays out: an emerald, a champagne or a smoky quartz, or a // ruby, step cut. A quartz is a pale stone: it absorbs far less along its // path than an emerald or a ruby. Only the jewel variants call it, so the // plain buttons' shaders carry no tracer. let mb_button_gem = fn(pos: vec2, size: vec2, o: vec2, dpi: float, down: float, dis: float, kind: float) -> vec3 { let m = 12.0 let px = mb_px(dpi) let p = pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let lo = vec2(mb_snap(0.0, o.x, px), mb_snap(0.0, o.y, px)) let hi = vec2(mb_snap(size.x, o.x, px), mb_snap(size.y, o.y, px)) let cf = (lo + hi) * 0.5 + vec2(0.0, 0.8 * down * (1.0 - dis)) let rw = clamp(0.15 * (hi.y - lo.y), 2.6, 4.6) var gem = mb_emerald() var dens = 1.0 if kind > 2.5 && kind < 3.5 { gem = mb_champagne() dens = 0.2 } if kind > 3.5 && kind < 4.5 { gem = mb_smoky() dens = 0.25 } if kind > 4.5 { gem = mb_ruby() } return mb_gstep(p - cf, (hi - lo) * 0.5 - vec2(rw, rw), gem, 3.0, 0.55, dens) } // `gem` is the jewel's stone from the function above (ignored by the // other kinds). let mb_button = fn(pos: vec2, size: vec2, o: vec2, dpi: float, hover: float, down: float, dis: float, focus: float, loading: float, anim: float, kind: float, dash: float, gem: vec3) -> vec4 { let m = 12.0 let px = mb_px(dpi) let p = pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let lo = vec2(mb_snap(0.0, o.x, px), mb_snap(0.0, o.y, px)) let hi = vec2(mb_snap(size.x, o.x, px), mb_snap(size.y, o.y, px)) let c = (lo + hi) * 0.5 let h = (hi - lo) * 0.5 let hh = hi.y - lo.y let r = min(3.5, min(h.x, h.y)) let live = 1.0 - dis var res = vec4(0.0, 0.0, 0.0, 0.0) var ink = mb_ink() if kind > 5.5 { let d = Material.sd_box(p, c, h, r) let a = Finish.cover(d, px) let n = mb_normal(p, c, h, r) if focus * live > 0.001 { res = Finish.over(res, mb_leak(d, a) * focus * live) } // Under the hand a faint white wash on the stone, held a faint gold one. let wash = 0.45 * hover * (1.0 - down) * live * a res = Finish.over(res, vec4(vec3(1.0, 1.0, 1.0) * wash * 0.6, wash * 0.6)) let press = 0.14 * down * live * a res = Finish.over(res, vec4(vec3(0.72, 0.59, 0.31) * press, press)) if kind < 6.5 { // The outline: a round gold wire, its contact shadow under it. let rw = 2.4 var ring = Finish.band(d, 0.0, rw, px) if dash > 0.0 { ring = ring * step(0.5, fract((p.x + p.y) / (dash * 2.0))) } let sh = Finish.drop(p, c, h, r, vec2(0.0, 0.6), 0.6) * (1.0 - Finish.cover(d + rw, px)) * 0.25 * live res = Finish.over(res, vec4(mb_shade() * sh, sh)) var gold = mb_rim(d, rw, n, p) gold = mix(gold, vec3(0.80, 0.77, 0.70), 0.55 * dis) gold = mb_focus_rim(gold, d, rw, px, focus * live) res = Finish.over(res, vec4(gold * ring, ring)) } } else { let sink = 0.8 * down * live let cf = c + vec2(0.0, sink) let rw = clamp(0.15 * hh, 2.6, 4.6) let hf = h - vec2(rw, rw) let rf = max(r - rw, 0.5) let df = Material.sd_box(p, cf, hf, rf) let nf = mb_normal(p, cf, hf, rf) var face = vec3(0.0, 0.0, 0.0) if kind < 0.5 { face = mb_goldface(p, cf, hf, df, nf, 2.2) ink = vec3(0.20, 0.14, 0.04) } else if kind < 1.5 { let t = clamp((p.y - lo.y) / max(hh, 1.0), 0.0, 1.0) face = mb_stone(p + mb_seed(size), px, 0.55) face = face * (1.015 - 0.045 * t) face = mb_milgrain(face, p, cf, h, r, rw + 1.0, 0.6, 1.9, px) } else { face = gem ink = vec3(1.0, 0.98, 0.94) } face = face * (1.0 + 0.05 * hover * live - 0.11 * down * live) let fl = dot(face, vec3(0.3, 0.5, 0.2)) face = mix(face, mix(mb_ground(), vec3(fl, fl, fl), 0.3), 0.6 * dis) res = mb_box(p, lo, hi, r, rw, px, face, hover, down, dis, focus, 1.0) if kind > 1.5 && dis < 0.5 { let gq = p - vec2(hi.x - rw - 4.5, lo.y + rw + 3.2 + sink) let g = mb_glint(gq, 5.0 + 1.5 * hover, px) * (0.5 + 0.5 * hover) * Finish.cover(df, px) res = Finish.over(res, vec4(g, g, g, g)) } } if loading > 0.001 { let rad = max(min(h.x, h.y) - 5.5, 2.0) let sdf = Sdf2d.viewport(p) let a0 = anim * 2.0 * PI sdf.arc_round_caps(c.x, c.y, rad, a0, a0 + 1.5 * PI, 1.5) sdf.fill(vec4(ink, loading)) res = Finish.over(res, sdf.result) } return res * mb_fade(pos, size, m) } mod.widgets.ButtonFlat.draw_bg.pixel = fn() { return self.host_over(mb_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, vec3(0.0, 0.0, 0.0)), 12.0) } mod.widgets.Button.draw_bg.pixel = fn() { return self.host_over(mb_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, vec3(0.0, 0.0, 0.0)), 12.0) } mod.widgets.ButtonPrimary.draw_bg.pixel = fn() { let gem = mb_button_gem(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.down, self.disabled, 2.0) return self.host_over(mb_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, gem), 12.0) } mod.widgets.ButtonSecondary.draw_bg.pixel = fn() { let gem = mb_button_gem(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.down, self.disabled, 3.0) return self.host_over(mb_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, gem), 12.0) } mod.widgets.ButtonTertiary.draw_bg.pixel = fn() { let gem = mb_button_gem(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.down, self.disabled, 4.0) return self.host_over(mb_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, gem), 12.0) } mod.widgets.ButtonDanger.draw_bg.pixel = fn() { let gem = mb_button_gem(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.down, self.disabled, 5.0) return self.host_over(mb_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, gem), 12.0) } mod.widgets.ButtonOutline.draw_bg.pixel = fn() { return self.host_over(mb_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, vec3(0.0, 0.0, 0.0)), 12.0) } mod.widgets.ButtonFlatter.draw_bg.pixel = fn() { return self.host_over(mb_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, vec3(0.0, 0.0, 0.0)), 12.0) } // The legends are set in the sheet's serif (the theme's bold face). mod.widgets.ButtonFlat.draw_text.text_style = mod.theme.font_bold{font_size: 9.5} mod.widgets.Button.draw_text.text_style = mod.theme.font_bold{font_size: 9.5} mod.widgets.ButtonPrimary.draw_text.text_style = mod.theme.font_bold{font_size: 9.5} mod.widgets.ButtonSecondary.draw_text.text_style = mod.theme.font_bold{font_size: 9.5} mod.widgets.ButtonTertiary.draw_text.text_style = mod.theme.font_bold{font_size: 9.5} mod.widgets.ButtonDanger.draw_text.text_style = mod.theme.font_bold{font_size: 9.5} mod.widgets.ButtonOutline.draw_text.text_style = mod.theme.font_bold{font_size: 9.5} mod.widgets.ButtonFlatter.draw_text.text_style = mod.theme.font_bold{font_size: 9.5} mod.widgets.ButtonSm.draw_text.text_style = mod.theme.font_bold{font_size: 8.5} mod.widgets.ButtonLg.draw_text.text_style = mod.theme.font_bold{font_size: 11.0} mod.widgets.ButtonXs.draw_text.text_style = mod.theme.font_bold{font_size: 7.5} mod.widgets.ButtonXl.draw_text.text_style = mod.theme.font_bold{font_size: 12.5} // Label ink: a dark brown on the gold bars (7:1 on the body) and on the // white stone, a warm white on the cut stones. mod.widgets.ButtonFlat.draw_text.color = #33230a mod.widgets.ButtonFlat.draw_text.color_hover = #26190a mod.widgets.ButtonFlat.draw_text.color_down = #26190a mod.widgets.ButtonFlat.draw_text.color_focus = #33230a mod.widgets.ButtonFlat.draw_text.color_disabled = #a89c86 mod.widgets.ButtonFlat.draw_icon.color = #33230a mod.widgets.Button.draw_text.color = #3a2e1c mod.widgets.Button.draw_text.color_hover = #2a2112 mod.widgets.Button.draw_text.color_down = #2a2112 mod.widgets.Button.draw_text.color_focus = #3a2e1c mod.widgets.Button.draw_text.color_disabled = #a89c86 mod.widgets.ButtonPrimary.draw_text.color = #fffaf0 mod.widgets.ButtonPrimary.draw_text.color_hover = #ffffff mod.widgets.ButtonPrimary.draw_text.color_down = #f2ece0 mod.widgets.ButtonPrimary.draw_text.color_focus = #fffaf0 mod.widgets.ButtonPrimary.draw_text.color_disabled = #f2efe8 mod.widgets.ButtonPrimary.draw_icon.color = #fffaf0 mod.widgets.ButtonSecondary.draw_text.color = #fffaf0 mod.widgets.ButtonSecondary.draw_text.color_hover = #ffffff mod.widgets.ButtonSecondary.draw_text.color_down = #f2ece0 mod.widgets.ButtonSecondary.draw_text.color_focus = #fffaf0 mod.widgets.ButtonSecondary.draw_text.color_disabled = #f2efe8 mod.widgets.ButtonSecondary.draw_icon.color = #fffaf0 mod.widgets.ButtonTertiary.draw_text.color = #fffaf0 mod.widgets.ButtonTertiary.draw_text.color_hover = #ffffff mod.widgets.ButtonTertiary.draw_text.color_down = #f2ece0 mod.widgets.ButtonTertiary.draw_text.color_focus = #fffaf0 mod.widgets.ButtonTertiary.draw_icon.color = #fffaf0 mod.widgets.ButtonDanger.draw_text.color = #fffaf0 mod.widgets.ButtonDanger.draw_text.color_hover = #ffffff mod.widgets.ButtonDanger.draw_text.color_down = #f2ece0 mod.widgets.ButtonDanger.draw_text.color_focus = #fffaf0 mod.widgets.ButtonDanger.draw_text.color_disabled = #f2efe8 mod.widgets.ButtonDanger.draw_icon.color = #fffaf0 mod.widgets.ButtonOutline.draw_text.color = #3a2e1c mod.widgets.ButtonOutline.draw_text.color_hover = #2a2112 mod.widgets.ButtonOutline.draw_text.color_down = #2a2112 mod.widgets.ButtonOutline.draw_text.color_focus = #3a2e1c mod.widgets.ButtonOutline.draw_text.color_disabled = #a89c86 mod.widgets.ButtonOutline.draw_icon.color = #6e521c mod.widgets.ButtonFlatter.draw_text.color = #3a2e1c mod.widgets.ButtonFlatter.draw_text.color_hover = #2a2112 mod.widgets.ButtonFlatter.draw_text.color_down = #2a2112 mod.widgets.ButtonFlatter.draw_text.color_focus = #3a2e1c mod.widgets.ButtonFlatter.draw_text.color_disabled = #a89c86 mod.widgets.ButtonFlatter.draw_icon.color = #6e521c // A compound button's second line: the second voice. mod.widgets.ButtonFlat.draw_description.color = #33230a mod.widgets.ButtonOutline.draw_description.color = #5c4a2e mod.widgets.ButtonFlatter.draw_description.color = #5c4a2e // A link is words on the stone: brown, turning emerald under the hand. mod.widgets.LinkLabel.draw_text.color = #3a2e1c mod.widgets.LinkLabel.draw_text.color_hover = #0d6a39 mod.widgets.LinkLabel.draw_text.color_down = #0b5028 mod.widgets.LinkLabel.draw_text.color_focus = #2a2112 mod.widgets.LinkLabel.draw_text.color_disabled = #a89c86 mod.widgets.CloseButton.draw_bg.cross_color = #5c4a2e // ---------------------------------------------------------------- selection // The small controls' quads grow by this much, for their shadow and their // focus spill. mod.widgets.CheckBoxFlat.draw_bg.vertex = fn() { let m = 8.0 self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m)) } mod.widgets.CheckBoxFlat.clip_x = false mod.widgets.CheckBoxFlat.clip_y = false mod.widgets.CheckBoxFlat.padding = mod.turtle.Inset{left: 8.0 right: 8.0 top: 4.0 bottom: 4.0} mod.widgets.CheckBoxFlat.draw_bg.size = uniform(15.0) mod.widgets.CheckBoxFlat.draw_bg.border_radius = uniform(1.5) mod.widgets.CheckBoxFlat.label_walk.margin.left = 14.0 // A gold mark (a check or a dash) in a warm white with a dark brown edge, // so it reads on gold. `dk` its distance. let mb_mark = fn(dst: vec4, dk: float, px: float, amt: float, dis: float) -> vec4 { let ea = Finish.cover(dk - 0.55, px) * amt var res = Finish.over(dst, vec4(vec3(0.24, 0.16, 0.04) * ea * 0.85, ea * 0.85)) let ma = Finish.cover(dk, px) * amt let mc = mix(vec3(1.0, 0.975, 0.90), vec3(0.93, 0.91, 0.86), dis) return Finish.over(res, vec4(mc * ma, ma)) } // The check box: a 15 point gold tile with a 3 point corner, its rim a // polished bead; empty, its middle is a satin gold sunk a little under the // rim; set, the middle is polished and a warm white check stands on it (a // dash when mixed). mod.widgets.CheckBoxFlat.draw_bg.pixel = fn() { let m = 8.0 let px = mb_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(mb_snap(x0, o.x, px), mb_snap(y0, o.y, px)) let hi = vec2(mb_snap(x0 + sz, o.x, px), mb_snap(y0 + sz, o.y, px)) let held = max(self.down, self.pressed) let on = max(self.active, self.mixed) let live = 1.0 - self.disabled let sink = 0.5 * held * live let c = (lo + hi) * 0.5 + vec2(0.0, sink) let h = (hi - lo) * 0.5 let r = 3.0 let rw = 1.7 let hf = h - vec2(rw, rw) let df = Material.sd_box(p, c, hf, r - rw * 0.5) let nf = mb_normal(p, c, hf, r - rw * 0.5) var face = mb_goldface(p, c, hf, df, nf, 1.4) // Empty: satin, a step deeper; set: polished. let satin = mb_gold(0.36 + 0.06 * (1.0 - smoothstep(-1.0, 1.0, (p.y - c.y) / max(hf.y, 1.0)))) face = mix(satin, face, on) face = face * (1.0 + 0.06 * self.hover * live - 0.12 * held * live) face = mix(face, vec3(0.82, 0.79, 0.72), 0.6 * self.disabled) var res = mb_box(p, lo, hi, r, rw, px, face, self.hover, held, self.disabled, self.focus, 1.0) // The error: the rim turns ruby. let d = Material.sd_box(p, c, h, r) res = Finish.over(res, vec4(0.70, 0.08, 0.15, 1.0) * Finish.band(d, 0.0, 1.2, px) * self.error * live) let cm = c + vec2(0.0, 0.2) let dm = min(mb_seg(p, vec2(cm.x - 3.4, cm.y + 0.1), vec2(cm.x - 1.1, cm.y + 2.5)), mb_seg(p, vec2(cm.x - 1.1, cm.y + 2.5), vec2(cm.x + 3.5, cm.y - 2.6))) let dd = mb_seg(p, vec2(cm.x - 3.2, cm.y), vec2(cm.x + 3.2, cm.y)) let dk = mix(dm - 0.85, dd - 0.95, self.mixed) res = mb_mark(res, dk, px, on, self.disabled) return res * mb_fade(self.pos, self.rect_size, m) } // THE TOGGLE, the pictures' gold key in a gold frame: a 36 x 20 point frame // with a 4 point corner, its floor a satin gold sunk under its rim, and in // it a square key of polished gold standing up, at the left while off and // at the right while on. On, the key carries a round brilliant diamond set // in it, glinting; off, an engraved dash. The key follows a drag. mod.widgets.ToggleFlat.draw_bg.size = uniform(20.0) mod.widgets.ToggleFlat.draw_bg.pill_aspect = uniform(1.9) mod.widgets.ToggleFlat.label_walk.margin.left = 38.0 let mb_toggle = fn(pos: vec2, size: vec2, o: vec2, dpi: float, th0: float, aspect: float, at_end: float, hover: float, held: float, dis: float, on: float, kt: float, focus: float, error: float) -> vec4 { let m = 10.0 let px = mb_px(dpi) let p = pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let tw = th0 * aspect let th = th0 let x0 = (size.x - tw) * at_end let y0 = size.y * 0.5 - th * 0.5 let lo = vec2(mb_snap(x0, o.x, px), mb_snap(y0, o.y, px)) let hi = vec2(mb_snap(x0 + tw, o.x, px), mb_snap(y0 + th, o.y, px)) let r = 4.0 let rw = 1.7 let live = 1.0 - dis let c = (lo + hi) * 0.5 let h = (hi - lo) * 0.5 // The frame's floor: satin gold, deeper under the rim. let floor_col = mix(mb_gold(0.30), mb_gold(0.40 + 0.06 * on), smoothstep(-1.0, 1.0, (p.y - c.y) / max(h.y, 1.0))) var face = mix(floor_col, vec3(0.82, 0.79, 0.72), 0.6 * dis) var res = mb_box(p, lo, hi, r, rw, px, face, hover * 0.4, 0.0, dis, focus, 0.8) // The key: a square of polished gold standing in the frame, its own // shadow on the floor. let ks = th - 2.0 * rw - 1.0 let kh = vec2(ks, ks) * 0.5 let travel = (hi.x - lo.x) - 2.0 * rw - ks - 1.0 let sink = 0.8 * held * live let kc = vec2(lo.x + rw + 0.5 + kh.x + travel * clamp(kt, 0.0, 1.0), c.y + sink) let fl = Finish.cover(Material.sd_box(p, c, h - vec2(rw, rw), r - rw), px) let ksh = Finish.drop(p, kc, kh, 2.5, vec2(0.5, 1.1), 1.0 + px) * 0.45 * (1.0 - 0.6 * held) * live * fl res = Finish.over(res, vec4(mb_shade() * ksh, ksh)) let kd = Material.sd_box(p, kc, kh, 2.5) let kn = mb_normal(p, kc, kh, 2.5) var kcol = mb_goldface(p, kc, kh, kd, kn, 2.0) kcol = kcol * (1.0 + 0.06 * hover * live - 0.12 * held * live) kcol = mix(kcol, mb_gold(0.28), Finish.band(kd, 0.0, px, px) * 0.6) kcol = mix(kcol, vec3(0.84, 0.81, 0.75), 0.6 * dis) let ka = Finish.cover(kd, px) res = Finish.over(res, vec4(kcol * ka, ka)) // On: a diamond set in the key, glinting; off: an engraved dash. let gr = ks * 0.30 let gd = length(p - kc) - gr if on > 0.001 { let seat = Finish.cover(gd - 0.7, px) * on res = Finish.over(res, vec4(mb_gold(0.25) * seat, seat)) let gcol = mix(mb_gem(p - kc, gr, vec3(1.0, 1.0, 1.0), 0.3, 2.0), vec3(0.85, 0.84, 0.80), 0.6 * dis) let ga = Finish.cover(gd, px) * on res = Finish.over(res, vec4(gcol * ga, ga)) let gl = mb_glint(p - kc - vec2(gr * 0.35, -gr * 0.4), gr * 2.6, px) * on * live res = Finish.over(res, vec4(gl, gl, gl, gl)) } let dsh = mb_seg(p, kc - vec2(ks * 0.22, 0.0), kc + vec2(ks * 0.22, 0.0)) - 0.75 res = mb_mark(res, dsh, px, 1.0 - on, dis) let ed = Material.sd_box(p, c, h, r) res = Finish.over(res, vec4(0.70, 0.08, 0.15, 1.0) * Finish.band(ed, 0.0, 1.2, px) * error * live) return res * mb_fade(pos, size, m) } mod.widgets.ToggleFlat.draw_bg.vertex = fn() { let m = 10.0 self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m)) } mod.widgets.ToggleSlide.draw_bg.vertex = fn() { let m = 10.0 self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m)) } mod.widgets.ToggleRocker.draw_bg.vertex = fn() { let m = 10.0 self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m)) } mod.widgets.ToggleFlat.draw_bg.pixel = fn() { return mb_toggle(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.size, self.pill_aspect, self.mark_at_end, self.hover, max(self.pressed, self.down), self.disabled, self.active, mix(self.active, self.drag_pos, self.drag), self.focus, self.error) } // The slide switch is the toggle: the same gold frame and key. mod.widgets.ToggleSlide.draw_bg.size = uniform(20.0) mod.widgets.ToggleSlide.draw_bg.pill_aspect = uniform(1.9) mod.widgets.ToggleSlide.label_walk.margin.left = 38.0 mod.widgets.ToggleSlide.draw_bg.pixel = fn() { return mb_toggle(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.size, self.pill_aspect, self.mark_at_end, self.hover, max(self.pressed, self.down), self.disabled, self.active, mix(self.active, self.drag_pos, self.drag), self.focus, self.error) } // The rocker: a white marble plate in a gold rim, split at its middle. The // half in force is pressed in and turned to polished gold, its legend a // warm white; the raised half is the white stone, its legend brown: the O // at the left while off, the I at the right while on. mod.widgets.ToggleRocker.draw_bg.size = uniform(20.0) mod.widgets.ToggleRocker.draw_bg.pill_aspect = uniform(2.3) mod.widgets.ToggleRocker.label_walk.margin.left = 46.0 mod.widgets.ToggleRocker.draw_bg.pixel = fn() { let m = 10.0 let px = mb_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(mb_snap(x0, o.x, px), mb_snap(y0, o.y, px)) let hi = vec2(mb_snap(x0 + tw, o.x, px), mb_snap(y0 + th, o.y, px)) let r = 4.0 let rw = 1.7 let c = (lo + hi) * 0.5 let live = 1.0 - self.disabled let on = self.active let held = max(self.pressed, self.down) var face = mb_stone(p + mb_seed(self.rect_size), px, 0.5) face = mix(face, mb_ground(), 0.5 * self.disabled) var res = mb_box(p, lo, hi, r, rw, px, face, self.hover, 0.0, self.disabled, self.focus, 1.0) // The half in force: polished gold, pressed in under the rim, a dark // seam at the middle. let hw = (hi.x - lo.x) * 0.5 let sx = mix(lo.x + rw, c.x, on) let slo = vec2(sx, lo.y + rw) let shi = vec2(sx + hw - rw, hi.y - rw) let sc = (slo + shi) * 0.5 let sh = (shi - slo) * 0.5 let sd = Material.sd_box(p, sc, sh, max(r - rw, 0.5)) let sn = mb_normal(p, sc, sh, max(r - rw, 0.5)) var gcol = mb_goldface(p, sc, sh, sd, sn, 1.6) * (1.0 - 0.08 * held) let ins = Finish.inset(p, sc, sh, max(r - rw, 0.5), vec2(0.0, 1.2), 1.3) gcol = mix(gcol, gcol * 0.70, clamp(ins, 0.0, 1.0) * 0.6) gcol = mix(gcol, vec3(0.84, 0.81, 0.75), 0.6 * self.disabled) let ga = Finish.cover(sd, px) res = Finish.over(res, vec4(gcol * ga, ga)) let seam = Finish.cover(abs(p.x - c.x) - 0.5 * px, px) * Finish.cover(abs(p.y - c.y) - (th * 0.5 - rw), px) res = Finish.over(res, vec4(vec3(0.30, 0.21, 0.07) * seam * 0.7, seam * 0.7)) // The legends. let lc_o = vec2(lo.x + hw * 0.5 + 0.5, c.y) let lc_i = vec2(c.x + hw * 0.5 - 0.5, c.y) let s = th / 20.0 let d_o = abs(length((p - lc_o) * vec2(1.25, 1.0)) - 3.4 * s) - 0.65 * s let d_i = mb_seg(p, lc_i - vec2(0.0, 3.5 * s), lc_i + vec2(0.0, 3.5 * s)) - 0.65 * s let cream = vec3(1.0, 0.975, 0.90) let brown = vec3(0.30, 0.23, 0.12) let fade = mix(1.0, 0.45, self.disabled) let ao = Finish.cover(d_o, px) * fade let ai = Finish.cover(d_i, px) * fade res = Finish.over(res, vec4(mix(cream, brown, on) * ao, ao)) res = Finish.over(res, vec4(mix(brown, cream, on) * ai, ai)) let d = Material.sd_box(p, c, (hi - lo) * 0.5, r) res = Finish.over(res, vec4(0.70, 0.08, 0.15, 1.0) * Finish.band(d, 0.0, 1.2, px) * self.error * live) return res * mb_fade(self.pos, self.rect_size, m) } // The radio: a 15 point gold dome; chosen, it opens into a gold bezel with // a round brilliant diamond set in it, glinting. mod.widgets.RadioButtonFlat.draw_bg.vertex = fn() { let m = 8.0 self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m)) } mod.widgets.RadioButtonFlat.clip_x = false mod.widgets.RadioButtonFlat.clip_y = false mod.widgets.RadioButtonFlat.draw_bg.size = uniform(15.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 // A gold dome `rad` round `c`, satin rather than mirror: lit from the top, // a soft highlight high on it, its lower side a deeper gold, its foot // rolling under. let mb_golddome = fn(p: vec2, c: vec2, rad: float) -> vec3 { let q = (p - c) / max(rad, 0.5) let ql = min(length(q), 1.0) let n = q / max(length(q), 0.001) let e = sin(ql * 1.4) * dot(n, mb_lv()) / sin(1.4) var l = 0.50 + 0.26 * e + 0.30 * exp(-(e - 0.55) * (e - 0.55) / 0.03) l = l - 0.12 * smoothstep(0.82, 1.0, ql) return mb_gold(l) } mod.widgets.RadioButtonFlat.draw_bg.pixel = fn() { let m = 8.0 let px = mb_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(mb_snap(0.0, o.x, px), mb_snap(y0, o.y, px)) let hi = vec2(mb_snap(sz, o.x, px), mb_snap(y0 + sz, o.y, px)) let live = 1.0 - self.disabled let on = self.active let rad = sz * 0.5 let sink = 0.4 * self.down * live let c = (lo + hi) * 0.5 + vec2(0.0, sink) let sc = mb_shadow(p, (lo + hi) * 0.5, vec2(rad, rad), rad, px, self.hover, self.down, self.disabled) var res = vec4(mb_shade() * sc, sc) let d = length(p - c) - rad let a = Finish.cover(d, px) if self.focus * live > 0.001 { res = Finish.over(res, mb_leak(d, a) * self.focus * live) } var col = mb_golddome(p, c, rad) * (1.0 + 0.06 * self.hover * live - 0.12 * self.down * live) // Chosen: the bezel's rim and the stone in it. let rw = 1.8 let n = (p - c) / max(length(p - c), 0.001) let bez = mb_rim(d, rw, n, p) let gr = rad - rw - 0.3 let gd = length(p - c) - gr let gem = mb_gem(p - c, gr, vec3(1.0, 1.0, 1.0), 0.2, 2.0) var chosen = mix(bez, gem, Finish.cover(gd, px)) chosen = mix(chosen, vec3(0.30, 0.21, 0.07), Finish.band(gd, -px, 0.0, px) * 0.6) col = mix(col, chosen, on) col = mix(col, vec3(0.84, 0.81, 0.75), 0.6 * self.disabled) col = mb_focus_rim(col, d, rw, px, self.focus * live) res = Finish.over(res, vec4(col * a, a)) let gl = mb_glint(p - c - vec2(gr * 0.3, -gr * 0.35), gr * 2.4, px) * on * live res = Finish.over(res, vec4(gl, gl, gl, gl)) return res * mb_fade(self.pos, self.rect_size, m) } // The radio tab: a cell of white marble in a gold rim; chosen, it is // pressed in and its floor turns to polished gold. mod.widgets.RadioButtonTabFlat.draw_bg.vertex = fn() { let m = 10.0 self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m)) } mod.widgets.RadioButtonTabFlat.clip_x = false mod.widgets.RadioButtonTabFlat.clip_y = false mod.widgets.RadioButtonTabFlat.padding = mod.turtle.Inset{left: 0.0 right: 12.0 top: 4.0 bottom: 4.0} mod.widgets.RadioButtonTabFlat.min_height = 26.0 mod.widgets.RadioButtonTabFlat.draw_bg.pixel = fn() { let m = 10.0 let px = mb_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(mb_snap(0.0, o.x, px), mb_snap(0.0, o.y, px)) let hi = vec2(mb_snap(self.rect_size.x, o.x, px), mb_snap(self.rect_size.y, o.y, px)) let sel = self.active let c = (lo + hi) * 0.5 let h = (hi - lo) * 0.5 let rw = 3.0 let hf = h - vec2(rw, rw) let df = Material.sd_box(p, c, hf, 2.0) let nf = mb_normal(p, c, hf, 2.0) var face = mb_stone(p + mb_seed(self.rect_size), px, 0.45) * (1.0 + 0.03 * self.hover) var gold = mb_goldface(p, c, hf, df, nf, 1.8) let ins = Finish.inset(p, c, hf, 2.0, vec2(0.0, 1.4), 1.6) gold = mix(gold, gold * 0.68, clamp(ins, 0.0, 1.0) * 0.65) face = mix(face, gold, sel) face = mix(face, mb_ground(), 0.5 * self.disabled) return mb_box(p, lo, hi, 3.5, rw, px, face, self.hover, max(self.down, sel), self.disabled, self.focus, 1.0) * mb_fade(self.pos, self.rect_size, m) } mod.widgets.RadioButtonTabFlat.draw_text.color = #5c4a2e mod.widgets.RadioButtonTabFlat.draw_text.color_hover = #2a2112 mod.widgets.RadioButtonTabFlat.draw_text.color_active = #26190a mod.widgets.RadioButtonTabFlat.draw_text.color_disabled = #a89c86 mod.widgets.RadioButtonTabFlat.draw_text.get_color = fn() { return self.color .mix(self.color_focus, self.focus) .mix(self.color_hover, self.hover) .mix(self.color_down, self.down) .mix(self.color_active, self.active) .mix(self.color_disabled, self.disabled) } // ---------------------------------------------------------------- sliders // THE RAIL: a gold tube from `lo` to `hi` with round ends, a polished bead // along each edge round a satin gold floor a step deeper; the value lies in // it as a row of pavé diamonds from `f0` to `f1`. Premultiplied over `dst`. let mb_rail = fn(dst: vec4, p: vec2, lo: vec2, hi: vec2, px: float, f0: float, f1: float, dis: float) -> vec4 { let c = (lo + hi) * 0.5 let h = (hi - lo) * 0.5 let r = min(h.x, h.y) let d = Material.sd_box(p, c, h, r) let n = mb_normal(p, c, h, r) let a = Finish.cover(d, px) let sh = Finish.drop(p, c, h, r, vec2(0.0, 0.8), 0.8 + px * 0.5) * 0.25 * (1.0 - a) * (1.0 - 0.6 * dis) var res = Finish.over(dst, vec4(mb_shade() * sh, sh)) let rw = clamp(0.30 * (hi.y - lo.y), 1.0, 1.8) let t = clamp((p.y - lo.y) / max(hi.y - lo.y, 1.0), 0.0, 1.0) var col = mix(mb_gold(0.26), mb_gold(0.42), t) col = mix(col, mb_rim(d, rw, n, p), Finish.band(d, 0.0, rw, px)) col = mix(col, vec3(0.84, 0.81, 0.75), 0.6 * dis) res = Finish.over(res, vec4(col * a, a)) let prad = max(h.y - rw - 0.15, 0.6) if f1 > f0 + 0.5 && dis < 0.5 { res = mb_pave_row(res, p, f0, f1, c.y, prad, prad * 2.15, px) } return res } // THE THUMB: a step-cut stone (an emerald cut, `hw` x `hv` either side of // `c`, its corners cut) held in a polished gold frame with a claw at each // corner, throwing its own shadow; under the hand it glints, held it sits // down. `ink` the stone. let mb_thumb = fn(dst: vec4, p: vec2, c0: vec2, hw: float, hv: float, ink: vec3, px: float, hover: float, drag: float, focus: float, dis: float) -> vec4 { let live = 1.0 - dis let c = c0 + vec2(0.0, 0.4 * drag * live) let h = vec2(hw, hv) let sc = mb_shadow(p, c0, h, 1.5, px, hover, drag, dis) var res = Finish.over(dst, vec4(mb_shade() * sc, sc)) let d = Finish.sd_chamfer(p, c, h, 2.0) let a = Finish.cover(d, px) if focus * live > 0.001 { res = Finish.over(res, mb_leak(d, a) * focus * live) } let rw = 1.3 let n = mb_normal(p, c, h, 1.5) let hg = h - vec2(rw, rw) var gem = mb_gstep(p - c, hg, ink, 3.0, 0.0, 1.0) gem = gem * (1.0 + 0.06 * hover * live) var col = mix(mb_rim(d, rw, n, p), gem, Finish.cover(d + rw, px)) col = mix(col, vec3(0.30, 0.21, 0.07), Finish.band(d, rw, rw + px, px) * 0.5) col = mix(col, vec3(0.84, 0.81, 0.75), 0.6 * dis) col = mb_focus_rim(col, d, rw, px, focus * live) res = Finish.over(res, vec4(col * a, a)) // The claws: four gold beads at the corners. let q = abs(p - c) - (h - vec2(1.1, 1.1)) let cd = length(q) - 1.15 let ca = Finish.cover(cd, px) let cn = (p - c) / max(length(p - c), 0.001) let ccol = mix(mb_gold(mb_env(dot(cn, mb_lv()) * 0.7)), vec3(0.84, 0.81, 0.75), 0.6 * dis) res = Finish.over(res, vec4(ccol * ca, ca)) let gl = mb_glint(p - c - vec2(hw * 0.35, -hv * 0.45), 4.0 + 3.0 * hover, px) * (0.35 + 0.65 * max(hover, drag)) * live return Finish.over(res, vec4(gl, gl, gl, gl)) } // A ROUND STONE THUMB: a round brilliant `rad` round `c` in a gold bezel, // its shadow under it; for the band's ends and the minimal slider. Its // stone is traced (`flat` 0) or, for the tiny stones (the progress bar's // head, the divider's stone, the needle's pivot), the flat sparkle of facets // (`flat` 1), which costs no tracer. // The setting round a stone whose colour here is `gem`. let mb_gemset = fn(dst: vec4, p: vec2, c0: vec2, rad: float, gem0: vec3, px: float, hover: float, drag: float, focus: float, dis: float) -> vec4 { let live = 1.0 - dis let c = c0 + vec2(0.0, 0.4 * drag * live) let sc = mb_shadow(p, c0, vec2(rad, rad), rad, px, hover, drag, dis) var res = Finish.over(dst, vec4(mb_shade() * sc, sc)) let d = length(p - c) - rad let a = Finish.cover(d, px) if focus * live > 0.001 { res = Finish.over(res, mb_leak(d, a) * focus * live) } let rw = clamp(0.24 * rad, 1.1, 1.8) let n = (p - c) / max(length(p - c), 0.001) var gem = gem0 * (1.0 + 0.06 * hover * live) var col = mix(mb_rim(d, rw, n, p), gem, Finish.cover(d + rw, px)) col = mix(col, vec3(0.30, 0.21, 0.07), Finish.band(d, rw, rw + px, px) * 0.5) col = mix(col, vec3(0.84, 0.81, 0.75), 0.6 * dis) col = mb_focus_rim(col, d, rw, px, focus * live) res = Finish.over(res, vec4(col * a, a)) let gl = mb_glint(p - c - vec2(rad * 0.25, -rad * 0.3), rad * 1.6 + 2.0 * hover, px) * (0.45 + 0.55 * max(hover, drag)) * live return Finish.over(res, vec4(gl, gl, gl, gl)) } let mb_gemthumb = fn(dst: vec4, p: vec2, c0: vec2, rad: float, ink: vec3, px: float, hover: float, drag: float, focus: float, dis: float) -> vec4 { let c = c0 + vec2(0.0, 0.4 * drag * (1.0 - dis)) let rw = clamp(0.24 * rad, 1.1, 1.8) let gem = mb_gem(p - c, rad - rw, mb_gink(ink), 0.4, 2.0) return mb_gemset(dst, p, c0, rad, gem, px, hover, drag, focus, dis) } let mb_gemdot = fn(dst: vec4, p: vec2, c0: vec2, rad: float, px: float) -> vec4 { let rw = clamp(0.24 * rad, 1.1, 1.8) let gem = mb_brilliant(p - c0, rad - rw, mb_diamond(), 0.4, px) return mb_gemset(dst, p, c0, rad, gem, px, 0.0, 0.0, 0.0, 0.0) } // The bar slider: an 8 point gold rail under its label row, the value a row // of pavé diamonds in it, and a citrine emerald-cut riding it, 13 x 16 // points in its claws. mod.widgets.SliderFlat.clip_x = false mod.widgets.SliderFlat.clip_y = false mod.widgets.SliderFlat.draw_bg.vertex = fn() { let m = 10.0 self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m)) } mod.widgets.SliderFlat.draw_bg.pixel = fn() { let m = 10.0 let px = mb_px(self.draw_pass.dpi_factor) let o = self.rect_pos let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let rad = 8.0 let cy = mb_snap(20.0 + (self.rect_size.y - 20.0) * 0.5, o.y, px) let lo = vec2(mb_snap(0.0, o.x, px), cy - 4.0) let hi = vec2(mb_snap(self.rect_size.x, o.x, px), cy + 4.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 + 2.0, min(ox, hx), self.arc_origin) let f1 = mix(hx - 5.0, max(ox, hx), self.arc_origin) var res = mb_rail(vec4(0.0, 0.0, 0.0, 0.0), p, lo, hi, px, f0, f1, self.disabled) return mb_thumb(res, p, vec2(hx, cy), 6.5, 8.2, mb_citrine_gem(), px, self.hover, self.drag, self.focus, self.disabled) * mb_fade(self.pos, self.rect_size, m) } // The minimal slider: a slim gold rail with the value in pavé, and a small // round diamond riding it. mod.widgets.SliderMinimal.draw_bg.pixel = fn() { let px = mb_px(self.draw_pass.dpi_factor) let o = self.rect_pos let p = self.pos * self.rect_size let cy = mb_snap(self.offset_y + (self.rect_size.y - self.offset_y) * 0.5, o.y, px) let lo = vec2(mb_snap(0.0, o.x, px), cy - 2.5) let hi = vec2(mb_snap(self.rect_size.x, o.x, px), cy + 2.5) let lo2 = min(self.origin_pos, self.slide_pos) let hi2 = max(self.origin_pos, self.slide_pos) let f0 = mix(1.5, lo2 * self.rect_size.x, self.arc_origin) let f1 = mix(self.slide_pos * self.rect_size.x - 3.0, hi2 * self.rect_size.x, self.arc_origin) var res = mb_rail(vec4(0.0, 0.0, 0.0, 0.0), p, lo, hi, px, f0, f1, self.disabled) let hx = clamp(self.slide_pos * self.rect_size.x, 4.5, self.rect_size.x - 4.5) return mb_gemthumb(res, p, vec2(hx, cy), 4.5, mb_diamond(), px, self.hover, self.drag, 0.0, self.disabled) } // The range slider: the bar slider's rail under its label row, the band in // pavé between its ends, and at each end a round emerald in a gold bezel. mod.widgets.RangeSliderFlat.handle_size = 13.0 mod.widgets.RangeSliderFlat.draw_bg.vertex = fn() { let m = 10.0 self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m)) } mod.widgets.RangeSliderFlat.draw_bg.pixel = fn() { let m = 10.0 let px = mb_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 top = self.label_px let track_h = self.rect_size.y - top let mid = mb_snap(top + track_h * 0.5, o.y, px) let lo = vec2(mb_snap(self.inset_px * 0.5, o.x, px), mid - 3.5) let hi = vec2(mb_snap(self.rect_size.x - self.inset_px * 0.5, o.x, px), mid + 3.5) let span_x = self.rect_size.x - self.inset_px * 2.0 - self.handle_px let s0 = self.inset_px + self.handle_px * 0.5 + self.start_pos * span_x let s1 = self.inset_px + self.handle_px * 0.5 + self.end_pos * span_x var res = mb_rail(vec4(0.0, 0.0, 0.0, 0.0), p, lo, hi, px, s0 + 4.0, s1 - 4.0, self.disabled) let rad = clamp(track_h * 0.5 - 0.5, 4.5, 6.5) // Each pixel traces only the nearer of the two stones. let ns = step(abs(p.x - s0), abs(p.x - s1)) let hot_t = mix(self.hot_end, self.hot_start, ns) return mb_gemthumb(res, p, vec2(mix(s1, s0, ns), mid), rad, mb_emerald(), px, hot_t, self.drag * hot_t, self.focus, self.disabled) * mb_fade(self.pos, self.rect_size, m) } // The round slider: a label column, then the bar slider's rail, pavé and // citrine; the rail ends short of the right edge for the value. mod.widgets.SliderRoundFlat.clip_x = false mod.widgets.SliderRoundFlat.clip_y = false mod.widgets.SliderRoundFlat.draw_bg.vertex = fn() { let m = 10.0 self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m)) } mod.widgets.SliderRoundFlat.draw_bg.pixel = fn() { let m = 10.0 let px = mb_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 = mb_snap(self.rect_size.y * 0.5, o.y, px) let x0 = mb_snap(self.label_size, o.x, px) let lo = vec2(x0, cy - 4.0) let hi = vec2(mb_snap(self.rect_size.x - 37.0, o.x, px), cy + 4.0) let span = hi.x - lo.x - rad * 2.0 let hx = lo.x + rad + self.slide_pos * span var res = mb_rail(vec4(0.0, 0.0, 0.0, 0.0), p, lo, hi, px, lo.x + 2.0, hx - 5.0, self.disabled) return mb_thumb(res, p, vec2(hx, cy), 6.5, 8.2, mb_citrine_gem(), px, self.hover, self.drag, self.focus, self.disabled) * mb_fade(self.pos, self.rect_size, m) } // THE FADER: a slim gold rail standing in the fader's middle, its value in // pavé from the stop to the cap, gold ticks either side (every fifth long), // and the cap an emerald baguette 22 points across and 14 along, held in a // polished gold frame with claws. Laid out in the fader's own frame (`ab`: // along the track from the value's zero, up the screen when it stands; // across it), so SliderFaderY is the same drawing stood on end. The travel // law is the widget's, so what is drawn is what is dragged. mod.widgets.SliderFader.cap_size = 22.0 mod.widgets.SliderFader.track_inset = 4.0 mod.widgets.SliderFader.draw_bg.track_size = uniform(5.0) mod.widgets.SliderFader.draw_bg.cap_cross = uniform(0.24) mod.widgets.SliderFader.clip_x = false mod.widgets.SliderFader.clip_y = false mod.widgets.SliderFader.draw_bg.vertex = fn() { let m = 8.0 self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m)) } mod.widgets.SliderFader.draw_bg.pixel = fn() { let m = 8.0 let px = mb_px(self.draw_pass.dpi_factor) let sz = self.rect_size let p = self.pos * (sz + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let v = self.is_vertical let dis = self.disabled let live = 1.0 - dis let along = mix(sz.x, sz.y, v) let across = mix(sz.y, sz.x, v) // Work in the fader's frame, then hand screen points to the helpers. let ab = vec2(mix(p.x, sz.y - p.y, v), mix(p.y, p.x, v)) let mid = across * 0.5 let travel = max(along - self.inset_px * 2.0 - self.cap_px, 1.0) let foot = self.inset_px + self.cap_px * 0.5 let at = foot + self.slide_pos * travel // A frame-space point to a screen point. let tin = self.inset_px + 3.0 let tw = self.track_size // The ticks, gold hairlines either side of the rail. var res = vec4(0.0, 0.0, 0.0, 0.0) let n = 24.0 let k = floor((ab.x - foot) / travel * n + 0.5) let tk = foot + k / n * travel let major = 1.0 - step(0.5, k - 5.0 * floor(k / 5.0 + 0.0001)) let tlen = mix(3.0, 5.0, major) let side = abs(ab.y - mid) - (tw * 0.5 + 2.5) let inrange = step(-0.01, k) * step(k, n + 0.01) let tick = Finish.cover(abs(ab.x - tk) - px * mix(0.5, 0.8, major), px) * Finish.cover(max(-side, side - tlen), px) * inrange let tcol = mix(mb_gold(0.45), vec3(0.84, 0.81, 0.75), 0.6 * dis) res = Finish.over(res, vec4(tcol * tick * 0.9, tick * 0.9)) // The rail and its value, laid out across the screen axis. var lo = foot + self.fill_lo * travel var hi = foot + self.fill_hi * travel if self.fill_lo < 0.0005 { lo = tin } if self.fill_hi > 0.9995 { hi = along - tin } let q = vec2(ab.x, ab.y) let rail = mb_rail(vec4(0.0, 0.0, 0.0, 0.0), q, vec2(tin, mid - tw * 0.5), vec2(along - tin, mid + tw * 0.5), px, lo + 1.0, hi - 1.0, dis) res = Finish.over(res, rail) // The cap, in screen orientation so its light falls from the top. let cs = mix(vec2(at, mid), vec2(mid, sz.y - at), v) let capw = min(across * 0.5 - 1.0, 11.0) let thumb = mb_thumb(vec4(0.0, 0.0, 0.0, 0.0), p, cs, mix(7.0, capw, v), mix(capw, 7.0, v), mb_emerald(), px, self.hover, self.drag, self.focus, dis) res = Finish.over(res, thumb) return res * mb_fade(self.pos, self.rect_size, m) } // ---------------------------------------------------------------- knobs // A KNOB is a cut stone in a gold bezel, the pictures' gem knobs: a round // brilliant that turns with the value, in a polished gold bezel whose outer // edge is milgrained (a row of fine beads), the pointer an engraved notch // across the bezel. Round it a slim gold ring over the whole sweep, and lit // from the start of the sweep (or from the origin) to the value a row of // pavé diamonds set in it. The big dial adds a ring of gold ticks outside. // Under the hand the stone glints and the bezel brightens; held, it glints // more; focus lays the gold light round the bezel; disabled, all fades // toward the stone. Angles run from straight down, clockwise, the gap at // the bottom. // The distance from `q` (at the angle `aq`) to a band `hw` either side of // the arc of radius `ra` from the angle `a0` to `a1`, its ends round; in .y // how far along the arc (0..1) the nearest point lies. let mb_arc_d = fn(q: vec2, aq: float, ra: float, hw: float, a0: float, a1: float) -> vec2 { let r = length(q) if aq >= a0 && aq <= a1 { return vec2(abs(r - ra) - hw, (aq - a0) / max(a1 - a0, 0.0001)) } let d0 = length(q - vec2(-sin(a0), cos(a0)) * ra) let d1 = length(q - vec2(-sin(a1), cos(a1)) * ra) if d0 < d1 { return vec2(d0 - hw, 0.0) } return vec2(d1 - hw, 1.0) } let mb_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, dis: float, ink: vec3, ticks: float) -> vec4 { let q = p - c let ql = length(q) let live = 1.0 - dis 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 aq = atan2(-q.x, q.y) if aq < 0.0 { aq = aq + 2.0 * PI } // The sizes: the ticks (on the dial) outermost, then the ring and its // pavé, then the bezel and the stone. let tk = ticks * clamp(0.10 * rr, 3.0, 6.0) let prad = clamp(0.045 * rr, 1.05, 2.1) let ra = rr - tk - prad - 0.6 let rb = ra - prad - clamp(0.06 * rr, 1.8, 4.0) let bw = clamp(0.16 * rb, 1.8, 5.5) let rg = rb - bw var res = vec4(0.0, 0.0, 0.0, 0.0) // THE TICKS (the dial only): eleven gold strokes outside the ring. if ticks > 0.5 { let tn = 10.0 let ti = floor(clamp((aq - start) / sweep, 0.0, 1.0) * tn + 0.5) let ta = start + sweep * ti / tn let td = vec2(-sin(ta), cos(ta)) let tdist = mb_seg(q, td * (rr - tk + 0.8), td * (rr - 0.8)) - 0.55 let tcov = Finish.cover(tdist, px) let tcol = mix(mb_gold(0.5), vec3(0.84, 0.81, 0.75), 0.6 * dis) res = Finish.over(res, vec4(tcol * tcov, tcov)) } // THE RING: a slim gold wire over the whole sweep, its shadow under it. let tr = mb_arc_d(q, aq, ra, prad * 0.55, start, start + sweep) let rsh = Finish.cover(tr.x - 0.9, 1.2) * 0.18 * (1.0 - Finish.cover(tr.x, px)) res = Finish.over(res, vec4(mb_shade() * rsh, rsh)) let ta2 = Finish.cover(tr.x, px) let rn = clamp((ql - ra) / max(prad * 0.55, 0.3), -1.0, 1.0) var rcol = mb_gold(mb_env(rn * dot(q / max(ql, 0.01), mb_lv()) * 0.9)) rcol = mix(rcol, vec3(0.84, 0.81, 0.75), 0.6 * dis) res = Finish.over(res, vec4(rcol * ta2, ta2)) // THE PAVÉ: from the start (or the origin) to the value, a diamond every // so many degrees on the ring. if dis < 0.5 { let pitch = prad * 2.3 / max(ra, 1.0) let ci = floor((aq - a0) / pitch) let ca = a0 + (ci + 0.5) * pitch let inside = step(a0, ca) * step(ca, a1) let pc = vec2(-sin(ca), cos(ca)) * ra res = Finish.over(res, mb_pave(q - pc, prad, Finish.hash(vec2(ci, 9.1)), px) * inside) } // THE BEZEL'S SHADOW on the stone ground. let sc = Finish.drop(p, c, vec2(rb, rb), rb, vec2(0.0, 0.10 * rb + 0.6), 0.10 * rb + 0.6) * 0.32 * (1.0 - 0.6 * dis) let db = ql - rb let ab_ = Finish.cover(db, px) res = Finish.over(res, vec4(mb_shade() * sc * (1.0 - ab_), sc * (1.0 - ab_))) if focus * live > 0.001 { res = Finish.over(res, mb_leak(db, ab_) * focus * live) } // THE BEZEL: a polished bead, milgrained along its outer third. let n = q / max(ql, 0.01) let u = clamp(-db / bw, 0.0, 1.0) var l = mb_bead(u, n) let nb = max(floor(2.0 * PI * rb / 1.5), 8.0) let mg = cos(aq * nb) * (1.0 - smoothstep(0.18, 0.36, u)) l = l + 0.16 * mg * smoothstep(0.0, 0.08, u) var bcol = mb_gold(l) * (1.0 + 0.05 * hover * live) bcol = mix(bcol, vec3(0.30, 0.21, 0.07), Finish.band(db, 0.0, px, px) * 0.35) // The pointer: an engraved notch across the bezel at the value. let dirv = vec2(-sin(ve), cos(ve)) let nw = max(0.15 * bw, 0.75) let nd = mb_seg(q, dirv * (rg - 0.2), dirv * (rb - 0.3)) - nw let notch = Finish.cover(nd, px) let lip = Finish.cover(mb_seg(q - vec2(0.0, 0.6), dirv * (rg - 0.2), dirv * (rb - 0.3)) - nw, px) * (1.0 - notch) bcol = mix(bcol, vec3(1.0, 0.95, 0.78), lip * 0.6) bcol = mix(bcol, vec3(0.20, 0.13, 0.03), notch * 0.9) // THE STONE, turning with the value; a dark seat where it meets gold. let dg = ql - rg var gcol = mb_gem_aa(q, rg, mb_gink(ink), ve, px, 4.0, 2.0) * (1.0 + 0.04 * hover * live) gcol = mix(gcol, vec3(0.25, 0.18, 0.06), Finish.band(dg, -px, 0.0, px) * 0.6) var col = mix(bcol, gcol, Finish.cover(dg, px)) col = mix(col, mix(mb_ground(), vec3(0.80, 0.78, 0.72), 0.5), 0.55 * dis) col = mb_focus_rim(col, db, bw, px, focus * live) res = Finish.over(res, vec4(col * ab_, ab_)) // The glints, the larger one brighter under the hand. let g1 = mb_glint(q - vec2(rg * 0.32, -rg * 0.38), rg * 0.95 + 2.0 * hover, px) * (0.55 + 0.45 * max(hover, held)) let g2 = mb_glint(q - vec2(-rg * 0.45, rg * 0.30), rg * 0.45, px) * 0.45 var gsum = g1 + g2 // A white diamond glints at its facet corners instead. if max(ink.x, max(ink.y, ink.z)) - min(ink.x, min(ink.y, ink.z)) < 0.15 { gsum = mb_dia_glints(q, rg, ve, px) * (0.75 + 0.25 * max(hover, held)) } let gl = clamp(gsum, 0.0, 1.0) * live * Finish.cover(dg + 0.5, px) return Finish.over(res, vec4(gl, gl, gl, gl)) } // The panel knob is 36 points across with no side margin: in a row beside // a big dial it then keeps inside its own column, its ring a few points // clear of the next column's frame. mod.widgets.RotaryKnob.width = 36.0 mod.widgets.RotaryKnob.height = 56.0 mod.widgets.RotaryKnob.margin = mod.turtle.Inset{left: 0.0 right: 0.0 top: 4.0 bottom: 4.0} mod.widgets.RotaryKnob.clip_x = false mod.widgets.RotaryKnob.clip_y = false mod.widgets.RotaryKnob.draw_bg.vertex = fn() { let m = 10.0 self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m)) } // The panel knob is an emerald. mod.widgets.RotaryKnob.draw_bg.pixel = fn() { let m = 10.0 let px = mb_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 mb_knob(p, c, dia * 0.5 - 0.5, px, self.slide_pos, self.origin_pos, self.arc_origin, self.gap, self.hover, self.drag, self.focus, self.disabled, mb_emerald(), 0.0) * mb_fade(self.pos, self.rect_size, m) } mod.widgets.RotaryFlat.clip_x = false mod.widgets.RotaryFlat.clip_y = false mod.widgets.RotaryFlat.draw_bg.vertex = fn() { let m = 10.0 self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m)) } // The dial is a white diamond with its ring of ticks. mod.widgets.RotaryFlat.draw_bg.pixel = fn() { let m = 10.0 let px = mb_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 mb_knob(p, c, dia * 0.5 - 0.5, px, self.slide_pos, self.origin_pos, self.arc_origin, self.gap, self.hover, self.drag, self.focus, self.disabled, mb_diamond(), 1.0) * mb_fade(self.pos, self.rect_size, m) } // The dial's readout stands as a plain line under it. mod.widgets.Rotary.text_input.min_height = 0.0 mod.widgets.Rotary.text_input.padding.top = 0.0 mod.widgets.Rotary.text_input.padding.bottom = 0.0 mod.widgets.RotaryKnob.draw_text.text_style = mod.theme.font_bold{font_size: 8.5} // ---------------------------------------------------------------- fields // A FIELD is white marble set in a double gold rim (a broad bead, a dark // groove, a slim bead), sunk a little under it: a soft shade under its upper // rim, a line of milgrain beads inside it, the veins calm under the words. // Focused, the rim burns brighter and spills its gold light. `lift` how // much shadow it throws. let mb_field = fn(p: vec2, lo: vec2, hi: vec2, r: float, px: float, seed: vec2, hover: float, focus: float, dis: float, lift: float) -> vec4 { let hh = hi.y - lo.y let full = step(20.0, hh) let rw = clamp(0.177 * hh, 1.8, 4.6) var face = mb_stone(p + seed, px, 0.42) * (1.0 + 0.02 * hover) let c = (lo + hi) * 0.5 let h = (hi - lo) * 0.5 let ins = Finish.inset(p, c, h - vec2(rw, rw), max(r - rw, 0.5), vec2(0.0, 1.5), 1.8) face = mix(face, face * 0.88, clamp(ins, 0.0, 1.0) * 0.6) face = mix(face, mb_milgrain(face, p, c, h, r, rw + 1.0, 0.6, 1.9, px), full) face = mix(face, mb_ground(), 0.45 * dis) return mb_box2(p, lo, hi, min(r, 0.3 * hh), rw, px, face, hover * 0.3, 0.0, dis, focus, lift, full) } mod.widgets.TextInputFlat.clip_x = false mod.widgets.TextInputFlat.clip_y = false mod.widgets.TextInputFlat.draw_bg.vertex = fn() { let m = 8.0 self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m)) } mod.widgets.TextInputFlat.draw_bg.pixel = fn() { if self.color.a < 0.01 { return vec4(0.0, 0.0, 0.0, 0.0) } let m = 8.0 let px = mb_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(mb_snap(0.0, o.x, px), mb_snap(0.0, o.y, px)) let hi = vec2(mb_snap(self.rect_size.x, o.x, px), mb_snap(self.rect_size.y, o.y, px)) return mb_field(p, lo, hi, 5.0, px, mb_seed(self.rect_size), self.hover, self.focus, self.disabled, 0.7) * mb_fade(self.pos, self.rect_size, m) } mod.widgets.TextInputFlat.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 4.0 bottom: 4.0} // A field well (a field whose input is drawn by another widget) wears the // same face. mod.widgets.FieldWell.clip_x = false mod.widgets.FieldWell.clip_y = false mod.widgets.FieldWell.draw_bg.vertex = fn() { let m = 8.0 self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m)) } mod.widgets.FieldWell.draw_bg.pixel = fn() { let m = 8.0 let px = mb_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(mb_snap(0.0, o.x, px), mb_snap(0.0, o.y, px)) let hi = vec2(mb_snap(self.rect_size.x, o.x, px), mb_snap(self.rect_size.y, o.y, px)) return mb_field(p, lo, hi, 5.0, px, mb_seed(self.rect_size), self.hover, self.focus, self.disabled, 0.7) * mb_fade(self.pos, self.rect_size, m) } mod.widgets.TextInputFlat.draw_cursor.color = #3a2e1c mod.widgets.TextInputFlat.draw_text.color = #3a2e1c mod.widgets.TextInputFlat.draw_text.color_empty = #6e5f46 mod.widgets.TextInputFlat.draw_text.color_disabled = #8f826a // The number field: a field; its stepper two small gold keys stacked at // the right, parted by a dark seam, their chevrons engraved. mod.widgets.NumberField.clip_x = false mod.widgets.NumberField.clip_y = false mod.widgets.NumberField.draw_bg.vertex = fn() { let m = 8.0 self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m)) } mod.widgets.NumberField.draw_bg.pixel = fn() { let m = 8.0 let px = mb_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(mb_snap(0.0, o.x, px), mb_snap(0.0, o.y, px)) let hi = vec2(mb_snap(self.rect_size.x, o.x, px), mb_snap(self.rect_size.y, o.y, px)) return mb_field(p, lo, hi, 5.0, px, mb_seed(self.rect_size), self.hover, self.focus, self.disabled, 0.7) * mb_fade(self.pos, self.rect_size, m) } mod.widgets.NumberField.spin_width = 16.0 mod.widgets.NumberSpin.width = 16.0 mod.widgets.NumberSpin.draw_text.color = #00000000 mod.widgets.NumberSpin.draw_text.color_disabled = #00000000 mod.widgets.NumberSpin.draw_bg.pixel = fn() { let px = mb_px(self.draw_pass.dpi_factor) let o = self.rect_pos let p = self.pos * self.rect_size let x0 = mb_snap(0.5, o.x, px) let x1 = mb_snap(self.rect_size.x - 4.6, o.x, px) let y0 = mb_snap(5.2, o.y, px) let y1 = mb_snap(self.rect_size.y - 5.2, o.y, px) let mid = mb_snap(self.rect_size.y * 0.5, o.y, px) let live = 1.0 - self.disabled let tu = step(p.y, mid) let hot = mix(max(0.0, -self.hot), max(0.0, self.hot), tu) * live let dn = mix(max(0.0, -self.down), max(0.0, self.down), tu) * live let lo = vec2(x0, mix(mid + px * 0.5, y0, tu)) let hi = vec2(x1, mix(y1, mid - px * 0.5, tu)) let c = (lo + hi) * 0.5 let h = (hi - lo) * 0.5 let d = Material.sd_box(p, c, h, 1.5) let n = mb_normal(p, c, h, 1.5) let a = Finish.cover(d, px) var col = mb_goldface(p, c, h, d, n, 1.2) * (1.0 + 0.07 * hot - 0.10 * dn) col = mix(col, vec3(0.84, 0.81, 0.75), 0.6 * self.disabled) var res = vec4(min(col, vec3(1.0, 1.0, 1.0)) * a, a) let cx = (x0 + x1) * 0.5 let cu = (y0 + mid) * 0.5 let cd = (mid + y1) * 0.5 let du = min(mb_seg(p, vec2(cx - 2.2, cu + 0.9), vec2(cx, cu - 1.0)), mb_seg(p, vec2(cx, cu - 1.0), vec2(cx + 2.2, cu + 0.9))) let dd = min(mb_seg(p, vec2(cx - 2.2, cd - 0.9), vec2(cx, cd + 1.0)), mb_seg(p, vec2(cx, cd + 1.0), vec2(cx + 2.2, cd - 0.9))) let k = Finish.cover(min(du, dd) - 0.55, px) * mix(1.0, 0.4, self.disabled) return Finish.over(res, vec4(vec3(0.20, 0.13, 0.03) * k, k)) } // The chevron of a drop-down, engraved: 7 x 4 points in a 1.4 point stroke. let mb_chevron = fn(dst: vec4, p: vec2, cc: vec2, ink: vec3, open: float, px: float) -> vec4 { let s = mix(1.0, -1.0, open) let dk = min(mb_seg(p, vec2(cc.x - 3.5, cc.y - 1.8 * s), vec2(cc.x, cc.y + 1.9 * s)), mb_seg(p, vec2(cc.x, cc.y + 1.9 * s), vec2(cc.x + 3.5, cc.y - 1.8 * s))) - 0.7 let k = Finish.cover(dk, px) return Finish.over(dst, vec4(ink * k, k)) } // THE DROP-DOWN: white marble in a chunky gold rim, a line of milgrain // inside it, with a square key of polished // gold at its right end carrying the chevron, as the pictures' selectors; // open, the key is pressed in and its chevron turns up. mod.widgets.DropDownFlat.padding = mod.turtle.Inset{left: 12.0 right: 30.0 top: 4.0 bottom: 4.0} mod.widgets.DropDownFlat.clip_x = false mod.widgets.DropDownFlat.clip_y = false mod.widgets.DropDownFlat.draw_bg.vertex = fn() { let m = 10.0 self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m)) } let mb_dropdown = fn(pos: vec2, size: vec2, o: vec2, dpi: float, hover: float, down: float, open: float, dis: float, focus: float, lift: float) -> vec4 { let m = 10.0 let px = mb_px(dpi) let p = pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let lo = vec2(mb_snap(0.0, o.x, px), mb_snap(0.0, o.y, px)) let hi = vec2(mb_snap(size.x, o.x, px), mb_snap(size.y, o.y, px)) let live = 1.0 - dis let rw = 3.8 let held = max(down, open) var face = mb_stone(p + mb_seed(size), px, 0.42) * (1.0 + 0.03 * hover * live) face = mb_milgrain(face, p, (lo + hi) * 0.5, (hi - lo) * 0.5, 4.5, rw + 1.0, 0.6, 1.9, px) // The key: a square of gold inside the rim at the right end. let ks = (hi.y - lo.y) - 2.0 * rw let kc = vec2(hi.x - rw - ks * 0.5, (lo.y + hi.y) * 0.5) let kh = vec2(ks * 0.5, ks * 0.5) let kd = Material.sd_box(p, kc, kh, 1.5) let kn = mb_normal(p, kc, kh, 1.5) var kcol = mb_goldface(p, kc, kh, kd, kn, 1.6) * (1.0 + 0.06 * hover * live - 0.10 * held * live) let kin = Finish.inset(p, kc, kh, 1.5, vec2(0.0, 1.2), 1.3) kcol = mix(kcol, kcol * 0.72, clamp(kin, 0.0, 1.0) * 0.6 * held) kcol = mix(kcol, vec3(0.30, 0.21, 0.07), Finish.band(kd, 0.0, px, px) * 0.4 * step(p.x, kc.x - kh.x + 0.6)) face = mix(face, kcol, Finish.cover(kd, px)) face = mix(face, mb_ground(), 0.45 * dis) var res = mb_box(p, lo, hi, 4.5, rw, px, face, hover, 0.0, dis, focus, lift) res = mb_chevron(res, p, kc, mix(vec3(0.20, 0.13, 0.03), vec3(0.55, 0.50, 0.42), dis), open, px) return res * mb_fade(pos, size, m) } mod.widgets.DropDownFlat.draw_bg.pixel = fn() { return mb_dropdown(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.down, self.active, self.disabled, self.focus, 1.0) } mod.widgets.DropDownFlat.draw_text.color = #3a2e1c mod.widgets.DropDownFlat.draw_text.color_hover = #2a2112 mod.widgets.DropDownFlat.draw_text.color_focus = #3a2e1c mod.widgets.DropDownFlat.draw_text.color_disabled = #a89c86 // The select wears a button of its own, built before this sheet: it is // given the drop-down's face, its grown quad and its room here. mod.widgets.Select.clip_x = false mod.widgets.Select.clip_y = false mod.widgets.MultiSelect.clip_x = false mod.widgets.MultiSelect.clip_y = false mod.widgets.Select.face.clip_x = false mod.widgets.Select.face.clip_y = false mod.widgets.MultiSelect.face.clip_x = false mod.widgets.MultiSelect.face.clip_y = false mod.widgets.Select.face.draw_bg.vertex = fn() { let m = 10.0 self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m)) } mod.widgets.MultiSelect.face.draw_bg.vertex = fn() { let m = 10.0 self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m)) } mod.widgets.Select.face.draw_bg.pixel = fn() { return mb_dropdown(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.down, 0.0, self.disabled, self.focus, 1.0) } mod.widgets.MultiSelect.face.draw_bg.pixel = fn() { return mb_dropdown(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.down, 0.0, self.disabled, self.focus, 1.0) } mod.widgets.Select.face.padding = mod.turtle.Inset{left: 12.0 right: 30.0 top: 4.0 bottom: 4.0} mod.widgets.MultiSelect.face.padding = mod.turtle.Inset{left: 12.0 right: 30.0 top: 4.0 bottom: 4.0} mod.widgets.Select.face.min_height = 26.0 mod.widgets.MultiSelect.face.min_height = 26.0 // The combo box: the drop-down one can type in, set a little lower (its // shadow shorter). mod.widgets.ComboBoxFlat.padding = mod.turtle.Inset{left: 12.0 right: 30.0 top: 4.0 bottom: 4.0} mod.widgets.ComboBoxFlat.input.min_height = 0.0 mod.widgets.ComboBoxFlat.clip_x = false mod.widgets.ComboBoxFlat.clip_y = false mod.widgets.ComboBoxFlat.draw_bg.vertex = fn() { let m = 10.0 self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m)) } mod.widgets.ComboBoxFlat.draw_bg.pixel = fn() { return mb_dropdown(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, 0.0, 0.0, self.disabled, self.focus, 0.6) } // Open lists: a card of white marble in a gold rim; a row under the hand // takes a pale gold wash; the chosen row is a strip of pavé in a gold rim. let mb_menu = fn(p: vec2, size: vec2, px: float) -> vec4 { let c = size * 0.5 let d = Material.sd_box(p, c, c, 4.0) let n = mb_normal(p, c, c, 4.0) let rw = 3.0 var col = mb_stone(p + mb_seed(size), px, 0.35) col = mix(col, vec3(0.30, 0.21, 0.07), Finish.band(d, rw, rw + px, px) * 0.5) col = mix(col, mb_rim(d, rw, n, p), Finish.band(d, 0.0, rw, px)) let a = Finish.cover(d, px) return vec4(col * a, a) } // The pavé strip of a chosen row: a hexagonal field of tiny diamonds in a // gold rim. let mb_pave_strip = fn(p: vec2, lo: vec2, hi: vec2, px: float, amt: float) -> vec4 { let c = (lo + hi) * 0.5 let h = (hi - lo) * 0.5 let r = min(3.0, min(h.x, h.y)) let d = Material.sd_box(p, c, h, r) let n = mb_normal(p, c, h, r) let a = Finish.cover(d, px) * amt let rw = 1.3 let pitch = 2.5 let row = floor((p.y - lo.y) / (pitch * 0.866)) let shift = fract(row * 0.5) * pitch let cell = floor((p.x - lo.x - shift) / pitch) let cc = vec2(lo.x + shift + (cell + 0.5) * pitch, lo.y + (row + 0.5) * pitch * 0.866) var col = mb_gold(0.58) let dot = mb_pave(p - cc, 1.12, Finish.hash(vec2(cell, row + 1.7)), px) col = col * (1.0 - dot.a) + dot.rgb col = mix(col, vec3(0.30, 0.21, 0.07), Finish.band(d, rw, rw + px, px) * 0.6) col = mix(col, mb_rim(d, rw, n, p), Finish.band(d, 0.0, rw, px)) let sh = Finish.drop(p, c, h, r, vec2(0.0, 0.7), 0.7) * 0.22 * amt * (1.0 - a) return Finish.over(vec4(mb_shade() * sh, sh), vec4(col * a, a)) } let mb_row = fn(p: vec2, size: vec2, px: float, hover: float, active: float, dis: float) -> vec4 { let wash = 0.10 * hover * (1.0 - dis) var res = vec4(vec3(0.78, 0.63, 0.30) * wash, wash) if active > 0.001 { res = Finish.over(res, mb_pave_strip(p, vec2(3.0, 1.5), size - vec2(3.0, 2.0), px, active)) } return res } mod.widgets.PopupMenuFlat.draw_bg.pixel = fn() { let px = mb_px(self.draw_pass.dpi_factor) return mb_menu(self.pos * self.rect_size, self.rect_size, px) } mod.widgets.PopupMenuItem.draw_bg.pixel = fn() { let px = mb_px(self.draw_pass.dpi_factor) return mb_row(self.pos * self.rect_size, self.rect_size, px, self.hover, self.active, self.disabled) } mod.widgets.PopupMenuItem.draw_text.color = #3a2e1c mod.widgets.PopupMenuItem.draw_text.color_hover = #2a2112 mod.widgets.PopupMenuItem.draw_text.color_active = #2a2112 mod.widgets.ComboBoxFlat.draw_popup_bg.pixel = fn() { let px = mb_px(self.draw_pass.dpi_factor) return mb_menu(self.pos * self.rect_size, self.rect_size, px) } mod.widgets.ComboBoxFlat.draw_item.pixel = fn() { let px = mb_px(self.draw_pass.dpi_factor) return mb_row(self.pos * self.rect_size, self.rect_size, px, self.hover, self.active, 0.0) } mod.widgets.ComboBoxFlat.draw_item_text.color_active = #2a2112 // ---------------------------------------------------------------- tabs // The segmented control: a row of white marble in a chunky gold rim; the chosen // answer is pressed down into it, its floor polished gold sunk under a deep // shade along its upper edge, its word engraved dark in the gold. mod.widgets.SegmentedControl.segment_height = 22.0 mod.widgets.SegmentedControlFlat.segment_height = 22.0 mod.widgets.SegmentedControlFlat.draw_bg.vertex = fn() { let m = 10.0 self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m)) } mod.widgets.SegmentedControlFlat.draw_bg.pixel = fn() { let m = 10.0 let px = mb_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(mb_snap(0.0, o.x, px), mb_snap(0.0, o.y, px)) let hi = vec2(mb_snap(self.rect_size.x, o.x, px), mb_snap(self.rect_size.y, o.y, px)) let face = mb_stone(p + mb_seed(self.rect_size), px, 0.42) return mb_box(p, lo, hi, 4.0, 3.4, px, face, 0.0, 0.0, 0.0, 0.0, 1.0) * mb_fade(self.pos, self.rect_size, m) } mod.widgets.SegmentedControlFlat.draw_pill.vertex = fn() { let m = 4.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_pill.pixel = fn() { let m = 4.0 let px = mb_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) // Kept inside the strip's wider rim. let lo = vec2(mb_snap(1.4, o.x, px), mb_snap(1.4, o.y, px)) let hi = vec2(mb_snap(self.rect_size.x - 1.4, o.x, px), mb_snap(self.rect_size.y - 1.4, o.y, px)) let c = (lo + hi) * 0.5 let h = (hi - lo) * 0.5 let r = min(2.5, min(h.x, h.y)) let d = Material.sd_box(p, c, h, r) let n = mb_normal(p, c, h, r) let a = Finish.cover(d, px) // Concave gold: the far wall lit, the near wall in shade. var col = mb_goldface(p, c, h, d, vec2(-n.x, -n.y), 2.4) let ins = Finish.inset(p, c, h, r, vec2(0.0, 1.6), 1.6) col = mix(col, col * 0.55, clamp(ins, 0.0, 1.0) * 0.75) col = mix(col, vec3(0.22, 0.15, 0.04), Finish.band(d, 0.0, px, px) * 0.7) return vec4(col * a, a) * mb_fade(self.pos, self.rect_size, m) } mod.widgets.SegmentedControlFlat.draw_text.text_style.font_size = 9.0 mod.widgets.SegmentedControlFlat.draw_text_selected.text_style.font_size = 9.0 mod.widgets.SegmentedControlFlat.draw_text.color = #5c4a2e mod.widgets.SegmentedControlFlat.draw_text_selected.color = #26190a // The chosen word takes its ink from the role palette: the dark brown. mod.widgets.SegmentedControlFlat.palette.on_primary_container = #26190a mod.widgets.SegmentedControlFlat.palette.on_neutral_container = #26190a mod.widgets.SegmentedControlFlat.palette.on_secondary_container = #26190a mod.widgets.SegmentedControl.palette.on_primary_container = #26190a mod.widgets.SegmentedControl.palette.on_neutral_container = #26190a mod.widgets.SegmentedControl.palette.on_secondary_container = #26190a // Tabs: words on the stone parted by a short gold line; the tab in use a // white marble tab in a gold rim with a gold bar under its word; under the // hand a pale gold wash. mod.widgets.Tabs.height = 26.0 mod.widgets.Tabs.clip_x = false mod.widgets.Tabs.clip_y = false let mb_tab = fn(pos: vec2, size: vec2, o: vec2, dpi: float, hover: float, active: float) -> vec4 { let m = 8.0 let px = mb_px(dpi) let p = pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let lo = vec2(mb_snap(1.0, o.x, px), mb_snap(0.0, o.y, px)) let hi = vec2(mb_snap(size.x - 1.0, o.x, px), mb_snap(size.y, o.y, px)) var res = vec4(0.0, 0.0, 0.0, 0.0) if active > 0.5 { let face = mb_stone(p + mb_seed(size), px, 0.42) res = mb_box(p, lo, hi, 3.5, 3.0, px, face, hover, 0.0, 0.0, 0.0, 1.0) let c = (lo + hi) * 0.5 let bar = Material.sd_box(p, vec2(c.x, hi.y - 6.2), vec2(clamp(0.22 * (hi.x - lo.x), 5.0, 12.0), 0.8), 0.8) let ba = Finish.cover(bar, px) res = Finish.over(res, vec4(mb_gold(0.42) * ba, ba)) } else { if hover > 0.001 { let d = Material.sd_box(p, (lo + hi) * 0.5, (hi - lo) * 0.5, 3.0) let w = Finish.cover(d, px) * 0.10 * hover res = vec4(vec3(0.78, 0.63, 0.30) * w, w) } } return res * mb_fade(pos, size, m) } mod.widgets.Tabs.draw_tab.vertex = fn() { let m = 8.0 self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m)) } mod.widgets.Tabs.draw_tab_hover.vertex = fn() { let m = 8.0 self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m)) } mod.widgets.Tabs.draw_tab_active.vertex = fn() { let m = 8.0 self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m)) } mod.widgets.Tabs.draw_bg.pixel = fn() { return vec4(0.0, 0.0, 0.0, 0.0) } mod.widgets.Tabs.draw_tab.pixel = fn() { return mb_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 mb_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 mb_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() { let px = mb_px(self.draw_pass.dpi_factor) let p = self.pos * self.rect_size let x = Finish.snap(self.rect_pos.x + self.rect_size.x * 0.5, px) - self.rect_pos.x let span = step(self.rect_size.y * 0.25, p.y) * step(p.y, self.rect_size.y * 0.75) let l = step(x, p.x) * step(p.x, x + px) * span let w = step(x + px, p.x) * step(p.x, x + px * 2.0) * span return vec4(mb_gold(0.40), 1.0) * l + vec4(mb_gold(0.85), 1.0) * w * (1.0 - l) } mod.widgets.Tabs.draw_mark.color = #b8964e mod.widgets.Tabs.draw_text.color = #5c4a2e mod.widgets.Tabs.draw_text_active.color = #2a2112 mod.widgets.Tabs.draw_text.text_style.font_size = 9.0 mod.widgets.Tabs.draw_text_active.text_style.font_size = 9.0 // ---------------------------------------------------------------- feedback // The progress bar: a gold tube, the value an emerald enamel rod in it // (deep green with a lit line along its middle and a crystalline sparkle), // a small round diamond at its head. mod.widgets.ProgressBarFlat.height = 10.0 mod.widgets.ProgressBarFlat.clip_x = false mod.widgets.ProgressBarFlat.clip_y = false mod.widgets.ProgressBarFlat.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.ProgressBarFlat.draw_bg.pixel = fn() { let m = 5.0 let px = mb_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 th = min(8.0, self.rect_size.y) let y0 = mb_snap((self.rect_size.y - th) * 0.5, o.y, px) let x0 = mb_snap(self.rect_size.x * self.start + 1.0, o.x, px) let x1 = mb_snap(self.rect_size.x * self.end - 1.0, o.x, px) let lo = vec2(x0, y0) let hi = vec2(x1, y0 + th) var res = vec4(0.0, 0.0, 0.0, 0.0) if self.track > 0.5 { res = mb_rail(res, p, lo, hi, px, 0.0, 0.0, 0.0) } let rw = 1.5 let span = hi.x - lo.x - rw * 2.0 var f0 = lo.x + rw var f1 = f0 + span * clamp((self.value - self.start) / max(self.end - self.start, 0.001), 0.0, 1.0) let unknown = step(self.value, -0.0001) if unknown > 0.5 { f0 = lo.x + span * 0.3 f1 = lo.x + span * 0.7 } let fc = vec2((f0 + f1) * 0.5, (lo.y + hi.y) * 0.5) let fh = vec2(max((f1 - f0) * 0.5, 0.01), th * 0.5 - rw) let fd = Material.sd_box(p, fc, fh, fh.y) let fa = Finish.cover(fd, px) * step(0.5, fh.x) let t = (p.y - fc.y) / max(fh.y, 0.5) // The enamel: an intent other than the plain one tints it. let ic = self.fill_color() let tint = step(0.5, self.intent) let ink = mix(mb_emerald(), ic.rgb, tint * 0.8) var col = mix(ink * 0.55, ink * 1.05, smoothstep(1.0, -0.6, t)) col = mix(col, mix(ink, vec3(1.0, 1.0, 1.0), 0.55), exp(-(t + 0.25) * (t + 0.25) / 0.06) * 0.55) let sp = Finish.hash(floor(p / (px * 1.5)) + vec2(3.7, 1.1)) col = col * (0.88 + 0.24 * sp) col = mix(col, vec3(1.0, 1.0, 1.0), step(0.985, sp) * 0.6) var fill = vec4(col * fa, fa) if unknown > 0.5 { fill = fill * smoothstep(f0 - 1.0, f0 + span * 0.1, p.x) * smoothstep(f1 + 1.0, f1 - span * 0.1, p.x) } res = Finish.over(res, fill) if unknown < 0.5 && f1 > f0 + 2.0 { res = mb_gemdot(res, p, vec2(f1, fc.y), 3.9, px) } return res * self.opacity * mb_fade(self.pos, self.rect_size, m) } // THE LEVEL METER: a row of small square-cut stones set in a gold channel, // the pictures' jewelled meter: emeralds to 70 %, citrines to 90 %, rubies // past it; a lit stone is light itself (white at its heart), an unlit one // dark tinted glass. A stone held at the peak, a ruby clip lamp. mod.widgets.LevelMeter.draw_bg.pixel = fn() { let px = mb_px(self.draw_pass.dpi_factor) let o = self.rect_pos let sz = self.rect_size let p = self.pos * sz let v = self.vertical let along = mix(sz.x, sz.y, v) let across = mix(sz.y, sz.x, v) let a = mix(p.x, sz.y - p.y, v) let b = mix(p.y, p.x, v) let lamp_room = self.lamp * (across + 3.0) let bar = max(4.0, along - lamp_room) let wa = min(across, 8.0) let wb0 = (across - wa) * 0.5 let wlo = mix(vec2(0.0, wb0), vec2(wb0, sz.y - bar), v) let whi = mix(vec2(bar, wb0 + wa), vec2(wb0 + wa, sz.y), v) let lo = vec2(mb_snap(wlo.x, o.x, px), mb_snap(wlo.y, o.y, px)) let hi = vec2(mb_snap(whi.x, o.x, px), mb_snap(whi.y, o.y, px) - 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 n = mb_normal(p, c, h, r) let ca = Finish.cover(d, px) let rw = clamp(0.18 * wa, 0.8, 1.4) var col = mb_gold(0.22) col = mix(col, mb_rim(d, rw, n, p), Finish.band(d, 0.0, rw, px)) var res = vec4(col * ca, ca) let pad = rw + 0.6 let span = max(1.0, bar - pad * 2.0) let pitch = 4.5 let ga = a - pad let ci = floor(ga / pitch) let cl = (ga - ci * pitch) - 1.85 let gb = b - (wb0 + wa * 0.5) let ghw = max(wa * 0.5 - rw - 0.5, 0.6) let sd = max(abs(cl) - 1.75, abs(gb) - ghw) let cell = Finish.cover(sd, px) let share = clamp((ci * pitch + 1.85) / span, 0.0, 1.0) let inb = step(pad, a) * step(a, bar - pad) let ha = max(self.hot_at, 0.7) var ink = mb_emerald() ink = mix(ink, mb_citrine(), step(ha, share)) ink = mix(ink, mb_ruby(), step(max(self.hot_to, 0.9), share)) let on = step(share, self.level) * step(0.001, self.level) let hc = step(abs(ci * pitch - floor((span * self.hold) / pitch) * pitch), 0.1) * step(0.002, self.hold) let lit = max(on, hc * 0.9) let gq = vec2(cl / 1.75, gb / ghw) let core = exp(-dot(gq, gq) / 0.35) var lcol = mix(mb_lamp_hue(ink, 0.6) * 0.9, vec3(1.0, 0.99, 0.96), core * 0.55) lcol = lcol * (0.88 + 0.22 * Finish.hash(vec2(ci, 4.4))) var dcol = mix(vec3(0.07, 0.065, 0.055), mb_lamp_full(ink) * 0.35, 0.18) dcol = dcol + vec3(0.18, 0.17, 0.15) * smoothstep(0.5, 0.95, gq.y) * 0.5 let gcol = mix(dcol, lcol, lit) res = Finish.over(res, vec4(gcol, 1.0) * cell * inb * self.opacity) 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) let rl = max(across * 0.5 - 2.0, 1.0) res = mb_lamp(res, p, lc, vec2(rl * 0.62, rl * 0.62), step(0.5, self.over), mb_ruby(), 0.6, px, mb_ground(), 2.0, 1.0) } return res * self.opacity } // The spinner keeps the library's own turning face and takes the gold. mod.widgets.SpinnerFlat.draw_bg.color = #b8964e mod.widgets.SpinnerFlat.draw_bg.track_alpha = 0.18 // The rings, the arc and the stock bars keep their own faces; the plain // intent reads in emerald. mod.widgets.ProgressRingFlat.draw_bg.color_primary = #0f7a42 mod.widgets.ProgressRing.draw_bg.color_primary = #0f7a42 mod.widgets.ProgressArc.draw_bg.color_primary = #0f7a42 mod.widgets.ActivityRings.draw_bg.color_primary = #0f7a42 mod.widgets.ProgressBarFlat.draw_bg.color_primary = #0f7a42 mod.widgets.ProgressBar.draw_bg.color_primary = #0f7a42 mod.widgets.ProgressBarGradientX.draw_bg.color_primary = #0f7a42 mod.widgets.ProgressBarGradientY.draw_bg.color_primary = #0f7a42 // Words in the error intent beside a check box or a toggle: a ruby red that // reads on the stone. mod.widgets.CheckBoxFlat.draw_text.color_error = #9c1830 mod.widgets.ToggleFlat.draw_text.color_error = #9c1830 // ---------------------------------------------------------------- surfaces // The scroll bar: a slim gold rod for its gutter and a polished gold bead // for its thumb, brighter under the hand. mod.widgets.ScrollBar.bar_size = 8.0 mod.widgets.ScrollBar.bar_side_margin = 1.0 mod.widgets.ScrollBar.draw_bg.pixel = fn() { let px = mb_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 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 mid = mix(sz.y * 0.5, sz.x * 0.5, v) let glo = mix(vec2(1.0, mid - 1.0), vec2(mid - 1.0, 1.0), v) let ghi = mix(vec2(sz.x - 1.0, mid + 1.0), vec2(mid + 1.0, sz.y - 1.0), v) let gd = Material.sd_box(p, (glo + ghi) * 0.5, (ghi - glo) * 0.5, 1.0) let gutter = Finish.cover(gd, px) * 0.55 var res = vec4(mb_gold(0.45) * gutter, gutter) let tlo = mix(vec2(t0, mid - 2.6), vec2(mid - 2.6, t0), v) let thi = mix(vec2(t1, mid + 2.6), vec2(mid + 2.6, t1), v) let lo = vec2(mb_snap(tlo.x, o.x, px), mb_snap(tlo.y, o.y, px)) let hi = vec2(mb_snap(thi.x, o.x, px), mb_snap(thi.y, o.y, px)) let c = (lo + hi) * 0.5 let h = (hi - lo) * 0.5 let d = Material.sd_box(p, c, h, 2.6) let n = mb_normal(p, c, h, 2.6) let across = mix((p.y - c.y) / max(h.y, 0.5), (p.x - c.x) / max(h.x, 0.5), v) let nn = mix(vec2(0.0, 1.0), vec2(1.0, 0.0), v) var col = mb_gold(mb_env(across * dot(nn, mb_lv()) * 0.9)) * (1.0 + 0.10 * max(self.hover, self.drag)) let a = Finish.cover(d, px) * 0.95 res = Finish.over(res, vec4(col * a, a)) return res * self.opacity } // A RAISED PANEL, the pictures' framed marble slab: white marble (its veins // calm under the words) in a double polished gold frame 5.4 points wide (a // broad bead, a dark groove, a slim bead), a dark seam and a line of // milgrain beads inside the frame, a small gold scroll in each corner (kept within // the corner's nine points, clear of anything the panel holds) and a soft // warm shadow under it, drawn in from the sides so it stays in the gap. // `sunk` 1 makes it a well: the stone under the frame, shaded under its top // edge, no shadow out. `orn` 0 leaves the scrolls off. let mb_panel = fn(p: vec2, size: vec2, o: vec2, px: float, r: float, hover: float, lift: float, sunk: float, orn: float) -> vec4 { let lo = vec2(mb_snap(0.0, o.x, px), mb_snap(0.0, o.y, px)) let hi = vec2(mb_snap(size.x, o.x, px), mb_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) let a = Finish.cover(d, px) let n = mb_normal(p, c, h, r) let hs = vec2(max(h.x - 3.0, 1.0), h.y) let amb = Finish.drop(p, c, hs, min(r, min(hs.x, hs.y)), vec2(0.0, 3.0 + 0.6 * hover), 2.6) * (0.26 + 0.04 * hover) * lift let con = Finish.drop(p, c, h, r, vec2(0.0, 0.6 + px * 0.5), 0.6 + px * 0.5) * 0.30 * (1.0 - 0.5 * sunk) let sc = (1.0 - (1.0 - amb * (1.0 - sunk)) * (1.0 - con)) * (1.0 - a) var res = vec4(mb_shade() * sc, sc) let rw = 5.4 var face = mb_stone(p + mb_seed(size) + vec2(37.0, 11.0), px, 0.6) let t = clamp((p.y - lo.y) / max(size.y, 1.0), 0.0, 1.0) face = face * mix(1.012 - 0.03 * t, 0.955, sunk) + vec3(0.006, 0.006, 0.005) * hover let insh = Finish.inset(p, c, h - vec2(rw, rw), max(r - rw, 0.0), vec2(0.0, mix(1.2, 3.4, sunk)), mix(1.5, 4.0, sunk)) face = mix(face, face * mix(0.86, 0.72, sunk), clamp(insh, 0.0, 1.0) * mix(0.45, 0.9, sunk)) var col = mb_milgrain(face, p, c, h, r, rw + 1.0, 0.62, 1.9, px) // The corner scrolls, with their own small shadow on the stone. if orn > 0.5 && size.x > 60.0 && size.y > 40.0 { let dl = p - lo let dr = hi - p let m = vec2(min(dl.x, dr.x), min(dl.y, dr.y)) - vec2(2.6, 2.6) let sx = mix(1.0, -1.0, step(dr.x, dl.x)) let sy = mix(1.0, -1.0, step(dr.y, dl.y)) let lv = vec2(mb_lv().x * sx, mb_lv().y * sy) let g = mb_corner(m, 0.9, lv, px) let shm = vec2(min(dl.x - 0.3, dr.x + 0.3), min(dl.y - 0.8, dr.y + 0.8)) - vec2(2.6, 2.6) let gs = mb_corner(shm, 0.9, lv, px) col = mix(col, col * 0.72, gs.x * 0.6) col = mix(col, mb_gold(g.y), g.x) // The small stone in each corner's eye. let eye = vec2(mix(hi.x - 8.9, lo.x + 8.9, step(0.0, sx)), mix(hi.y - 8.9, lo.y + 8.9, step(0.0, sy))) let ed = length(p - eye) - 1.55 col = mix(col, mb_gold(0.3), Finish.cover(ed - 0.45, px)) col = mix(col, mb_brilliant(p - eye, 1.55, mb_diamond(), 0.4, px), Finish.cover(ed, px)) } col = mix(col, vec3(0.30, 0.21, 0.07), Finish.band(d, rw, rw + px, px) * 0.55) col = mix(col, mb_rim2(d, rw, n, p, px), Finish.band(d, 0.0, rw, px)) return Finish.over(res, vec4(col * a, a)) } mod.widgets.PanelView.clip_x = false mod.widgets.PanelView.clip_y = false mod.widgets.PanelView.padding = mod.turtle.Inset{left: 10.0 right: 10.0 top: 10.0 bottom: 10.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 = mb_px(self.draw_pass.dpi_factor) let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) return mb_panel(p, self.rect_size, self.rect_pos, px, 5.0, 0.0, 1.0, step(0.5, self.sunken), 1.0) * mb_fade(self.pos, self.rect_size, m) } // The rounded view keeps the colour its caller gives it, with a true // corner and, when it is opaque, a fine gold edge. mod.widgets.RoundedView.draw_bg.pixel = fn() { let px = mb_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(mb_gold(0.5), fill.a) } let a = Finish.cover(d, px) let bd = Finish.band(d, 0.0, max(self.border_size, px), px) var col = mix(fill.rgb, ink.rgb, bd * ink.a) return vec4(col * a * fill.a, a * fill.a) + vec4(ink.rgb * ink.a, ink.a) * bd * a * (1.0 - fill.a) } // The card: its three appearances told apart. Elevated, a framed marble // slab standing off the stone with its scrolls and a deeper shadow, more of // it under the hand; filled, a framed slab seated with its contact shadow // only; outlined, the stone inside a fine gold wire. mod.widgets.Card.clip_x = false mod.widgets.Card.clip_y = false mod.widgets.Card.padding = mod.turtle.Inset{left: 14.0 right: 14.0 top: 13.0 bottom: 13.0} mod.widgets.Card.draw_bg.vertex = fn() { let m = 14.0 self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m)) } mod.widgets.Card.draw_bg.pixel = fn() { let m = 14.0 let px = mb_px(self.draw_pass.dpi_factor) let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let fade = mb_fade(self.pos, self.rect_size, m) if self.border_size > 0.01 { let o = self.rect_pos let lo = vec2(mb_snap(0.0, o.x, px), mb_snap(0.0, o.y, px)) let hi = vec2(mb_snap(self.rect_size.x, o.x, px), mb_snap(self.rect_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, 5.0) let n = mb_normal(p, c, h, 5.0) let ring = Finish.band(d, 0.0, 2.4, px) let col = mb_rim(d, 2.4, n, p) let wash = 0.3 * self.hover * Finish.cover(d, px) var res = vec4(vec3(1.0, 1.0, 1.0) * wash, wash) return Finish.over(res, vec4(col * ring, ring)) * fade } if self.shadow_radius < 0.01 { return mb_panel(p, self.rect_size, self.rect_pos, px, 5.0, self.hover, 0.0, 0.0, 1.0) * fade } return mb_panel(p, self.rect_size, self.rect_pos, px, 5.0, self.hover, 1.3, 0.0, 1.0) * fade } // LISTS: the rows stand in a white marble well framed in a gold rim, each // row drawing the part of the well it covers (the well is the clip the rows // are drawn in, the list's own box, so it stays put while the rows scroll). // The well is held four points in from the box's leading edge and a point // from the others, so whatever stands beside the list keeps its room. Under the hand a row takes a pale gold wash; the chosen row is a // strip of pavé diamonds in a gold rim. The words are the ink. mod.widgets.ListItem.padding = mod.turtle.Inset{left: 14.0 right: 10.0 top: 0.0 bottom: 0.0} // A one-line row is 25 points, so a short list ends on a whole row. mod.widgets.ListItemOne.height = 25.0 mod.widgets.ListItem.clip_x = false mod.widgets.ListItem.clip_y = false mod.widgets.ListItem.draw_bg.pixel = fn() { let px = mb_px(self.draw_pass.dpi_factor) let o = self.rect_pos let p = self.pos * self.rect_size let live = 1.0 - self.disabled let tlo = vec2(mb_snap(self.draw_clip.x - o.x + 4.0, o.x, px), mb_snap(self.draw_clip.y - o.y + 1.0, o.y, px)) let thi = vec2(mb_snap(self.draw_clip.z - o.x - 1.0, o.x, px), mb_snap(self.draw_clip.w - o.y - 1.0, o.y, px)) let tc = (tlo + thi) * 0.5 let th = max((thi - tlo) * 0.5, vec2(1.0, 1.0)) let tr = min(4.0, min(th.x, th.y)) let d = Material.sd_box(p, tc, th, tr) let n = mb_normal(p, tc, th, tr) let a = Finish.cover(d, px) // The well's stone is laid in the well's own frame, so it holds still // under the scrolling rows. let wq = p - tlo + mb_seed(thi - tlo) var col = mb_stone(wq, px, 0.4) * 0.985 col = mix(col, col * 0.84, clamp(Finish.inset(p, tc, th - vec2(2.2, 2.2), tr, vec2(0.0, 2.0), 2.4), 0.0, 1.0) * 0.6) col = col + vec3(0.06, 0.045, 0.01) * self.hover * live col = mix(col, vec3(0.30, 0.21, 0.07), Finish.band(d, 2.2, 2.2 + px, px) * 0.5) col = mix(col, mb_rim(d, 2.2, n, p), Finish.band(d, 0.0, 2.2, px)) var res = vec4(col * a, a) if self.selected > 0.001 { let lo = vec2(mb_snap(8.0, o.x, px), mb_snap(2.0, o.y, px)) let hi = vec2(mb_snap(self.rect_size.x - 5.0, o.x, px), mb_snap(self.rect_size.y - 2.0, o.y, px)) res = Finish.over(res, mb_pave_strip(p, lo, hi, px, self.selected) * a) } return res } mod.widgets.ListItem.draw_text.color = #3a2e1c mod.widgets.ListItem.draw_text_2.color = #5c4a2e // A chip is a small white marble box in a gold rim; its label the ink // whatever its role. mod.widgets.ChipFlat.draw_text.text_style = mod.theme.font_bold{font_size: 9.0} mod.widgets.ChipFlat.palette.on_neutral = #3a2e1c mod.widgets.ChipFlat.palette.on_neutral_container = #3a2e1c mod.widgets.ChipFlat.palette.neutral = #3a2e1c mod.widgets.ChipFlat.palette.on_primary = #3a2e1c mod.widgets.ChipFlat.palette.on_primary_container = #3a2e1c mod.widgets.ChipFlat.palette.primary = #3a2e1c mod.widgets.ChipFlat.palette.on_secondary_container = #3a2e1c mod.widgets.ChipFlat.palette.on_tertiary_container = #3a2e1c mod.widgets.ChipFlat.draw_bg.vertex = fn() { let m = 8.0 self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m)) } mod.widgets.ChipFlat.draw_bg.pixel = fn() { let m = 8.0 let px = mb_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(mb_snap(0.0, o.x, px), mb_snap(0.0, o.y, px)) let hi = vec2(mb_snap(self.rect_size.x, o.x, px), mb_snap(self.rect_size.y, o.y, px)) let face = mb_stone(p + mb_seed(self.rect_size), px, 0.45) * (1.0 + 0.04 * self.hover - 0.06 * self.down) return mb_box(p, lo, hi, 3.5, 2.6, px, face, self.hover, self.down, 0.0, self.keyed, 1.0) * mb_fade(self.pos, self.rect_size, m) } // A count is a small white marble box in a gold rim with brown figures. mod.widgets.BadgePalette.neutral = #f6f5f1 mod.widgets.BadgePalette.on_neutral = #3a2e1c mod.widgets.BadgeFlat.draw_bg.pixel = fn() { let px = mb_px(self.draw_pass.dpi_factor) let o = self.rect_pos let p = self.pos * self.rect_size let lo = vec2(mb_snap(0.0, o.x, px), mb_snap(0.0, o.y, px)) let hi = vec2(mb_snap(self.rect_size.x - px * 0.49, o.x, px), mb_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(3.0, min(h.x, h.y)) let d = Material.sd_box(p, c, h, r) let n = mb_normal(p, c, h, r) let rw = 2.2 var col = mb_stone(p + mb_seed(self.rect_size), px, 0.3) col = mix(col, self.color.rgb, 0.3 * step(self.color.a, 0.99)) col = mix(col, vec3(0.30, 0.21, 0.07), Finish.band(d, rw, rw + px, px) * 0.5) col = mix(col, mb_rim(d, rw, n, p), Finish.band(d, 0.0, rw, px)) let a = Finish.cover(d, px) return vec4(col * a, a) } // LAMPS. The Lamp and the bar lamp are the sheet's LED gem (above) in its // gold bezel. The halo's room is held in the lamp's own box, so the light is // never cut at its edge; the bar lamp keeps to the left of its cell, short // enough that it and its light end well before whatever stands beside it. mod.widgets.Lamp.halo = 0.3 mod.widgets.Lamp.halo_reach = 1.0 mod.widgets.Lamp.draw_bg.color_accent = uniform(#18b864) mod.widgets.Lamp.draw_bg.color_success = uniform(#22c76a) mod.widgets.Lamp.draw_bg.color_warning = uniform(#f5a52e) mod.widgets.Lamp.draw_bg.color_error = uniform(#ec2f4a) mod.widgets.Lamp.draw_bg.color_plain = uniform(#ffe6b8) 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 // A bar is a short light pipe a little slimmer than the round lamp, // set at the left of its cell, its light kept within two and a half // points of its bezel. let tb = mix(t, t * 0.78, self.bar) let half_len = min(max((self.rect_size.x - 2.0 * room) * 0.5, t * 0.5), tb * 1.1) let bwb = clamp(0.32 * tb * 0.5, 0.7, 2.4) let cb = vec2(mix(c.x, 1.0 + bwb + half_len, self.bar), c.y) let hb = vec2(mix(t * 0.5, half_len, self.bar), tb * 0.5) let res = mb_lamp(vec4(0.0, 0.0, 0.0, 0.0), p, cb, hb, self.lit, self.intent_color().rgb, 0.35 * (1.0 - step(3.5, self.intent)), px, mb_ground(), mix(max(room - 0.5, 2.0), 2.5, self.bar), 1.0) return res * self.opacity } // Dividers are a fine gold wire: a bright line over a dark one, a small // round diamond set at the middle of a long one. mod.widgets.Hr.draw_bg.vertex = fn() { let m = 4.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.Hr.draw_bg.pixel = fn() { let m = 4.0 let px = mb_px(self.draw_pass.dpi_factor) let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let y = Finish.snap(self.rect_pos.y + self.rect_size.y * 0.5, px) - self.rect_pos.y let span = step(4.0, p.x) * step(p.x, self.rect_size.x - 4.0) let l = step(y, p.y) * step(p.y, y + px) * span let w = step(y + px, p.y) * step(p.y, y + px * 2.0) * (1.0 - l) * span var res = vec4(mb_gold(0.80), 1.0) * l + vec4(mb_gold(0.30), 1.0) * w if self.rect_size.x > 80.0 { res = mb_gemdot(res, p, vec2(self.rect_size.x * 0.5, y + px), 2.6, px) } return res * mb_fade(self.pos, self.rect_size, m) } mod.widgets.Vr.draw_bg.pixel = fn() { let px = mb_px(self.draw_pass.dpi_factor) let p = self.pos * self.rect_size let x = Finish.snap(self.rect_pos.x + self.rect_size.x * 0.5, px) - self.rect_pos.x let l = step(x, p.x) * step(p.x, x + px) let w = step(x + px, p.x) * step(p.x, x + px * 2.0) * (1.0 - l) return vec4(mb_gold(0.80), 1.0) * l + vec4(mb_gold(0.30), 1.0) * w } // ---------------------------------------------------------------- screens // A SCREEN is black onyx glass in a polished gold bezel: the face the // screen colour with a few faint grey veins in it (a black marble, calm // enough for the figures), darkest under its top edge where the bezel // shades it (`recess` points deep), a dark device pixel where the face // meets the gold; the bezel (`bezel` points, at least two) a round bead lit // from the top; a contact shadow on the stone under it. Figures on it are // champagne gold. let mb_screen = fn(p: vec2, size: vec2, o: vec2, px: float, r: float, bezel: float, recess: float, face_ink: vec3) -> vec4 { let lo = vec2(mb_snap(0.0, o.x, px), mb_snap(0.0, o.y, px)) let hi = vec2(mb_snap(size.x, o.x, px), mb_snap(size.y, o.y, px) - px) let c = (lo + hi) * 0.5 let ho = (hi - lo) * 0.5 let ro = min(r, min(ho.x, ho.y)) let outer = Material.sd_box(p, c, ho, ro) let n = mb_normal(p, c, ho, ro) let b = Finish.snap(max(bezel, 2.2), px) let hf = max(ho - vec2(b, b), vec2(0.5, 0.5)) let rf = max(ro - b, 0.0) let fd = Material.sd_box(p, c, hf, rf) let sh = Finish.drop(p, c, ho, ro, vec2(0.0, 1.0), 1.1) * 0.30 * (1.0 - Finish.cover(outer, px)) var res = vec4(mb_shade() * sh, sh) // The black marble: the stone's veins, inverted and faint. let st = mb_stone(p + mb_seed(size) + vec2(91.0, 17.0), px, 1.0) let vein = clamp((0.965 - dot(st, vec3(0.333, 0.333, 0.334))) * 3.0, 0.0, 1.0) var face = face_ink + vec3(0.11, 0.105, 0.095) * vein * 0.55 if recess > 0.0 { let insh = Finish.inset(p, c, hf, rf, vec2(0.0, recess * 0.5), max(recess * 0.45, 0.3)) face = mix(face, face * 0.35, 0.8 * insh) } face = mix(face, vec3(0.02, 0.015, 0.008), Finish.band(fd, 0.0, px, px) * 0.8) let col = mix(mb_rim(outer, b, n, p), face, Finish.cover(fd, px)) let a = Finish.cover(outer, px) return Finish.over(res, vec4(col * a, a)) } mod.widgets.ScreenView.draw_bg.border_radius = uniform(5.0) mod.widgets.ScreenView.draw_bg.pixel = fn() { let px = mb_px(self.draw_pass.dpi_factor) let p = self.pos * self.rect_size var res = mb_screen(p, self.rect_size, self.rect_pos, px, self.border_radius, self.bezel, self.recess, self.color.rgb) // The screen's status light, an emerald lit in its top right corner; // its light stays on the face. let b = max(self.bezel, 2.2) let fdist = Material.sd_box(p, self.rect_size * 0.5, max(self.rect_size * 0.5 - vec2(b, b), vec2(0.5, 0.5)), max(self.border_radius - b, 0.0)) res = mix(res, mb_lamp(res, p, vec2(self.rect_size.x - b - 5.5, b + 5.5), vec2(1.5, 1.5), 1.0, mb_emerald(), 0.6, px, vec3(0.08, 0.07, 0.06), 6.0, 0.0), Finish.cover(fdist + 0.5, px)) if self.scan > 0.0 { let s = Finish.scan(p.y, px, 3.0 * px, self.scan) res = vec4(res.rgb * mix(1.0, s, 0.6), res.a) } return res * self.color.a } // The glass: a faint diagonal band over the face. mod.widgets.ScreenView.sheen = 0.035 mod.widgets.ScreenView.draw_glass.pixel = fn() { let p = self.pos * self.rect_size let px = mb_px(self.draw_pass.dpi_factor) 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 b = Finish.snap(max(self.bezel, 2.2), px) let hf = max(ho - vec2(b, b), vec2(0.5, 0.5)) let fd = Material.sd_box(p, c, hf, max(min(self.face_radius, min(ho.x, ho.y)) - b, 0.0)) let u = (p - (c - hf)) / max(hf * 2.0, vec2(1.0, 1.0)) let diag = u.x + u.y * 0.6 let band = smoothstep(0.0, 0.12, diag) * (1.0 - smoothstep(0.28, 0.5, diag)) let a = clamp(self.sheen * band, 0.0, 1.0) * Finish.cover(fd + px, px) return vec4(vec3(1.0, 0.95, 0.85) * a, a) } // THE NEEDLE METER is a screen: the same onyx in its gold bezel, its scale // printed in gold (a tick at every division, the majors two device pixels // wide and the minors one and dimmer, a faint arc under them), the red // zone a ruby band just outside the arc, the needle a fine gold line with a // soft warm light along it, its root under a small round diamond in a gold // bezel. mod.widgets.NeedleMeter.draw_bg.border_radius = uniform(5.0) mod.widgets.NeedleMeter.draw_bg.color_zone = uniform(#d0203e) mod.widgets.NeedleMeter.draw_bg.color_scale = uniform(#e2c886) mod.widgets.NeedleMeter.draw_bg.color_needle = uniform(#f6dc98) mod.widgets.NeedleMeter.draw_bg.pixel = fn() { let p = self.pos * self.rect_size let px = mb_px(self.draw_pass.dpi_factor) 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) let res = mb_screen(p, self.rect_size, self.rect_pos, px, self.border_radius, 2.4, 4.0, self.color.rgb) var col = res.rgb / max(res.a, 0.001) let ink = self.color_scale.rgb 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, -q.y * cos(ak) + q.x * sin(ak)) col = mix(col, ink, max(arc * 0.4, tick * mix(0.55, 0.95, major))) 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) col = mix(col, self.color_zone.rgb, zone_t * zone_r * 0.9) } let na = self.needle_angle(self.value) let nd = vec2(sin(na), 0.0 - cos(na)) let tail = max(self.pivot_size - px, 0.0) let along = dot(q, nd) let across = abs(q.x * nd.y - q.y * nd.x) let span = smoothstep(-1.0, 1.0, rr + 3.0 - along) * smoothstep(-1.0, 1.0, along + tail) let halo = 0.30 / (1.0 + (across / 1.2) * (across / 1.2)) * span col = col + (vec3(1.0, 1.0, 1.0) - exp(-vec3(0.95, 0.70, 0.30) * halo)) * max(vec3(1.0, 1.0, 1.0) - col, vec3(0.0, 0.0, 0.0)) let needle = Finish.cover(across - 0.55, px) * Finish.cover(along - (rr + 3.0), px) * Finish.cover(0.0 - along - tail, px) col = mix(col, self.color_needle.rgb, needle) let pr = max(self.pivot_size + 0.5, 2.0) let piv = mb_gemdot(vec4(0.0, 0.0, 0.0, 0.0), p, pv, pr, px) col = col * (1.0 - piv.a) + piv.rgb return vec4(col * res.a, res.a) * self.opacity } // ---------------------------------------------------------------- own faces // Templates that draw a face of their own on top of these roots keep the // plain quad: the grown one is only for the faces written here. mod.widgets.CloseButton.draw_bg.vertex = fn() { self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size) } mod.widgets.CopyButton.draw_bg.vertex = fn() { self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size) } mod.widgets.BurgerButton.draw_bg.vertex = fn() { self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size) } mod.widgets.ButtonFloating.draw_bg.vertex = fn() { self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size) } mod.widgets.ButtonFloatingSm.draw_bg.vertex = fn() { self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size) } mod.widgets.CheckBoxCustom.draw_bg.vertex = fn() { self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size) } mod.widgets.glass.CutButton.draw_bg.vertex = fn() { self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size) } mod.widgets.glass.RadioButton.draw_bg.vertex = fn() { self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size) } // The icon of a face-less icon button stands on the stone: gold-brown. mod.widgets.ButtonFlatterIcon.draw_icon.color = #6e521c mod.widgets.BurgerButton.draw_bg.bar_color = #5c4a2e mod.widgets.BurgerButton.draw_bg.bar_color_hover = #2a2112 mod.widgets.BurgerButton.draw_bg.bar_color_disabled = #a89c86 mod.widgets.CloseButton.draw_bg.cross_color_hover = #2a2112 // ---------------------------------------------------------------- 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, so the box metrics are stated ONCE here // and handed to each field below; a field added to the library later joins // the row by being named here rather than by having these numbers copied // into it. Fields and buttons share the one 26 point row. let field_height = 26.0 let field_pad_v = 4.0 // Stock faces draw the corner doubled: 1.75 is a 3.5 point corner. let field_radius = 1.75 // One device pixel; the faces written above draw their own edges. let field_border = 0.34 let field_room = fn(w) { w.min_height = field_height w.padding.top = field_pad_v w.padding.bottom = field_pad_v } let field_face = fn(d) { d.border_radius = field_radius d.border_size = field_border } let field_frame = fn(w) { w.min_height = field_height } field_room(mod.widgets.TextInput) field_face(mod.widgets.TextInput.draw_bg) field_room(mod.widgets.ComboBox) field_face(mod.widgets.ComboBox.draw_bg) field_room(mod.widgets.DropDown) field_face(mod.widgets.DropDown.draw_bg) field_room(mod.widgets.DropDown2) field_face(mod.widgets.DropDown2.draw_bg) field_room(mod.widgets.FieldWell) field_face(mod.widgets.FieldWell.draw_bg) field_room(mod.widgets.TagField) field_face(mod.widgets.TagField.draw_bg) field_frame(mod.widgets.NumberField) mod.widgets.NumberField.draw_bg.border_radius = field_radius field_frame(mod.widgets.ValueInput) field_frame(mod.widgets.DateField) field_frame(mod.widgets.TimeField) field_frame(mod.widgets.DatePicker) field_frame(mod.widgets.DateRangePicker) // The chrome-less input a well holds must impose no height of its own. mod.widgets.WellInput.min_height = 0.0 // ---------------------------------------------------------------- ground // Plain views stop clipping so a shadow or a light 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.ConcedingRow.clip_x = false mod.widgets.ConcedingRow.clip_y = false mod.widgets.Window.pass.clear_color = #efeee9 // THE WINDOW'S GROUND: one slab of white marble, fixed in window space (it // never moves with what scrolls over it), its veins at their strongest // along the window's frame and calmed to under half of that under the // content, so words read on it; a soft pool of light high in the middle, a // faint fall toward the corners. Round its edge a polished gold frame 2.4 // points wide, set 3 points in, and in each corner a gold scroll. mod.widgets.Window.show_bg = true mod.widgets.Window.draw_bg.pixel = fn() { let px = mb_px(self.draw_pass.dpi_factor) let p = self.pos * self.rect_size let sz = self.rect_size let de = min(min(p.x, p.y), min(sz.x - p.x, sz.y - p.y)) let edge = 1.0 - smoothstep(20.0, 120.0, de) let k = mix(0.58, 1.0, edge) var col = mb_marble(p + vec2(311.0, 127.0), px, k) let qv = (p - vec2(sz.x * 0.5, sz.y * 0.2)) / vec2(max(sz.x * 0.8, 1.0), max(sz.y * 1.1, 1.0)) col = col * (1.0 + 0.012 * exp(-dot(qv, qv) * 6.0) - 0.03 * smoothstep(0.5, 1.2, length(qv))) // The frame. let lo = vec2(3.0, 3.0) let hi = sz - vec2(3.0, 3.0) let c = (lo + hi) * 0.5 let h = (hi - lo) * 0.5 let d = Material.sd_box(p, c, h, 6.0) let n = mb_normal(p, c, h, 6.0) let fw = 3.6 let fb = Finish.band(d, 0.0, fw, px) col = mix(col, vec3(0.30, 0.21, 0.07), Finish.band(d, fw, fw + px, px) * 0.45) col = mix(col, mb_rim(d, fw, vec2(-n.x, -n.y), p), fb) // The corner scrolls, inside the frame. let dl = p - lo let dr = hi - p let m = vec2(min(dl.x, dr.x), min(dl.y, dr.y)) - vec2(fw - 0.5, fw - 0.5) let sx = mix(1.0, -1.0, step(dr.x, dl.x)) let sy = mix(1.0, -1.0, step(dr.y, dl.y)) let lv = vec2(mb_lv().x * sx, mb_lv().y * sy) if m.x < 60.0 && m.y < 60.0 { let g = mb_corner(m, 1.35, lv, px) let gs = mb_corner(m - vec2(0.3 * sx, 0.8 * sy), 1.35, lv, px) col = mix(col, col * 0.75, gs.x * 0.5) col = mix(col, mb_gold(g.y), g.x) } return vec4(col, 1.0) } // ---------------------------------------------------------------- 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}} true