makepad/widgets/sheets/themes/steampunk-brass/widgets.splash
Admin 53e0e85b92 draw and widgets: Slug text everywhere, no blank glyphs, slimmer widgets
All outline text draws with Slug curves on every platform (SDF/MSDF stays
selectable per size); a frame never presents a blank glyph and retained draw
lists keep theirs. One coverage model for vector feathering, widget families
split out of the core crate, fewer steady-state allocations, video keeps its
aspect (Contain/Cover), render and SSAO pass unification.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 12:26:21 +02:00

3083 lines
155 KiB
Text

use mod.prelude.widgets_internal.*
// Steampunk brass. Raised controls are smoked faces set in bevelled brass
// frames; caps, knobs and thumbs are brass (knurled, spun, geared); wells
// are cut into dark iron with rivets along their frames; plates are riveted
// iron; the dial is a pressure gauge; the list is parchment. What is chosen
// or on is lit a pale steel blue; the instruments glow amber. Every rim is
// one device pixel, snapped.
mod.widgets.Button.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 4.0 bottom: 4.0}
mod.widgets.Button.draw_bg.border_radius = 2.0
mod.widgets.Button.draw_bg.border_size = 0.5
// A ROW OF FIELDS IS ONE HEIGHT: the box metrics are stated once here and
// handed to each field below. Buttons take the same 28.
let field_height = 28.0
let field_pad_v = mod.theme.mspace_1.top
let field_radius = 2.0
let field_border = 0.5
// The room a Fit field takes: where a field is Fit, the padding IS its
// height.
let field_room = fn(w) {
w.min_height = field_height
w.padding.top = field_pad_v
w.padding.bottom = field_pad_v
}
// The box a field draws. Given the draw object and not the widget, because
// not every field draws its own box.
let field_face = fn(d) {
d.border_radius = field_radius
d.border_size = field_border
}
// A field that states a frame and lays its own parts out inside it takes
// the height alone: a padding would displace its line.
let field_frame = fn(w) {
w.min_height = field_height
}
field_room(mod.widgets.TextInputFlat)
field_face(mod.widgets.TextInputFlat.draw_bg)
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
mod.widgets.Window.pass.clear_color = #3b342b
// Plain views stop clipping, so a part's shadow may fall into the gap
// beside it. Scroll views keep their own clip.
mod.widgets.View.clip_x = false
mod.widgets.View.clip_y = false
// ---------------------------------------------------------------------------
// SHARED PARTS. Distances in points, negative inside, y down; `px` is one
// device pixel in points.
let g3 = fn(v: float) -> vec3 {
return vec3(v, v, v)
}
// A rounded box, true radius.
let bz_rbox = fn(p: vec2, c: vec2, h: vec2, r: float) -> float {
let rr = clamp(r, 0.0, min(h.x, h.y))
let q = abs(p - c) - h + vec2(rr, rr)
return min(max(q.x, q.y), 0.0) + length(max(q, vec2(0.0, 0.0))) - rr
}
// Its outward direction, by central differences.
let bz_grad = fn(p: vec2, c: vec2, h: vec2, r: float) -> vec2 {
let e = 0.35
let gx = bz_rbox(p + vec2(e, 0.0), c, h, r) - bz_rbox(p - vec2(e, 0.0), c, h, r)
let gy = bz_rbox(p + vec2(0.0, e), c, h, r) - bz_rbox(p - vec2(0.0, e), c, h, r)
return normalize(vec2(gx, gy) + vec2(0.00001, 0.00002))
}
// A segment's distance.
let bz_seg = fn(p: vec2, a: vec2, b: vec2) -> float {
let pa = p - a
let ba = b - a
let k = clamp(dot(pa, ba) / max(dot(ba, ba), 0.0001), 0.0, 1.0)
return length(pa - ba * k)
}
// A stroked chevron's distance: `w` wide, `hgt` tall, pointing down
// (`flip` 1 points up).
let bz_chevron = fn(p: vec2, c: vec2, w: float, hgt: float, flip: float) -> float {
let s = mix(1.0, -1.0, flip)
let a = c + vec2(-w * 0.5, -hgt * 0.5 * s)
let b = c + vec2(0.0, hgt * 0.5 * s)
let e = c + vec2(w * 0.5, -hgt * 0.5 * s)
return min(bz_seg(p, a, b), bz_seg(p, b, e))
}
// A disc laid on the pixel grid: a whole number of device pixels across.
// `c` is quad-local; returns (centre.x, centre.y, radius).
let bz_fit = fn(c: vec2, o: vec2, rad: float, px: float) -> vec3 {
let dn = max(floor(2.0 * rad / px + 0.5), 2.0)
let r2 = dn * px * 0.5
let ex = Finish.snap(o.x + c.x - r2, px) - o.x
let ey = Finish.snap(o.y + c.y - r2, px) - o.y
return vec3(ex + r2, ey + r2, r2)
}
// Signed grain, -amount..amount, one value per device pixel (pass `p / px`),
// two hashes displaced by a first so no lattice repeats.
let bz_grain = fn(q: vec2, amount: float) -> float {
let i = floor(q)
let h = Finish.hash(i)
let a = Finish.hash(i + vec2(h * 397.13, h * 211.71))
let b = Finish.hash(vec2(i.y * 0.7548 + h * 157.3, i.x * 1.1698 + h * 89.9))
return (a + b - 1.0) * amount
}
// Six-stop ramp over 0..1.
let bz_ramp = fn(x0: float, k0: vec3, k1: vec3, k2: vec3, k3: vec3, k4: vec3, k5: vec3) -> vec3 {
let x = clamp(x0, 0.0, 1.0)
var col = mix(k0, k1, clamp(x / 0.2, 0.0, 1.0))
col = mix(col, k2, clamp((x - 0.2) / 0.2, 0.0, 1.0))
col = mix(col, k3, clamp((x - 0.4) / 0.2, 0.0, 1.0))
col = mix(col, k4, clamp((x - 0.6) / 0.2, 0.0, 1.0))
return mix(col, k5, clamp((x - 0.8) / 0.2, 0.0, 1.0))
}
// The metals by light 0..1, measured on the picture: brass (#4a371b deep,
// #8a6e40 shade, #c4a867 body, #ecd89c highlight), copper, and warm iron.
let bz_brass = fn(l: float) -> vec3 {
return bz_ramp(l, vec3(0.086, 0.059, 0.024), vec3(0.290, 0.216, 0.106), vec3(0.525, 0.420, 0.255), vec3(0.722, 0.612, 0.392), vec3(0.886, 0.808, 0.600), vec3(0.984, 0.945, 0.812))
}
let bz_copper = fn(l: float) -> vec3 {
return bz_ramp(l, vec3(0.082, 0.039, 0.020), vec3(0.278, 0.176, 0.118), vec3(0.478, 0.290, 0.188), vec3(0.659, 0.459, 0.337), vec3(0.816, 0.643, 0.533), vec3(0.957, 0.847, 0.753))
}
let bz_iron = fn(l: float) -> vec3 {
return bz_ramp(l, vec3(0.055, 0.051, 0.043), vec3(0.180, 0.169, 0.153), vec3(0.231, 0.220, 0.204), vec3(0.306, 0.294, 0.275), vec3(0.439, 0.424, 0.396), vec3(0.659, 0.643, 0.612))
}
// 0 brass, 1 copper, 2 iron.
let bz_metal = fn(l: float, metal: float) -> vec3 {
let b = bz_brass(l)
let k = bz_copper(l)
let i = bz_iron(l)
return mix(mix(b, k, step(0.5, metal)), i, step(1.5, metal))
}
// The light on a bevelled frame `fw` wide: a rounded bead whose outer half
// faces out and inner half faces in, lit from the top left, with a ridge
// highlight on the lit runs. `g` is the outward direction.
let bz_frame_l = fn(d: float, g: vec2, fw: float) -> float {
let t = clamp(-d / max(fw, 0.001), 0.0, 1.0)
let facing = dot(g, vec2(-0.24, -0.97))
let slope = 1.0 - 2.0 * t
var l = 0.50 + 0.30 * facing * slope + 0.13 * facing
let s = (t - 0.34) / 0.2
l = l + 0.24 * max(facing, 0.0) * exp(-s * s)
return l
}
// A domed rivet with its shadow on the plate, premultiplied.
let bz_rivet = fn(p: vec2, c: vec2, rad: float, px: float, metal: float, lift: float) -> vec4 {
let q = p - c
let r = length(q)
let d = r - rad
let sd = length(q - vec2(rad * 0.35, rad * 0.55)) - rad
let sh = (1.0 - smoothstep(-rad * 0.3, rad * 0.8, sd)) * 0.5
var out = vec4(0.0, 0.0, 0.0, sh)
let n = q / max(rad, 0.001)
var l = 0.46 - 0.26 * n.y - 0.10 * n.x + lift
let hq = q - vec2(-0.30, -0.38) * rad
l = l + 0.42 * exp(-dot(hq, hq) / max(rad * rad * 0.12, 0.0001))
l = l * mix(1.0, 0.6, smoothstep(0.6, 1.0, r / max(rad, 0.001)))
let col = bz_metal(l, metal)
let a = Finish.cover(d, px)
return Finish.over(out, vec4(col * a, a))
}
// The accent: a pale steel blue, its hot core and its deep glass.
let bz_acc = fn() -> vec3 {
return vec3(0.498, 0.714, 0.910)
}
let bz_acc_hot = fn() -> vec3 {
return vec3(0.843, 0.925, 1.0)
}
let bz_acc_deep = fn() -> vec3 {
return vec3(0.200, 0.345, 0.522)
}
// The instrument light: amber.
let bz_amber = fn() -> vec3 {
return vec3(0.941, 0.659, 0.282)
}
let bz_shadow_ink = fn() -> vec3 {
return vec3(0.039, 0.027, 0.016)
}
let bz_edge = fn() -> vec3 {
return vec3(0.055, 0.035, 0.016)
}
// The smoked face of a framed control by height t 0..1: #58544d under the
// frame, #3e3b36 over the upper third, then the dark glass of its lower
// half, #191817 at the foot.
let bz_smoke = fn(t: float) -> vec3 {
let a = vec3(0.380, 0.361, 0.333)
let b = vec3(0.286, 0.271, 0.251)
let c = vec3(0.137, 0.133, 0.125)
let e = vec3(0.098, 0.094, 0.090)
var col = mix(a, b, smoothstep(0.0, 0.28, t))
col = mix(col, c, smoothstep(0.36, 0.58, t))
return mix(col, e, smoothstep(0.6, 1.0, t))
}
// Focus: one device pixel of the accent, two device pixels out.
let bz_focus = fn(d: float, px: float, focus: float) -> vec4 {
let f = Finish.ring_out(d, 2.0 * px, 3.0 * px, px) * 0.8 * focus
let k = bz_acc()
return vec4(k * f, f)
}
// A well cut into iron: the fill, the plate's shadow falling in from the
// top wall, a dark mouth darkest on top.
let bz_well = fn(p: vec2, c: vec2, h: vec2, r: float, px: float, fill: vec3, depth: float) -> vec3 {
let d = bz_rbox(p, c, h, r)
let sh = Finish.inset(p, c, h, r, vec2(0.0, depth * 0.5), max(depth * 0.4, px * 0.6))
var col = mix(fill, vec3(0.031, 0.024, 0.016), clamp(sh, 0.0, 1.0) * 0.85)
let up = 1.0 - smoothstep(-0.5, 0.5, p.y - c.y)
col = mix(col, vec3(0.031, 0.024, 0.016), Finish.band(d, 0.0, px, px) * mix(0.5, 0.95, up))
return col
}
// The lip under a well's lower rim, outside it: premultiplied warm white.
let bz_lip = fn(p: vec2, c: vec2, h: vec2, r: float, px: float, a: float) -> vec4 {
let d = bz_rbox(p, c, h, r)
let foot = smoothstep(c.y + h.y - max(r, 1.0) - 1.5, c.y + h.y, p.y)
let l = Finish.ring_out(d, 0.0, px, px) * foot * a
return vec4(l, l * 0.95, l * 0.85, l)
}
// A lamp bead of glass: lit with a white-hot centre and a tight halo, or
// unlit at a fifth of its light.
let bz_bead = fn(p: vec2, c: vec2, rad: float, px: float, ink: vec3, lit: float) -> vec4 {
let q = p - c
let d = length(q) - rad
let a = Finish.cover(d, px)
let rr = clamp(length(q) / max(rad, 0.001), 0.0, 1.0)
let on = mix(mix(ink, vec3(1.0, 1.0, 1.0), 0.65), ink * 0.85, smoothstep(0.0, 1.0, rr))
let off = mix(ink * 0.26, ink * 0.12, rr) + g3(0.02)
var col = mix(off, on, lit)
// a glint on the glass
let gq = q - vec2(-0.35, -0.4) * rad
col = col + g3(0.25 * exp(-dot(gq, gq) / max(rad * rad * 0.06, 0.0001)))
var out = vec4(col * a, a)
let ha = 0.28 * lit * exp(-max(d - px, 0.0) / (0.8 * px)) * (1.0 - a) * step(0.0, d)
return Finish.over(out, vec4(ink * ha, ha))
}
// ---------------------------------------------------------------------------
// BUTTONS. One face for the family; `kind` picks the dressing:
// 0 flat smoked plate, 1 smoked face in a brass frame, 2 a polished brass
// plate in a bronze frame (primary), 3 as 1 (secondary, brass legend),
// 4 smoked face in a copper frame (danger), 5 the brass frame alone,
// 6 bare until touched, 7 an aged bronze plate (tertiary), 8 smoked face in
// an iron frame (drop-down), 9 a brass cap in a thin bronze frame (segments).
// The quad is grown by `m` on every side for the shadow and the focus ring.
let bz_button = fn(pos: vec2, size: vec2, o: vec2, px: float, hover: float, down: float, focus: float, disabled: float, loading: float, anim: float, r: float, kind: float, m: float) -> vec4 {
let p = pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let live = 1.0 - disabled
let press = down * live
let hov = hover * live * (1.0 - press)
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o
let hi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y, px)) - o
let c = (lo + hi) * 0.5
let h = (hi - lo) * 0.5
let d = bz_rbox(p, c, h, r)
let isflat = 1.0 - step(0.5, kind)
let outl = step(4.5, kind) * step(kind, 5.5)
let bare = step(5.5, kind) * step(kind, 6.5)
let pad = step(6.5, kind) * step(kind, 7.5)
let plate = step(1.5, kind) * step(kind, 2.5) + step(8.5, kind)
let metal = step(3.5, kind) * step(kind, 4.5) + 2.0 * step(7.5, kind) * step(kind, 8.5)
let framed = 1.0 - isflat - bare - pad
let thin = step(7.5, kind)
let fw = mix(7.0 * px, 4.0 * px, thin) * framed
let solid = 1.0 - bare - outl
var out = vec4(0.0, 0.0, 0.0, 0.0)
let ink = bz_shadow_ink()
// the soft layer, faded within the gap to the next row
let soft = Finish.drop(p, c, h, r, vec2(0.5, 2.5), 2.6) * 0.45 * solid * (1.0 - press) * live * (1.0 - 0.5 * isflat)
out = Finish.over(out, vec4(ink * soft, soft))
let con = Finish.drop(p, c, h, r, vec2(0.0, px), px * 1.2) * 0.65 * mix(1.0, 0.3, press) * mix(1.0, 0.5, disabled) * solid
out = Finish.over(out, vec4(ink * con, con))
// the face inside the frame
let fy0 = lo.y + fw
let fh = max(2.0 * h.y - 2.0 * fw, 1.0)
let t = clamp((p.y - fy0) / fh, 0.0, 1.0)
let yi = p.y - fy0
let q = p + o
let br = (Finish.brushed(q, vec2(1.0, 0.0), px * 1.3) - 0.5) * 0.06
var face = bz_smoke(t)
if plate > 0.5 {
face = bz_brass(mix(mix(0.80, 0.74, thin), mix(0.56, 0.50, thin), t) + br + 0.07 * hov - 0.20 * press)
}
if pad > 0.5 {
face = bz_brass(mix(0.34, 0.20, t) + br * 0.6 + 0.04 * hov - 0.08 * press)
}
face = face + g3(0.05 * hov * (1.0 - plate - pad))
face = face * (1.0 + (Finish.fbm(q * 0.09) - 0.5) * 0.10 * (1.0 - plate - pad))
// pressed: the smoked face sinks: dark, its gloss gone
let sunk = mix(vec3(0.063, 0.059, 0.055), vec3(0.110, 0.106, 0.098), smoothstep(0.0, 3.0, yi))
face = mix(face, sunk, press * (1.0 - plate - pad))
// the frame's shadow inside the face, deeper while pressed
let ih = h - vec2(fw, fw)
let insh = Finish.inset(p, c, ih, max(r - fw * 0.5, 0.5), vec2(0.0, 1.0 + 1.6 * press), 1.0 + 1.2 * press)
face = mix(face, vec3(0.020, 0.016, 0.012), clamp(insh, 0.0, 1.0) * mix(0.45, 0.8, press) * framed)
face = mix(face, vec3(0.200, 0.184, 0.161), disabled * 0.85)
face = face + g3(bz_grain(q / px, 0.012))
// unframed plates: a lit top line and a dark edge
let up = 1.0 - smoothstep(-0.5, 0.5, p.y - c.y)
let topw = 1.0 - smoothstep(lo.y + r + 0.5, lo.y + r + 1.5, p.y)
let unf = isflat + pad
let hl = mix(vec3(0.459, 0.439, 0.404), bz_brass(0.62), pad)
face = mix(face, hl, Finish.band(d, px, 2.0 * px, px) * topw * (1.0 - press) * live * unf)
face = mix(face, bz_edge(), Finish.band(d, 0.0, px, px) * unf * 0.9)
// the frame: a bevelled bead of metal
let g = bz_grad(p, c, h, r)
var fl = bz_frame_l(d, g, max(fw, px)) + 0.08 * hov - 0.05 * press
fl = fl - 0.16 * step(1.5, kind) * step(kind, 2.5) - 0.12 * thin
var fcol = bz_metal(fl, metal)
fcol = mix(fcol, vec3(0.31, 0.28, 0.24), disabled * 0.75)
let fm = (1.0 - Finish.cover(d + fw, px)) * framed
// its dark outer line and the dark line where it meets the face
fcol = mix(fcol, bz_edge(), Finish.band(d, 0.0, px, px) * 0.85)
let inner = Finish.band(d + fw, -px, 0.0, px) * framed * 0.75
var col = mix(face, fcol, fm)
col = mix(col, bz_edge(), inner)
var fa = 1.0
if kind > 4.5 && kind < 6.5 {
fa = mix(0.0, 0.55, hov) + 0.45 * press
}
let alpha = Finish.cover(d, px) * mix(fa, 1.0, fm)
out = Finish.over(out, vec4(col * alpha, alpha))
// rivets on the long runs of a wide frame
if framed > 0.5 && size.x > 40.0 && size.y > 23.0 && kind < 7.5 {
let rr = fw * 0.36
let ry0 = lo.y + fw * 0.5 + 0.2
let ry1 = hi.y - fw * 0.5 - 0.2
let rx0 = lo.x + fw * 0.5 + 0.2
let rx1 = hi.x - fw * 0.5 - 0.2
let rx = mix(rx0, rx1, step(c.x, p.x))
let ry = mix(ry0, ry1, step(c.y, p.y))
let rv = bz_rivet(p, vec2(rx, ry), rr, px, metal, 0.08 * hov - 0.25 * disabled)
out = Finish.over(out, rv)
}
out = Finish.over(out, bz_focus(d, px, focus * live))
// the wait spinner, which the stock face drew and this one must too
if loading > 0.0 {
let sdf = Sdf2d.viewport(p)
let rs = max(min(h.x, h.y) - 5.0, 2.0)
let a0 = anim * 2.0 * PI
sdf.arc_round_caps(c.x, c.y, rs, a0, a0 + 1.5 * PI, 1.2)
let k = bz_acc()
sdf.fill(vec4(k * loading, loading))
out = Finish.over(out, sdf.result)
}
return out
}
// The drop-down face: smoked in an iron frame, an engraved split 22 points
// from its right end and a stroked chevron behind it; `held` is pressed.
let bz_dropdown = fn(pos: vec2, size: vec2, o: vec2, px: float, hover: float, held: float, focus: float, disabled: float, m: float) -> vec4 {
var out = bz_button(pos, size, o, px, hover, held, focus, disabled, 0.0, 0.0, 2.0, 8.0, m)
let p = pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let live = 1.0 - disabled
let dx = Finish.snap(o.x + size.x - 22.0, px) - o.x
let in_y = step(size.y * 0.22, p.y) * step(p.y, size.y * 0.78)
let div_d = step(dx - px, p.x) * step(p.x, dx) * in_y * 0.9
let div_l = step(dx, p.x) * step(p.x, dx + px) * in_y * 0.5 * (1.0 - held)
let bz = bz_edge()
out = Finish.over(out, vec4(bz * div_d, div_d))
let hl = vec3(0.420, 0.400, 0.369)
out = Finish.over(out, vec4(hl * div_l, div_l))
let cc = vec2(size.x - 11.5, Finish.snap(o.y + size.y * 0.5, px) - o.y + held * live * px)
let ch = Finish.cover(bz_chevron(p, cc, 6.0, 3.5, 0.0) - 0.65, px)
let ink = mix(mix(vec3(0.890, 0.847, 0.761), vec3(0.957, 0.922, 0.847), hover * live), vec3(0.478, 0.439, 0.376), disabled)
return Finish.over(out, vec4(ink * ch, ch))
}
let grow_vertex = fn(rp: vec2, rs: vec2, m: float) -> vec4 {
return vec4(rp.x - m, rp.y - m, rs.x + 2.0 * m, rs.y + 2.0 * m)
}
// Button faces. The flat root and the bare face keep to their own box, so
// every template derived from them with a face of its own is left as it is;
// the framed faces grow their quad for the shadow and the focus ring.
mod.widgets.ButtonFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return self.host_over(bz_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 2.0, 0.0, 0.0), 0.0)
}
mod.widgets.ButtonFlatter.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return self.host_over(bz_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 2.0, 6.0, 0.0), 0.0)
}
mod.widgets.Button.draw_bg.vertex = fn() {
let g = grow_vertex(self.rect_pos, self.rect_size, 8.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.Button.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return self.host_over(bz_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 2.0, 1.0, 8.0), 8.0)
}
mod.widgets.ButtonPrimary.draw_bg.vertex = fn() {
let g = grow_vertex(self.rect_pos, self.rect_size, 8.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.ButtonPrimary.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return self.host_over(bz_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 2.0, 2.0, 8.0), 8.0)
}
mod.widgets.ButtonSecondary.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return self.host_over(bz_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 2.0, 3.0, 8.0), 8.0)
}
mod.widgets.ButtonTertiary.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return self.host_over(bz_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 2.0, 7.0, 8.0), 8.0)
}
mod.widgets.ButtonDanger.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return self.host_over(bz_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 2.0, 4.0, 8.0), 8.0)
}
mod.widgets.ButtonOutline.draw_bg.vertex = fn() {
let g = grow_vertex(self.rect_pos, self.rect_size, 8.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.ButtonOutline.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return self.host_over(bz_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 2.0, 5.0, 8.0), 8.0)
}
mod.widgets.ButtonXs.draw_bg.vertex = fn() {
let g = grow_vertex(self.rect_pos, self.rect_size, 8.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.ButtonXs.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return self.host_over(bz_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 1.5, 0.0, 8.0), 8.0)
}
mod.widgets.ButtonSm.draw_bg.vertex = fn() {
let g = grow_vertex(self.rect_pos, self.rect_size, 8.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.ButtonSm.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return self.host_over(bz_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 2.0, 0.0, 8.0), 8.0)
}
mod.widgets.ButtonLg.draw_bg.vertex = fn() {
let g = grow_vertex(self.rect_pos, self.rect_size, 8.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.ButtonLg.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return self.host_over(bz_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 2.5, 1.0, 8.0), 8.0)
}
mod.widgets.ButtonXl.draw_bg.vertex = fn() {
let g = grow_vertex(self.rect_pos, self.rect_size, 8.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.ButtonXl.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return self.host_over(bz_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 3.0, 1.0, 8.0), 8.0)
}
mod.widgets.ButtonCompound.draw_bg.vertex = fn() {
let g = grow_vertex(self.rect_pos, self.rect_size, 8.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.ButtonCompound.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return self.host_over(bz_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 2.0, 1.0, 8.0), 8.0)
}
mod.widgets.Button.clip_x = false
mod.widgets.Button.clip_y = false
mod.widgets.ButtonPrimary.clip_x = false
mod.widgets.ButtonPrimary.clip_y = false
mod.widgets.ButtonOutline.clip_x = false
mod.widgets.ButtonOutline.clip_y = false
mod.widgets.ButtonXs.clip_x = false
mod.widgets.ButtonXs.clip_y = false
mod.widgets.ButtonSm.clip_x = false
mod.widgets.ButtonSm.clip_y = false
mod.widgets.ButtonLg.clip_x = false
mod.widgets.ButtonLg.clip_y = false
mod.widgets.ButtonXl.clip_x = false
mod.widgets.ButtonXl.clip_y = false
mod.widgets.ButtonCompound.clip_x = false
mod.widgets.ButtonCompound.clip_y = false
// The Select and MultiSelect face was built from Button before this sheet
// ran, so it takes the drop-down's face by its own path.
mod.widgets.Select.face.draw_bg.vertex = fn() {
let g = grow_vertex(self.rect_pos, self.rect_size, 8.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.Select.face.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return bz_dropdown(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, 8.0)
}
mod.widgets.Select.face.clip_x = false
mod.widgets.Select.face.clip_y = false
mod.widgets.Select.face.padding = mod.turtle.Inset{left: 10.0 right: 30.0 top: 4.0 bottom: 4.0}
mod.widgets.Select.face.min_height = 28.0
mod.widgets.MultiSelect.face.draw_bg.vertex = fn() {
let g = grow_vertex(self.rect_pos, self.rect_size, 8.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.MultiSelect.face.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return bz_dropdown(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, 8.0)
}
mod.widgets.MultiSelect.face.clip_x = false
mod.widgets.MultiSelect.face.clip_y = false
mod.widgets.MultiSelect.face.padding = mod.turtle.Inset{left: 10.0 right: 30.0 top: 4.0 bottom: 4.0}
mod.widgets.MultiSelect.face.min_height = 28.0
// Button geometry: one 28 point row, a 2 point radius.
mod.widgets.ButtonFlat.padding = mod.turtle.Inset{left: 10.0 right: 10.0 top: 4.0 bottom: 4.0}
mod.widgets.Button.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 4.0 bottom: 4.0}
mod.widgets.ButtonPrimary.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 4.0 bottom: 4.0}
mod.widgets.ButtonTertiary.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 4.0 bottom: 4.0}
mod.widgets.ButtonOutline.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 4.0 bottom: 4.0}
mod.widgets.ButtonIcon.padding = mod.turtle.Inset{left: 7.0 right: 7.0 top: 4.0 bottom: 4.0}
mod.widgets.Button.icon_walk = mod.turtle.Walk{width: 13.0 height: Fit}
mod.widgets.ButtonFlat.min_height = 28.0
mod.widgets.Button.min_height = 28.0
mod.widgets.ButtonXs.min_height = 18.0
mod.widgets.ButtonXs.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 1.0 bottom: 1.0}
mod.widgets.ButtonSm.min_height = 22.0
mod.widgets.ButtonLg.min_height = 32.0
// Legends: semibold cream on the smoked faces; dark and engraved on the
// brass plate; brass on the secondary; coral on the danger face.
mod.widgets.ButtonFlat.draw_text.text_style = mod.theme.font_regular{font_size: 8.25}
mod.widgets.ButtonFlat.draw_bg.loading_color = #x7fb6e8
mod.widgets.ButtonFlat.draw_icon_end.color = #xe3d8c2
mod.widgets.ButtonPrimary.draw_text.color = #261a0a
mod.widgets.ButtonPrimary.draw_text.color_hover = #160f05
mod.widgets.ButtonPrimary.draw_text.color_down = #1f1508
mod.widgets.ButtonPrimary.draw_text.color_focus = #261a0a
mod.widgets.ButtonPrimary.draw_text.color_disabled = #7a7060
mod.widgets.ButtonPrimary.draw_icon.color = #261a0a
mod.widgets.ButtonPrimary.draw_icon_end.color = #261a0a
mod.widgets.ButtonPrimary.draw_text.text_style = mod.theme.font_regular{font_size: 8.25}
mod.widgets.ButtonSecondary.draw_text.color = #d9bf82
mod.widgets.ButtonSecondary.draw_text.color_hover = #xecd79c
mod.widgets.ButtonSecondary.draw_text.color_down = #b89c62
mod.widgets.ButtonSecondary.draw_text.color_focus = #d9bf82
mod.widgets.ButtonSecondary.draw_icon.color = #d9bf82
mod.widgets.ButtonSecondary.draw_icon_end.color = #d9bf82
mod.widgets.ButtonTertiary.draw_text.color = #xe3d8c2
mod.widgets.ButtonTertiary.draw_text.color_hover = #xf4ebd8
mod.widgets.ButtonTertiary.draw_text.color_down = #c9bda4
mod.widgets.ButtonTertiary.draw_text.color_focus = #xe3d8c2
mod.widgets.ButtonTertiary.draw_icon.color = #xe3d8c2
mod.widgets.ButtonTertiary.draw_icon_end.color = #xe3d8c2
mod.widgets.ButtonDanger.draw_text.color = #f09a86
mod.widgets.ButtonDanger.draw_text.color_hover = #f8b6a6
mod.widgets.ButtonDanger.draw_text.color_down = #c87a68
mod.widgets.ButtonDanger.draw_text.color_focus = #f09a86
mod.widgets.ButtonDanger.draw_icon.color = #f09a86
mod.widgets.ButtonDanger.draw_icon_end.color = #f09a86
mod.widgets.ButtonOutline.draw_text.color = #xe3d8c2
mod.widgets.ButtonOutline.draw_text.color_hover = #xf4ebd8
mod.widgets.ButtonOutline.draw_text.color_down = #c9bda4
mod.widgets.ButtonOutline.draw_text.color_focus = #xe3d8c2
mod.widgets.ButtonOutline.draw_text.color_disabled = #7a7060
mod.widgets.ButtonOutline.draw_icon.color = #xe3d8c2
// ---------------------------------------------------------------------------
// CHECK BOXES. A 14 point window of dark glass in a brass bezel; set, the
// glass is lit steel blue behind a cream tick.
// A glass window by height t: dark smoked glass, or lit blue.
let bz_glass = fn(t: float, on: float) -> vec3 {
let off_c = mix(vec3(0.192, 0.180, 0.165), vec3(0.067, 0.063, 0.059), smoothstep(0.0, 0.75, t))
let on_c = mix(vec3(0.561, 0.745, 0.918), vec3(0.216, 0.373, 0.557), smoothstep(0.0, 1.0, t))
return mix(off_c, on_c, on)
}
mod.widgets.CheckBoxFlat.padding = mod.turtle.Inset{left: 0.0 right: 3.0 top: 7.0 bottom: 7.0}
mod.widgets.CheckBoxFlat.label_walk.margin = mod.turtle.Inset{left: 22.0 right: 3.0 top: 0.0 bottom: 0.0}
mod.widgets.CheckBoxFlat.draw_bg.size = uniform(14.0)
mod.widgets.CheckBoxFlat.min_height = 28.0
mod.widgets.CheckBoxFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * self.rect_size
let o = self.rect_pos
let sz = self.size
let x0 = mix(2.0, self.rect_size.x - sz - 2.0, self.mark_at_end)
let lo = vec2(Finish.snap(o.x + x0, px), Finish.snap(o.y + (self.rect_size.y - sz) * 0.5, px)) - o
let h = vec2(sz * 0.5, sz * 0.5)
let c = lo + h
let r = mix(2.0, h.y, step(20.0, self.border_radius))
let d = bz_rbox(p, c, h, r)
let live = 1.0 - self.disabled
let hov = self.hover * live * (1.0 - self.down)
let fw = 3.0 * px
let on = self.active * live
// the glass
let t = clamp((p.y - lo.y - fw) / max(sz - 2.0 * fw, 1.0), 0.0, 1.0)
var col = bz_glass(t, max(on, self.mixed * live))
col = col + g3(0.06 * hov - 0.03 * self.down * live)
// a reflection over the top third of the glass
col = col + g3(0.07 * (1.0 - smoothstep(0.05, 0.38, t)) * (1.0 - 0.5 * on))
col = mix(col, vec3(0.20, 0.18, 0.16), self.disabled * 0.7)
let ih = h - vec2(fw, fw)
let insh = Finish.inset(p, c, ih, max(r - fw, 0.5), vec2(0.0, 1.0 + self.down), 1.0)
col = mix(col, vec3(0.02, 0.02, 0.03), clamp(insh, 0.0, 1.0) * 0.55)
// the tick or the mixed dash, 1.9 points, cream
let k = sz / 14.0
let m0 = lo + vec2(3.6, 7.2) * k
let m1 = lo + vec2(6.0, 9.7) * k
let m2 = lo + vec2(10.5, 4.4) * k
let ck = min(bz_seg(p, m0, m1), bz_seg(p, m1, m2)) - 0.95 * k
let dash = bz_seg(p, lo + vec2(4.0, 7.0) * k, lo + vec2(10.0, 7.0) * k) - 0.95 * k
let show_ck = self.active * (1.0 - self.mixed)
let md = mix(mix(1000.0, ck, show_ck), dash, self.mixed)
let mk = Finish.cover(md, px)
var tick = mix(vec3(0.980, 0.961, 0.910), vec3(0.620, 0.600, 0.560), self.disabled)
col = mix(col, vec3(0.10, 0.17, 0.26), Finish.cover(md - px, px) * 0.5 * live)
col = mix(col, tick, mk)
// the brass bezel
let g = bz_grad(p, c, h, r)
var bl = bz_frame_l(d, g, fw) + 0.14 * hov
var bcol = mix(bz_brass(bl), vec3(0.34, 0.31, 0.27), self.disabled * 0.7)
bcol = mix(bcol, vec3(0.86, 0.33, 0.25), self.error * 0.8)
bcol = mix(bcol, bz_edge(), Finish.band(d, 0.0, px, px) * 0.8)
let fm = 1.0 - Finish.cover(d + fw, px)
col = mix(col, bcol, fm)
col = mix(col, bz_edge(), Finish.band(d + fw, -px, 0.0, px) * 0.7)
let a = Finish.cover(d, px)
var out = vec4(0.0, 0.0, 0.0, 0.0)
let ink = bz_shadow_ink()
let sh = Finish.drop(p, c, h, r, vec2(0.3, 1.0), 1.0) * 0.5
out = Finish.over(out, vec4(ink * sh, sh))
out = Finish.over(out, vec4(col * a, a))
return Finish.over(out, bz_focus(d, px, self.focus * live))
}
// ---------------------------------------------------------------------------
// TOGGLES. An iron slot 38 by 16 points holding a knurled brass block; the
// part of the slot the block leaves open is a glass window, dark oxblood
// when off and lit steel blue when on.
mod.widgets.ToggleFlat.label_walk.margin = mod.turtle.Inset{left: 46.0 right: 3.0 top: 0.0 bottom: 0.0}
mod.widgets.ToggleFlat.draw_bg.size = uniform(16.0)
mod.widgets.ToggleFlat.min_height = 28.0
mod.widgets.ToggleFlat.padding = mod.turtle.Inset{left: 0.0 right: 3.0 top: 6.0 bottom: 6.0}
mod.widgets.ToggleFlat.draw_bg.pill_aspect = uniform(2.375)
mod.widgets.ToggleFlat.draw_bg.knob_inset = uniform(0.0)
mod.widgets.ToggleFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * self.rect_size
let o = self.rect_pos
let sz = vec2(self.size * self.pill_aspect, self.size)
let x0 = mix(2.0, self.rect_size.x - sz.x - 2.0, self.mark_at_end)
let lo = vec2(Finish.snap(o.x + x0, px), Finish.snap(o.y + (self.rect_size.y - sz.y) * 0.5, px)) - o
let h = sz * 0.5
let c = lo + h
let r = 2.5
let d = bz_rbox(p, c, h, r)
let live = 1.0 - self.disabled
let hov = self.hover * live * (1.0 - self.down)
let knob_t = mix(self.active, self.drag_pos, self.drag)
let fw = 3.0 * px
let ink = bz_shadow_ink()
var out = vec4(0.0, 0.0, 0.0, 0.0)
let sh = Finish.drop(p, c, h, r, vec2(0.3, 1.2), 1.2) * 0.5
out = Finish.over(out, vec4(ink * sh, sh))
// the slot: an iron frame round a dark floor
let ih = h - vec2(fw, fw)
var col = bz_well(p, c, ih, max(r - fw, 0.5), px, vec3(0.071, 0.063, 0.055), 2.2)
let g = bz_grad(p, c, h, r)
var il = bz_frame_l(d, g, fw) + 0.12 * hov
var icol = mix(bz_iron(il), vec3(0.24, 0.22, 0.20), self.disabled * 0.6)
icol = mix(icol, vec3(0.86, 0.33, 0.25), self.error * 0.8)
icol = mix(icol, bz_edge(), Finish.band(d, 0.0, px, px) * 0.85)
let fm = 1.0 - Finish.cover(d + fw, px)
col = mix(col, icol, fm)
let a = Finish.cover(d, px)
out = Finish.over(out, vec4(col * a, a))
// the block: 16 points wide, its left edge on a pixel boundary
let bw = 16.0
let bx = Finish.snap(o.x + lo.x + knob_t * (sz.x - bw), px) - o.x
let bh = vec2(bw * 0.5, h.y)
let bc = vec2(bx + bh.x, c.y)
// the window in the open part of the slot
let wx0 = mix(bx + bw + px, lo.x + fw + 1.5 * px, knob_t)
let wx1 = mix(lo.x + sz.x - fw - 1.5 * px, bx - px, knob_t)
let wh = vec2(max((wx1 - wx0) * 0.5, 1.0), h.y - fw - 1.5 * px)
let wc = vec2((wx0 + wx1) * 0.5, c.y)
let wd = bz_rbox(p, wc, wh, 1.0)
let wt = clamp((p.y - (wc.y - wh.y)) / max(2.0 * wh.y, 1.0), 0.0, 1.0)
let on = self.active * live
let off_c = mix(vec3(0.259, 0.133, 0.098), vec3(0.137, 0.067, 0.047), wt)
let k = bz_acc()
let wq = (p - wc) / max(wh, vec2(0.5, 0.5))
let hot = exp(-dot(wq, wq) * 1.4)
let on_c = mix(k * 0.8, mix(k, bz_acc_hot(), 0.7), hot)
var wcol = mix(off_c, on_c, on)
wcol = wcol + g3(0.06 * (1.0 - smoothstep(0.0, 0.4, wt)))
wcol = mix(wcol, vec3(0.16, 0.15, 0.14), self.disabled * 0.6)
let wa = Finish.cover(wd, px)
out = Finish.over(out, vec4(wcol * wa, wa))
// the lit window's light on the floor round it
let ha = 0.35 * on * exp(-max(wd, 0.0) / (1.4 * px)) * (1.0 - wa) * step(0.0, wd) * (1.0 - fm)
out = Finish.over(out, vec4(k * ha, ha))
// the block's shadow: contact and a short soft layer
let sd = Finish.drop(p, bc, bh, 1.5, vec2(0.7, 1.2), 1.2) * 0.45 * live
out = Finish.over(out, vec4(ink * sd, sd))
let bd = bz_rbox(p, bc, bh, 2.0)
// a brass cylinder across the width, knurled with fine ridges
let u = clamp((p.x - (bc.x - bh.x)) / bw, 0.0, 1.0)
let cyl = sin(u * PI)
var bl = 0.26 + 0.52 * cyl * cyl + 0.10 * smoothstep(0.2, 0.4, u) * (1.0 - smoothstep(0.4, 0.6, u))
let ridge = step(0.5, fract((p.x + o.x) / (2.0 * px)))
bl = bl + (ridge - 0.5) * 0.12 * step(0.12, u) * step(u, 0.88)
let bv = clamp((p.y - (bc.y - bh.y)) / max(2.0 * bh.y, 1.0), 0.0, 1.0)
bl = bl * mix(1.08, 0.82, bv) + 0.11 * hov - 0.06 * self.down * live
var face = bz_brass(bl)
face = mix(face, vec3(0.30, 0.28, 0.25), self.disabled * 0.75)
let bt = p.y - (bc.y - bh.y)
face = mix(face, bz_brass(0.9), step(0.0, bt) * step(bt, px - 0.01) * live * 0.7)
face = mix(face, bz_edge(), Finish.band(bd, 0.0, px, px) * mix(0.5, 0.9, smoothstep(-0.5, 0.5, p.y - bc.y)))
let ba = Finish.cover(bd, px)
out = Finish.over(out, vec4(face * ba, ba))
return Finish.over(out, bz_focus(d, px, self.focus * live))
}
// ---------------------------------------------------------------------------
// RADIOS. A round window in a brass bezel; the chosen one is a steel blue
// jewel lamp, white-hot at its centre.
mod.widgets.RadioButtonFlat.padding = mod.turtle.Inset{left: 0.0 right: 3.0 top: 7.0 bottom: 7.0}
mod.widgets.RadioButtonFlat.label_walk.margin = mod.turtle.Inset{left: 22.0 right: 3.0 top: 0.0 bottom: 0.0}
mod.widgets.RadioButtonFlat.draw_bg.size = uniform(14.0)
mod.widgets.RadioButtonFlat.min_height = 28.0
mod.widgets.RadioButtonFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * self.rect_size
let o = self.rect_pos
let sz = self.size
let kf = bz_fit(vec2(2.0 + sz * 0.5, self.rect_size.y * 0.5), o, sz * 0.5, px)
let c = kf.xy
let rad = kf.z
let h = vec2(rad, rad)
let live = 1.0 - self.disabled
let hov = self.hover * live * (1.0 - self.down)
let q = p - c
let d = length(q) - rad
let fw = 3.0 * px
let t = clamp((p.y - (c.y - rad + fw)) / max(2.0 * (rad - fw), 1.0), 0.0, 1.0)
var col = bz_glass(t, 0.0) + g3(0.06 * hov - 0.03 * self.down * live)
col = col + g3(0.07 * (1.0 - smoothstep(0.05, 0.4, t)))
col = mix(col, vec3(0.20, 0.18, 0.16), self.disabled * 0.7)
// the brass bezel
let g = q / max(length(q), 0.001)
var bl = bz_frame_l(d, g, fw) + 0.14 * hov
var bcol = mix(bz_brass(bl), vec3(0.34, 0.31, 0.27), self.disabled * 0.7)
bcol = mix(bcol, bz_edge(), Finish.band(d, 0.0, px, px) * 0.8)
let fm = 1.0 - Finish.cover(d + fw, px)
col = mix(col, bcol, fm)
col = mix(col, bz_edge(), Finish.band(d + fw, -px, 0.0, px) * 0.7)
let a = Finish.cover(d, px)
var out = vec4(0.0, 0.0, 0.0, 0.0)
let ink = bz_shadow_ink()
let sh = Finish.drop(p, c, h, rad, vec2(0.3, 1.0), 1.0) * 0.5
out = Finish.over(out, vec4(ink * sh, sh))
out = Finish.over(out, vec4(col * a, a))
let bead = bz_bead(p, c, rad - fw - 0.5 * px, px, mix(bz_acc(), vec3(0.30, 0.30, 0.32), self.disabled), live)
out = Finish.over(out, vec4(bead.rgb * self.active, bead.a * self.active))
return Finish.over(out, bz_focus(d, px, self.focus * live))
}
// Radio tabs: brass caps; the chosen one is pushed in, dark, its legend lit
// blue over a small lamp.
let bz_pushed = fn(p: vec2, lo: vec2, hi: vec2, o: vec2, px: float, lamp: float) -> vec4 {
let inside = step(lo.x, p.x) * (1.0 - step(hi.x, p.x)) * step(lo.y, p.y) * (1.0 - step(hi.y, p.y))
let t = clamp((p.y - lo.y) / max(hi.y - lo.y, 1.0), 0.0, 1.0)
var col = mix(vec3(0.106, 0.098, 0.086), vec3(0.086, 0.078, 0.067), t)
col = col + g3(bz_grain((p + o) / px, 0.008))
let top = exp(-max(p.y - lo.y, 0.0) / (2.4 * px))
let wl = 0.8 * exp(-max(p.x - lo.x, 0.0) / (1.5 * px))
let wr = 0.8 * exp(-max(hi.x - px - p.x, 0.0) / (1.5 * px))
let foot = 0.4 * step(hi.y - px, p.y)
let dk = 1.0 - (1.0 - top) * (1.0 - wl) * (1.0 - wr) * (1.0 - foot)
col = mix(col, vec3(0.012, 0.008, 0.004), clamp(dk, 0.0, 1.0))
// the lamp's light pooling on the floor
let k = bz_acc()
let lc = vec2(Finish.snap(o.x + (lo.x + hi.x) * 0.5, px) - o.x, Finish.snap(o.y + hi.y, px) - o.y - 5.0 * px)
let qq = vec2((p.x - lc.x) / max((hi.x - lo.x) * 0.26, 1.0), (p.y - lc.y) / 3.4)
let pool = exp(-dot(qq, qq)) * 0.22 * (1.0 - 0.6 * top) * lamp
col = mix(col, k * 0.85, pool)
var res = vec4(col, 1.0) * inside
let ld = bz_rbox(p, lc, vec2(7.0 * px, px), px)
let la = Finish.cover(ld, px) * lamp
let hot = mix(mix(k, vec3(1.0, 1.0, 1.0), 0.7), mix(k, vec3(1.0, 1.0, 1.0), 0.25), step(lc.y, p.y))
let near = 0.42 * exp(-max(ld, 0.0) / (1.1 * px)) + 0.14 * exp(-max(ld, 0.0) / (3.0 * px))
let ha = clamp(near, 0.0, 1.0) * (1.0 - Finish.cover(ld, px)) * step(0.0, ld) * lamp
res = Finish.over(res, vec4(k * ha, ha) * inside)
res = Finish.over(res, vec4(hot * la, la) * inside)
return res
}
mod.widgets.RadioButtonTabFlat.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 7.0 bottom: 7.0}
mod.widgets.RadioButtonTabFlat.min_height = 28.0
mod.widgets.RadioButtonTabFlat.label_walk.margin = mod.turtle.Inset{left: 0.0 right: 0.0 top: 0.0 bottom: 0.0}
mod.widgets.RadioButtonTabFlat.clip_x = false
mod.widgets.RadioButtonTabFlat.clip_y = false
mod.widgets.RadioButtonTabFlat.draw_bg.vertex = fn() {
let g = grow_vertex(self.rect_pos, self.rect_size, 8.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.RadioButtonTabFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = 8.0
let size = self.rect_size
let o = self.rect_pos
let live = 1.0 - self.disabled
let on = self.active
var out = bz_button(self.pos, size, o, px, self.hover * (1.0 - on), self.down * (1.0 - on), 0.0, self.disabled, 0.0, 0.0, 2.0, 9.0, m)
let pf = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let flo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o
let fhi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y, px)) - o
let fd = bz_rbox(pf, (flo + fhi) * 0.5, (fhi - flo) * 0.5, 2.0)
// focus: the frame's inner edge in the accent, inside the box (a ring
// outside it is cut by a row that clips)
let fk = Finish.band(fd, 4.0 * px, 5.0 * px, px) * 0.9 * self.focus * live * (1.0 - on)
out = Finish.over(out, vec4(bz_acc() * fk, fk))
if on > 0.001 {
let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o
let hi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y, px)) - o
// the frame stays; the cap inside it is pushed in
let well = bz_pushed(p, lo + vec2(3.0 * px, 3.0 * px), hi - vec2(3.0 * px, 3.0 * px), o, px, live)
out = Finish.over(out, well * on)
let fk2 = Finish.band(fd, 3.0 * px, 4.0 * px, px) * 0.9 * self.focus * live
out = Finish.over(out, vec4(bz_acc() * fk2, fk2) * on)
}
return out
}
mod.widgets.RadioButtonTabFlat.draw_text.text_style = mod.theme.font_regular{font_size: 8.25}
mod.widgets.RadioButtonTabFlat.draw_text.color = #261a0a
mod.widgets.RadioButtonTabFlat.draw_text.color_hover = #160f05
mod.widgets.RadioButtonTabFlat.draw_text.color_down = #1f1508
mod.widgets.RadioButtonTabFlat.draw_text.color_focus = #261a0a
mod.widgets.RadioButtonTabFlat.draw_text.color_active = #9ccbf2
mod.widgets.RadioButtonTabFlat.draw_text.color_disabled = #7a7060
// ---------------------------------------------------------------------------
// SLIDERS. An engraved groove in the plate, the value a polished brass
// inlay inside it, and a brass gear wheel for the thumb: teeth, a rim,
// spokes with open windows, a domed hub that lights blue while the thumb is
// held or focused.
mod.widgets.SliderMinimal.draw_bg.vertex = fn() {
let g = grow_vertex(self.rect_pos, self.rect_size, 6.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.SliderMinimal.clip_x = false
mod.widgets.SliderMinimal.clip_y = false
// The groove, `th` tall, and its brass inlay from the start to `fx`.
let bz_track = fn(p: vec2, lo: vec2, w: float, th: float, px: float, fx: float, dis: float, hot: float) -> vec4 {
let h = vec2(w * 0.5, th * 0.5)
let c = lo + h
let r = h.y
let d = bz_rbox(p, c, h, r)
let fill = mix(vec3(0.059, 0.051, 0.043), vec3(0.14, 0.13, 0.12), dis)
var col = bz_well(p, c, h, r, px, fill, 1.6)
// the inlay: a strip of polished brass inside the groove, lit on top
let ih = vec2(h.x - px, h.y - px)
let id = bz_rbox(p, c, ih, max(r - px, 0.3))
let inx = step(p.x, fx)
let it = clamp((p.y - (c.y - ih.y)) / max(2.0 * ih.y, 0.5), 0.0, 1.0)
var inl = bz_brass(mix(0.86, 0.46, it) + 0.06 * hot)
inl = mix(inl, vec3(0.28, 0.26, 0.23), dis)
col = mix(col, inl, Finish.cover(id, px) * inx)
let a = Finish.cover(d, px)
let out = bz_lip(p, c, h, r, px, 0.09)
return Finish.over(out, vec4(col * a, a))
}
// A brass gear wheel of radius `rad` with `teeth` teeth; `spokes` 0 draws a
// plain knurled disc.
let bz_gear = fn(p: vec2, c: vec2, rad: float, px: float, teeth: float, spokes: float, hover: float, press: float, dis: float, lamp: float) -> vec4 {
let q = p - c
let r = length(q)
let ang = atan2(q.y, q.x)
let toothh = max(rad * 0.15, 1.1)
let root = rad - toothh
let f = fract(ang / (2.0 * PI) * teeth)
let tw = smoothstep(0.10, 0.28, f) * (1.0 - smoothstep(0.62, 0.80, f))
let edge_r = mix(root, rad, tw)
let d = r - edge_r
var out = vec4(0.0, 0.0, 0.0, 0.0)
let cast = Finish.drop(p, c, vec2(rad, rad), rad, vec2(0.7, 1.5), 1.3) * 0.55 * mix(1.0, 0.4, dis)
out = Finish.over(out, vec4(bz_shadow_ink() * cast, cast))
let n = q / max(r, 0.001)
let facing = -0.92 * n.y - 0.38 * n.x
let lift = 0.06 * hover - 0.06 * press
// teeth and rim: a bead lit from the top left
let rimw = max(rad * 0.2, 1.4)
let inner_r = root - rimw
let rt = clamp((r - inner_r) / max(rad - inner_r, 0.001), 0.0, 1.0)
var l = 0.56 + 0.26 * facing * (2.0 * rt - 0.6) + 0.08 * facing + lift
let s = (rt - 0.45) / 0.22
l = l + 0.16 * exp(-s * s) * max(facing, 0.0)
// the web: spokes, and dark windows between them
let hub_r = rad * 0.30
let da = (fract(ang / (2.0 * PI) * max(spokes, 1.0) + 0.5) - 0.5) * 2.0 * PI / max(spokes, 1.0)
let sd = abs(r * sin(da)) - max(rad * 0.09, 0.6)
let web = 1.0 - step(inner_r, r)
let win = web * step(hub_r, r) * Finish.cover(-sd, px) * step(0.5, spokes)
// a plain disc: a spun face inside the rim
let spun = 0.50 + 0.18 * (-n.y) + 0.10 * cos(2.0 * ang + 0.3) + lift
l = mix(l, mix(spun, 0.62 + 0.14 * facing + lift, step(0.5, spokes)), web)
// the hub: a dome
let hq = q / max(hub_r, 0.001)
let hub = 1.0 - step(hub_r, r)
let dome = 0.55 - 0.3 * hq.y - 0.1 * hq.x + 0.3 * exp(-dot(hq + vec2(0.35, 0.45), hq + vec2(0.35, 0.45)) * 3.0) + lift
l = mix(l, dome, hub * step(0.5, spokes))
var col = bz_brass(l)
col = mix(col, vec3(0.031, 0.024, 0.016), win * 0.92)
// dark lines at the rim's inner edge and round the hub
col = mix(col, bz_edge(), Finish.band(r - inner_r, -px, 0.0, px) * 0.6 * step(0.5, spokes))
col = mix(col, bz_edge(), Finish.band(r - hub_r, 0.0, px, px) * 0.5 * step(0.5, spokes))
col = mix(col, bz_edge(), Finish.band(d, 0.0, px, px) * 0.7)
col = mix(col, mix(g3(0.30), col * 0.5, 0.4), dis)
let a = Finish.cover(d, px)
out = Finish.over(out, vec4(col * a, a))
// the lit hub jewel
let jewel = bz_bead(p, c, max(hub_r * 0.75, 1.2), px, bz_acc(), 1.0)
return Finish.over(out, jewel * lamp)
}
mod.widgets.SliderFlat.height = 40.0
mod.widgets.SliderFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = 6.0
let size = self.rect_size
let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let o = self.rect_pos
let live = 1.0 - self.disabled
// the gear fills what the label band leaves, 12 to 20 points across
let tr = clamp((size.y - self.label_size) * 0.5 - 1.0, 6.0, 10.0)
let cy = size.y - tr - 1.0
let th = 6.0 * px
let top = Finish.snap(o.y + cy, px) - o.y - 3.0 * px
let x0 = Finish.snap(o.x + 1.0, px) - o.x
let w = Finish.snap(o.x + size.x - 1.0, px) - o.x - x0
let kx = x0 + tr + self.slide_pos * (w - 2.0 * tr)
let hot = max(self.hover, self.drag) * live
var out = bz_track(p, vec2(x0, top), w, th, px, kx, self.disabled, hot)
let kf = bz_fit(vec2(kx, top + th * 0.5), o, tr, px)
out = Finish.over(out, bz_gear(p, kf.xy, kf.z, px, 15.0, 6.0, self.hover * live, self.drag * live, self.disabled, max(self.focus, self.drag) * live))
return Finish.over(out, bz_focus(length(p - kf.xy) - kf.z, px, self.focus * live))
}
// The minimal slider: a hairline groove, the brass thread in it and a small
// knurled brass cap.
mod.widgets.SliderMinimal.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = 6.0
let size = self.rect_size
let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let o = self.rect_pos
let live = 1.0 - self.disabled
let cy = Finish.snap(o.y + size.y - 5.0, px) - o.y
let tr = mix(4.5, 5.0, max(self.hover, self.drag))
let kx = tr + self.slide_pos * (size.x - 2.0 * tr)
let inx = step(0.0, p.x) * step(p.x, size.x)
let dark = step(cy - 2.0 * px, p.y) * step(p.y, cy) * inx
let light = step(cy, p.y) * step(p.y, cy + px) * inx * 0.08
let lit = step(p.x, kx) * dark
var out = vec4(light, light, light, light)
let g = mix(vec3(0.047, 0.039, 0.031), mix(bz_brass(0.72), vec3(0.3, 0.28, 0.25), self.disabled), lit)
out = Finish.over(out, vec4(g * dark, dark))
let kf = bz_fit(vec2(kx, cy - px), o, tr, px)
out = Finish.over(out, bz_gear(p, kf.xy, kf.z, px, 12.0, 0.0, self.hover * live, self.drag * live, self.disabled, 0.0))
return Finish.over(out, bz_focus(length(p - kf.xy) - kf.z, px, self.focus * live))
}
mod.widgets.SliderMinimal.text_input.draw_text.text_style = mod.theme.font_regular{font_size: 7.5}
// The round slider: a pill well beside its label, the brass inlay inside
// it and a knurled brass cap riding in the well.
mod.widgets.SliderRoundFlat.height = 16.0
mod.widgets.SliderRoundFlat.text_input.margin = mod.turtle.Inset{left: 0.0 right: 8.0 top: 0.0 bottom: 0.0}
mod.widgets.SliderRoundFlat.text_input.min_height = 0.0
mod.widgets.SliderRoundFlat.text_input.padding = mod.turtle.Inset{left: 0.0 right: 0.0 top: 0.0 bottom: 0.0}
mod.widgets.SliderRoundFlat.text_input.draw_text.text_style = mod.theme.font_regular{font_size: 7.5}
mod.widgets.SliderRoundFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = 6.0
let size = self.rect_size
let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let o = self.rect_pos
let live = 1.0 - self.disabled
let x0 = Finish.snap(o.x + self.label_size, px) - o.x
let y0 = Finish.snap(o.y, px) - o.y
let hi = vec2(Finish.snap(o.x + size.x, px) - o.x, Finish.snap(o.y + size.y, px) - o.y - px)
let h = (hi - vec2(x0, y0)) * 0.5
let c = vec2(x0, y0) + h
let r = h.y
let d = bz_rbox(p, c, h, r)
let fill = mix(vec3(0.071, 0.063, 0.055), vec3(0.14, 0.13, 0.12), self.disabled) + g3(0.018 * self.hover * live)
var col = bz_well(p, c, h, r, px, fill, 2.2)
let cr = h.y - 2.0 * px
let kx = x0 + 2.0 * px + cr + self.slide_pos * (2.0 * h.x - 4.0 * px - 2.0 * cr)
// the inlay, four device pixels at the well's middle
let mid = Finish.snap(o.y + c.y, px) - o.y
let ib = bz_rbox(p, vec2((x0 + 3.0 * px + kx) * 0.5, mid), vec2(max((kx - x0 - 3.0 * px) * 0.5, 0.0), 2.0 * px), px)
let it = clamp((p.y - (mid - 2.0 * px)) / (4.0 * px), 0.0, 1.0)
var inl = bz_brass(mix(0.86, 0.46, it))
inl = mix(inl, vec3(0.28, 0.26, 0.23), self.disabled)
col = mix(col, inl, Finish.cover(ib, px))
let a = Finish.cover(d, px)
var out = bz_lip(p, c, h, r, px, 0.08)
out = Finish.over(out, vec4(col * a, a))
let kf = bz_fit(vec2(kx, c.y), o, cr, px)
out = Finish.over(out, bz_gear(p, kf.xy, kf.z, px, 12.0, 0.0, self.hover * live, self.drag * live, self.disabled, 0.0))
return Finish.over(out, bz_focus(d, px, self.focus * live))
}
// ---------------------------------------------------------------------------
// KNOBS. A knurled brass cap: a serrated edge, a spun brass face bright at
// its centre, an engraved pointer. Round it an engraved scale; the ticks up
// to the value are polished brass, the rest cut dark into the plate.
let bz_knob = fn(p: vec2, c: vec2, rad: float, px: float, val: float, gap: float, hover: float, press: float, dis: float) -> vec4 {
let dia = rad * 2.0
let q = p - c
let r = length(q)
let ang = atan2(q.y, q.x)
var out = vec4(0.0, 0.0, 0.0, 0.0)
let cast = Finish.drop(p, c, vec2(rad, rad), rad, vec2(dia * 0.06, dia * 0.09), dia * 0.07) * 0.6 * mix(1.0, 0.4, dis)
out = Finish.over(out, vec4(bz_shadow_ink() * cast, cast))
// serrations: one tooth per 1.7 points of circumference
let nt = floor(2.0 * PI * rad / 1.7)
let kn = 0.5 + 0.5 * cos(ang * nt)
let d = r - (rad - 0.45 * (1.0 - kn))
let n = q / max(r, 0.001)
let facing = -0.92 * n.y - 0.38 * n.x
let lift = 0.05 * hover - 0.05 * press
let kw = max(rad * 0.21, 2.0)
let fr = rad - kw
// the knurled band: ridges catching the light
var l = 0.44 + 0.22 * facing + 0.22 * (kn - 0.5) * (0.6 + 0.4 * max(facing, 0.0)) + lift
// the face: spun brass, a bowtie sheen and a bright centre
let spin = cos(2.0 * (ang + 1.35))
let rr = r / max(fr, 0.001)
var fl = 0.48 + 0.17 * spin * (0.3 + 0.7 * rr) + 0.22 * (1.0 - rr * rr) + 0.10 * facing + lift
let on_face = Finish.cover(r - fr, px)
l = mix(l, fl, on_face)
// a chamfer ring between band and face
l = l + 0.22 * Finish.band(r - fr, 0.0, 1.5 * px, px) * (0.4 + 0.6 * max(facing, 0.0))
l = l - 0.18 * Finish.band(r - fr, -px, 0.0, px)
var col = bz_brass(l)
col = mix(col, bz_edge(), Finish.band(d, 0.0, px, px) * 0.6)
// the pointer, engraved: a dark line with a light line under it
let pang = gap * 0.5 + (2.0 * PI - gap) * val
let dir = vec2(-sin(pang), cos(pang))
let pa = c + dir * fr * 0.52
let pb = c + dir * fr * 0.90
let pw = max(dia * 0.02, 0.6)
let pl = bz_seg(p, pa, pb) - pw
let pl_lit = bz_seg(p - vec2(0.0, px), pa, pb) - pw
col = mix(col, bz_brass(0.95), Finish.cover(pl_lit, px) * 0.5 * (1.0 - dis))
col = mix(col, vec3(0.110, 0.075, 0.035), Finish.cover(pl, px))
col = mix(col, mix(g3(0.32), col * 0.5, 0.4), dis)
let a = Finish.cover(d, px)
return Finish.over(out, vec4(col * a, a))
}
// The scale round a knob: `n` steps between `r0` and `r1`, majors every
// fifth reaching further in.
let bz_scale = fn(p: vec2, c: vec2, r0: float, r1: float, px: float, val: float, gap: float, n: float, dis: float, hot: float) -> vec4 {
let start = gap * 0.5
let sweep = 2.0 * PI - gap
let q = p - c
var ang = atan2(-q.x, q.y)
if ang < 0.0 {
ang = ang + 2.0 * PI
}
let r = length(q)
let k = (ang - start) / sweep * n
let ki = clamp(floor(k + 0.5), 0.0, n)
let da = (k - ki) * sweep / n * r
let major = 1.0 - step(0.1, abs(fract(ki / 5.0 + 0.5) - 0.5))
let rin = mix(r0 + (r1 - r0) * 0.45, r0, major)
let inr = step(rin, r) * step(r, r1)
let wid = mix(0.5 * px, 0.8 * px, major)
let tk = (1.0 - smoothstep(wid - 0.3 * px, wid + 0.3 * px, abs(da))) * inr
let lit = step(ki / n, val + 0.0001) * (1.0 - dis)
let col = mix(vec3(0.035, 0.027, 0.020), bz_brass(0.86 + 0.06 * hot), lit)
let a = tk * mix(0.75, 1.0, lit)
// the light lip beside an engraved tick
let lip = (1.0 - smoothstep(wid - 0.3 * px, wid + 0.3 * px, abs(da - px))) * inr * 0.07 * (1.0 - lit) * step(0.0, da)
return Finish.over(vec4(lip, lip * 0.95, lip * 0.85, lip), vec4(col * a, a))
}
mod.widgets.RotaryKnob.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = 6.0
let size = self.rect_size
let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let o = self.rect_pos
let live = 1.0 - self.disabled
let dia = min(size.x, size.y)
let rr = dia * 0.5
let c = vec2(Finish.snap(o.x + size.x * 0.5, px) - o.x, Finish.snap(o.y + size.y - rr, px) - o.y)
let gap = self.gap * PI / 180.0
let rad = floor(rr * 0.74 / px) * px
let hot = max(self.hover, self.drag) * live
var out = bz_scale(p, c, rad + 1.5, rr - 0.2, px, self.slide_pos, gap, 20.0, self.disabled, hot)
out = Finish.over(out, bz_knob(p, c, rad, px, self.slide_pos, gap, max(self.hover, self.focus) * live, self.drag * live, self.disabled))
return Finish.over(out, bz_focus(length(p - c) - rad, px, self.focus * live))
}
// The dial is a pressure gauge: a bevelled brass bezel held by four screws,
// a cream face with its scale and a red end zone, a black needle on a
// brass hub, a crescent of light on the glass.
let bz_gauge = fn(p: vec2, c: vec2, rad: float, px: float, val: float, gap: float, hover: float, press: float, dis: float) -> vec4 {
let q = p - c
let r = length(q)
let d = r - rad
var out = vec4(0.0, 0.0, 0.0, 0.0)
let cast = Finish.drop(p, c, vec2(rad, rad), rad, vec2(rad * 0.08, rad * 0.14), rad * 0.12) * 0.6
out = Finish.over(out, vec4(bz_shadow_ink() * cast, cast))
let bw = max(rad * 0.15, 3.0)
let fr = rad - bw
let n = q / max(r, 0.001)
// the bezel
var bl = bz_frame_l(d, n, bw) + 0.05 * hover
var col = bz_brass(bl)
col = mix(col, bz_edge(), Finish.band(d, 0.0, px, px) * 0.8)
// the face
let rn = r / max(fr, 0.001)
var face = mix(vec3(0.925, 0.886, 0.784), vec3(0.769, 0.722, 0.620), smoothstep(0.3, 1.0, rn))
face = face + g3((Finish.fbm((p) * 0.35) - 0.5) * 0.05)
// the bezel's shadow over the face's top
let sh = exp(-max(fr - r, 0.0) / max(fr * 0.07, 0.5)) * (0.35 + 0.35 * max(-n.y, 0.0))
face = mix(face, vec3(0.20, 0.16, 0.11), sh)
// the scale
let start = gap * 0.5
let sweep = 2.0 * PI - gap
var ang = atan2(-q.x, q.y)
if ang < 0.0 {
ang = ang + 2.0 * PI
}
let ns = 20.0
let k = (ang - start) / sweep * ns
let ki = clamp(floor(k + 0.5), 0.0, ns)
let da = (k - ki) * sweep / ns * r
let major = 1.0 - step(0.1, abs(fract(ki / 5.0 + 0.5) - 0.5))
let in_sw = step(start - 0.02, ang) * step(ang, start + sweep + 0.02)
let t_in = mix(fr * 0.70, fr * 0.58, major)
let tick = (1.0 - smoothstep(mix(0.4, 0.7, major) * px, mix(0.4, 0.7, major) * px + 0.6 * px, abs(da))) * step(t_in, r) * step(r, fr * 0.80) * in_sw
let ink = vec3(0.157, 0.118, 0.078)
face = mix(face, ink, tick * 0.9)
// the scale's arc and the red zone over its last fifth
let arc = Finish.band(abs(r - fr * 0.80) - 0.35 * px, -px, 0.0, px) * in_sw
face = mix(face, ink, arc * 0.6)
let zone = step(start + sweep * 0.8, ang) * step(ang, start + sweep) * step(fr * 0.80, r) * step(r, fr * 0.87)
face = mix(face, vec3(0.690, 0.204, 0.141), zone * 0.9)
face = mix(face, mix(g3(0.55), face * 0.6, 0.4), dis)
let fm = Finish.cover(r - fr, px)
col = mix(col, face, fm)
col = mix(col, bz_edge(), Finish.band(r - fr, -px, 0.0, px) * 0.8)
let a = Finish.cover(d, px)
out = Finish.over(out, vec4(col * a, a))
// the four screws on the bezel
let sr = bw * 0.26
let sc = normalize(vec2(sign(q.x) + 0.0001, sign(q.y) + 0.0001)) * (rad - bw * 0.5)
let sv = bz_rivet(p, c + sc, sr, px, 0.0, -0.05)
let slot = Finish.cover(abs(dot(p - c - sc, vec2(0.707, -0.707))) - 0.35 * px, px) * Finish.cover(length(p - c - sc) - sr * 0.75, px)
out = Finish.over(out, sv)
out = Finish.over(out, vec4(bz_edge() * slot * 0.8, slot * 0.8))
// the needle, with its shadow on the face
let pang = start + sweep * val
let dir = vec2(-sin(pang), cos(pang))
let na = c - dir * fr * 0.18
let nb = c + dir * fr * 0.76
let tq = clamp(dot(p - na, nb - na) / max(dot(nb - na, nb - na), 0.0001), 0.0, 1.0)
let nw = mix(max(fr * 0.045, 0.9), 0.35, tq)
let nd = bz_seg(p, na, nb) - nw
let nsd = bz_seg(p - vec2(0.7, 1.1), na, nb) - nw
let nsh = (1.0 - smoothstep(-0.4, 0.9, nsd)) * 0.3 * fm
out = Finish.over(out, vec4(0.0, 0.0, 0.0, nsh))
let nc = mix(vec3(0.071, 0.055, 0.039), vec3(0.30, 0.28, 0.26), dis)
let na2 = Finish.cover(nd, px)
out = Finish.over(out, vec4(nc * na2, na2))
// the hub
let hub = bz_rivet(p, c, max(fr * 0.11, 1.6), px, 0.0, 0.05 * hover)
out = Finish.over(out, hub)
// the glass: a crescent of light over the upper left
let gq = (p - c + vec2(fr * 0.18, fr * 0.22)) / max(fr, 0.001)
let glass = (1.0 - smoothstep(0.0, 0.9, length(gq * vec2(1.0, 1.3)))) * 0.10 * fm
return Finish.over(out, vec4(glass, glass, glass, glass))
}
mod.widgets.RotaryFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = 6.0
let size = self.rect_size
let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let o = self.rect_pos
let live = 1.0 - self.disabled
let room = vec2(size.x, size.y - 20.0)
let dia = min(room.x, room.y)
let rr = dia * 0.5
let c = vec2(Finish.snap(o.x + size.x * 0.5, px) - o.x, Finish.snap(o.y + 20.0 + room.y * 0.5, px) - o.y)
let gap = self.gap * PI / 180.0
let rad = floor(rr * 0.92 / px) * px
var out = bz_gauge(p, c, rad, px, self.slide_pos, gap, max(self.hover, self.focus) * live, self.drag * live, self.disabled)
return Finish.over(out, bz_focus(length(p - c) - rad, px, self.focus * live))
}
// The dial's readout: a small dark window, amber digits.
mod.widgets.RotaryFlat.text_input.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 3.0 bottom: 3.0}
mod.widgets.RotaryFlat.text_input.min_height = 20.0
mod.widgets.RotaryFlat.text_input.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * self.rect_size
let o = self.rect_pos
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o
let hi = vec2(Finish.snap(o.x + self.rect_size.x, px), Finish.snap(o.y + self.rect_size.y, px)) - o - vec2(0.0, px)
let h = (hi - lo) * 0.5
let c = lo + h
let d = bz_rbox(p, c, h, 2.0)
var col = bz_well(p, c, h, 2.0, px, vec3(0.071, 0.055, 0.043), 2.4)
let t = clamp((p.y - lo.y) / max(2.0 * h.y, 1.0), 0.0, 1.0)
col = col + g3(0.025 * (1.0 - smoothstep(0.1, 0.35, t)) * step(2.0 * px, p.y - lo.y))
// a thin brass bezel
let g = bz_grad(p, c, h, 2.0)
let bcol = bz_brass(bz_frame_l(d, g, 2.0 * px) - 0.1)
col = mix(col, bcol, 1.0 - Finish.cover(d + 2.0 * px, px))
let a = Finish.cover(d, px)
var out = bz_lip(p, c, h, 2.0, px, 0.07)
return Finish.over(out, vec4(col * a, a))
}
mod.widgets.RotaryFlat.text_input.draw_text.color = #f0a848
mod.widgets.RotaryFlat.text_input.draw_text.color_hover = #f6b85c
mod.widgets.RotaryFlat.text_input.draw_text.color_focus = #ffc670
mod.widgets.RotaryFlat.text_input.draw_text.color_empty = #6a4a22
mod.widgets.RotaryFlat.text_input.draw_text.text_style = mod.theme.font_code{font_size: 9.0}
// The knob's caption is a scale label: 8 points, tan.
mod.widgets.RotaryKnob.draw_text.text_style = mod.theme.font_regular{font_size: 6.0}
mod.widgets.RotaryKnob.draw_text.color = #c2b498
// ---------------------------------------------------------------------------
// FADERS. A black slot with an iron wall, engraved ticks on one side and a
// row of brass studs on the other, polished below the value; the cap is a
// brass block with an engraved centre line that lights blue while held.
mod.widgets.SliderFader.cap_size = 10.0
mod.widgets.SliderFader.draw_bg.cap_cross = uniform(0.5)
mod.widgets.SliderFader.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = 6.0
let size = self.rect_size
let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let live = 1.0 - self.disabled
let vert = self.is_vertical
let a = mix(p.x, size.y - p.y, vert)
let b = mix(p.y, p.x, vert)
let along = mix(size.x, size.y, vert)
let across = mix(size.y, size.x, vert)
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 mid = floor(across * 0.5 * 1.5) / 1.5
let q = vec2(a, b)
// the slot
let sw = 3.0
let sh = vec2((along - self.inset_px * 2.0) * 0.5, sw * 0.5)
let sc = vec2(self.inset_px + sh.x, mid)
let sd = bz_rbox(q, sc, sh, sh.y)
let cd0 = bz_rbox(q, sc, vec2(sh.x - 0.5, 0.5), 0.5)
var col = mix(vec3(0.200, 0.188, 0.169), vec3(0.20, 0.19, 0.18), self.disabled)
col = mix(col, vec3(0.020, 0.016, 0.012), Finish.cover(cd0, px))
let sa = Finish.cover(sd, px)
var out = vec4(col * sa, sa)
// ticks on the far side: every tenth, a long one at each half
let f = (a - foot) / travel * 10.0
let fi = floor(f + 0.5)
let da = abs(a - (foot + fi / 10.0 * travel))
let major = 1.0 - step(0.3, abs(fract(fi / 5.0 + 0.5) - 0.5))
let tl = mix(2.5, 4.0, major)
let off = b - mid
let in_rng = step(-0.5, fi) * step(fi, 10.5)
let tk = (1.0 - smoothstep(px * 0.5 - 0.01, px * 0.5 + 0.01, da)) * step(sw * 0.5 + 3.0, off) * step(off, sw * 0.5 + 3.0 + tl) * in_rng
out = Finish.over(out, vec4(0.0, 0.0, 0.0, tk * 0.6))
// brass studs on the near side, polished below the value
let at = foot + self.slide_pos * travel
let dp = 5.0
let di = floor((a - foot) / dp + 0.5)
let dc = vec2(foot + di * dp, mid - sw * 0.5 - 4.0)
let in_d = step(-0.5, di) * step(foot + di * dp, foot + travel + 0.1)
let dot_lit = step(dc.x, at) * live
let stud = bz_rivet(q, dc, 1.1, px, 0.0, mix(-0.3, 0.12, dot_lit))
out = Finish.over(out, stud * in_d)
// the cap
let cap_w = min(max(across * self.cap_cross, 4.0), across)
let ch = vec2(self.cap_px * 0.5, cap_w * 0.5)
let cc = vec2(at, mid)
let cd = bz_rbox(q, cc, ch, 1.5)
let down_y = mix(vec2(0.0, 1.0), vec2(-1.0, 0.0), vert)
let ink = bz_shadow_ink()
let amb = Finish.drop(q, cc, ch, 1.5, down_y * 1.4 + vec2(0.3, 0.0), 1.4) * 0.5 * mix(1.0, 0.3, self.disabled)
out = Finish.over(out, vec4(ink * amb, amb))
// the cap in window terms: lit from above, shaded as a bar across
let wy = mix(q.y - cc.y, cc.x - q.x, vert)
let wh = mix(ch.y, ch.x, vert)
let tt = clamp(wy / max(2.0 * wh, 1.0) + 0.5, 0.0, 1.0)
var cl0 = mix(0.80, 0.40, tt) + 0.05 * max(self.hover, self.focus) * live - 0.05 * self.drag * live
var face = bz_brass(cl0)
face = mix(face, vec3(0.30, 0.28, 0.25), self.disabled * 0.75)
let cl = 1.0 - smoothstep(px * 0.5 - 0.01, px * 0.5 + 0.01, abs(a - at))
let line = mix(bz_edge(), bz_acc(), self.drag * live)
face = mix(face, line, cl * step(abs(b - mid), ch.y - 1.5))
face = mix(face, bz_edge(), Finish.band(cd, 0.0, px, px) * 0.8)
let ca = Finish.cover(cd, px)
out = Finish.over(out, vec4(face * ca, ca))
return Finish.over(out, bz_focus(cd, px, self.focus * live))
}
// ---------------------------------------------------------------------------
// TEXT FIELDS. A dark well in a riveted iron frame five device pixels wide:
// the frame is a bevelled bead lit from the top left, the rivets run along
// its top and bottom; the well's top wall throws its shadow in. Focus lights
// the well's mouth blue, inside the box.
let bz_field = fn(p: vec2, size: vec2, o: vec2, px: float, hover: float, focus: float, dis: float, r: float) -> vec4 {
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o
let hi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y, px)) - o
let h = (hi - lo) * 0.5
let c = lo + h
let d = bz_rbox(p, c, h, r)
let live = 1.0 - dis
let fw = 5.0 * px
// the floor: dark under the top wall, rising to the well's own shade
let ih = h - vec2(fw, fw)
let t = clamp((p.y - (c.y - ih.y)) / max(2.0 * ih.y, 1.0), 0.0, 1.0)
let u = clamp(t * 2.0, 0.0, 1.0)
let rise = 1.0 - (1.0 - u) * (1.0 - u)
var fill = mix(vec3(0.043, 0.039, 0.031), vec3(0.110, 0.106, 0.090), rise)
fill = fill + g3(0.03 * hover * live)
fill = mix(fill, vec3(0.150, 0.140, 0.125), dis)
fill = fill + g3(bz_grain((p + o) / px, 0.006))
var col = bz_well(p, c, ih, max(r - 1.0, 0.5), px, fill, 2.2)
let k = bz_acc()
let di = bz_rbox(p, c, ih, max(r - 1.0, 0.5))
col = mix(col, k, Finish.band(di, 0.0, px, px) * focus * 0.85)
col = mix(col, k * 0.5, Finish.band(di, px, 2.0 * px, px) * focus * 0.35)
// the iron frame
let g = bz_grad(p, c, h, r)
var il = bz_frame_l(d, g, fw) + 0.12 + 0.08 * hover * live - 0.1 * dis
var icol = bz_iron(il)
icol = icol + g3(bz_grain((p + o) / px, 0.012))
icol = mix(icol, bz_edge(), Finish.band(d, 0.0, px, px) * 0.85)
let fm = 1.0 - Finish.cover(d + fw, px)
col = mix(col, icol, fm)
col = mix(col, bz_edge(), Finish.band(d + fw, -px, 0.0, px) * 0.8)
let a = Finish.cover(d, px)
var out = vec4(col * a, a)
// rivets along the top and bottom runs, about 36 points apart
let x0 = lo.x + 5.0
let x1 = hi.x - 5.0
let nr = max(floor((x1 - x0) / 36.0 + 0.5), 1.0)
let stp = (x1 - x0) / nr
let kx = clamp(floor((p.x - x0) / stp + 0.5), 0.0, nr)
let ry = mix(lo.y + fw * 0.5, hi.y - fw * 0.5, step(c.y, p.y))
let rv = bz_rivet(p, vec2(x0 + kx * stp, ry), fw * 0.40, px, 2.0, 0.26 - 0.1 * dis)
return Finish.over(out, rv * a)
}
mod.widgets.TextInputFlat.padding = mod.turtle.Inset{left: 9.0 right: 9.0 top: 4.0 bottom: 4.0}
mod.widgets.TextInputFlat.draw_bg.pixel = fn() {
// The value field inside a slider or a well draws no box of its own.
if self.color.a < 0.01 {
return vec4(0.0, 0.0, 0.0, 0.0)
}
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return bz_field(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, self.hover, self.focus, self.disabled, 2.0)
}
mod.widgets.TextInputFlat.draw_text.color = #xe6ddc8
mod.widgets.TextInputFlat.draw_text.color_hover = #xf4ebd8
mod.widgets.TextInputFlat.draw_text.color_focus = #xf4ebd8
mod.widgets.TextInputFlat.draw_text.color_empty = #8a806d
mod.widgets.TextInputFlat.draw_text.color_disabled = #7a7060
mod.widgets.TextInputFlat.draw_cursor.color = #x7fb6e8
mod.widgets.NumberField.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return bz_field(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, self.hover, self.focus, self.disabled, 2.0)
}
mod.widgets.FieldWell.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return bz_field(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, self.hover, self.focus, self.disabled, 2.0)
}
// The stepper: a brass column set into the well's end, split by an
// engraved line, each half with a small stroked chevron.
let bz_spin = fn(p: vec2, size: vec2, o: vec2, px: float, hot: float, down: float, dis: float) -> vec4 {
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o + vec2(0.0, 5.0 * px)
let hi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y, px)) - o - vec2(5.0 * px, 5.0 * px)
let h = (hi - lo) * 0.5
let c = lo + h
let d = bz_rbox(p, c, h, 1.5)
let live = 1.0 - dis
let upper = step(p.y, c.y)
let hh = mix(max(0.0, -hot), max(0.0, hot), upper) * live
let dn = mix(max(0.0, -down), max(0.0, down), upper) * live
let hw = clamp((p.x - lo.x) / max(2.0 * h.x, 1.0), 0.0, 1.0)
let cyl = sin(hw * PI)
var l = 0.30 + 0.40 * cyl + 0.06 * hh - 0.18 * dn
let tt = clamp((p.y - lo.y) / max(2.0 * h.y, 1.0), 0.0, 1.0)
l = l * mix(1.1, 0.8, tt)
var face = bz_brass(l)
face = mix(face, vec3(0.30, 0.28, 0.25), dis * 0.75)
let mid = Finish.snap(o.y + c.y, px) - o.y
face = mix(face, bz_edge(), step(mid - px, p.y) * step(p.y, mid))
face = mix(face, bz_edge(), Finish.band(d, 0.0, px, px) * 0.8)
let ink = mix(vec3(0.149, 0.102, 0.039), vec3(0.20, 0.19, 0.17), dis)
let cu = vec2(c.x, (lo.y + mid) * 0.5)
let cl = vec2(c.x, (mid + hi.y) * 0.5)
let chd = min(bz_chevron(p, cu, 5.0, 2.5, 1.0), bz_chevron(p, cl, 5.0, 2.5, 0.0)) - 0.65
face = mix(face, ink, Finish.cover(chd, px))
let a = Finish.cover(d, px)
return vec4(face * a, a)
}
mod.widgets.NumberSpin.width = 16.0
mod.widgets.NumberSpin.draw_text.color = #00000000
mod.widgets.NumberSpin.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return bz_spin(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, self.hot, self.down, self.disabled)
}
mod.widgets.NumberField.spin_width = 16.0
mod.widgets.NumberField.spin.width = 16.0
mod.widgets.NumberField.spin.draw_text.color = #00000000
mod.widgets.NumberField.spin.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return bz_spin(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, self.hot, self.down, self.disabled)
}
// ---------------------------------------------------------------------------
// DROP-DOWNS. A smoked face in an iron frame with an engraved split 22
// points from its right end and a chevron behind it; open is pressed.
mod.widgets.DropDownFlat.padding = mod.turtle.Inset{left: 10.0 right: 30.0 top: 4.0 bottom: 4.0}
mod.widgets.DropDown.padding = mod.turtle.Inset{left: 10.0 right: 30.0 top: field_pad_v bottom: field_pad_v}
mod.widgets.DropDownFlat.clip_x = false
mod.widgets.DropDownFlat.clip_y = false
mod.widgets.DropDownFlat.draw_bg.vertex = fn() {
let g = grow_vertex(self.rect_pos, self.rect_size, 8.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.DropDownFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return bz_dropdown(self.pos, self.rect_size, self.rect_pos, px, self.hover, max(self.down * self.hover, self.active), self.focus, self.disabled, 8.0)
}
mod.widgets.DropDownFlat.draw_text.text_style = mod.theme.font_regular{font_size: 8.25}
// The combo box: the riveted well with a brass key at its right end.
mod.widgets.ComboBox.padding = mod.turtle.Inset{left: 9.0 right: 28.0 top: field_pad_v bottom: field_pad_v}
mod.widgets.ComboBox.input.min_height = 0.0
mod.widgets.ComboBox.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * self.rect_size
let size = self.rect_size
let o = self.rect_pos
let live = 1.0 - self.disabled
var out = bz_field(p, size, o, px, self.hover, self.focus, self.disabled, 2.0)
let kx = Finish.snap(o.x + size.x - 21.0, px) - o.x
let hi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y, px)) - o - vec2(5.0 * px, 5.0 * px)
let klo = vec2(kx, Finish.snap(o.y, px) - o.y + 5.0 * px)
let kh = (hi - klo) * 0.5
let kc = klo + kh
let kd = bz_rbox(p, kc, kh, 1.5)
let t = clamp((p.y - klo.y) / max(2.0 * kh.y, 1.0), 0.0, 1.0)
let held = self.down * self.hover * live
var face = bz_brass(mix(0.72, 0.42, t) + 0.05 * self.hover * live - 0.2 * held)
face = mix(face, vec3(0.30, 0.28, 0.25), self.disabled * 0.75)
face = mix(face, bz_edge(), Finish.band(kd, 0.0, px, px) * 0.8)
let ka = Finish.cover(kd, px)
out = Finish.over(out, vec4(face * ka, ka))
let cc = vec2(kc.x, Finish.snap(o.y + kc.y, px) - o.y)
let ch = Finish.cover(bz_chevron(p, cc, 6.0, 3.5, 0.0) - 0.65, px)
let ink = mix(vec3(0.149, 0.102, 0.039), vec3(0.20, 0.19, 0.17), self.disabled)
return Finish.over(out, vec4(ink * ch, ch))
}
// ---------------------------------------------------------------------------
// SEGMENTS AND TABS. The segmented control is a row of brass caps in one
// thin bronze frame; the chosen answer is pushed in, dark, its legend lit
// blue over a small lamp, with its neighbours' ends standing over it. The
// tab strip is a row of copper caps; the tab in use is pushed in the same
// way.
let bz_strip = fn(pos: vec2, size: vec2, o: vec2, px: float, m: float) -> vec4 {
let p = pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o
let hi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y, px)) - o
var face = bz_button(pos, size, o, px, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 2.0, 9.0, m)
// Down a column every answer is a cap of its own: where two meet, a
// dark row and a lit row. Answers are 28 points tall under this sheet
// and start two points in; a strip one answer tall has no join.
let sh = 28.0
let n = floor((size.y - 4.0) / sh + 0.5)
let col = step(abs(size.y - 4.0 - n * sh), 0.5) * step(1.5, n)
let fw = 3.0 * px
let inx = step(lo.x + fw, p.x) * (1.0 - step(hi.x - fw, p.x))
let kb = floor((p.y - 2.0) / sh + 0.5)
let yb = Finish.snap(o.y + 2.0 + kb * sh, px) - o.y
let join = inx * step(0.5, kb) * step(kb, n - 0.5) * col
let r_dk = step(yb - px, p.y) * (1.0 - step(yb + px, p.y)) * join
let r_lt = step(yb + px, p.y) * (1.0 - step(yb + 2.0 * px, p.y)) * join
face = Finish.over(face, vec4(bz_edge() * r_dk, r_dk))
let lt = bz_brass(0.9)
face = Finish.over(face, vec4(lt * r_lt * 0.7, r_lt * 0.7))
return face
}
// The chosen answer: a well the segment's width, from under the strip's
// top frame to above its bottom frame, with the neighbours' ends lit over
// it.
let bz_push_well = fn(p: vec2, size: vec2, o: vec2, px: float) -> vec4 {
let x0 = Finish.snap(o.x, px) - o.x
let x1 = Finish.snap(o.x + size.x, px) - o.x
let y0 = Finish.snap(o.y - 2.0, px) - o.y + 3.0 * px
let y1 = Finish.snap(o.y + size.y + 2.0, px) - o.y - 3.0 * px
var res = bz_pushed(p, vec2(x0, y0), vec2(x1, y1), o, px, 1.0)
let t = clamp((p.y - y0) / max(y1 - y0, 1.0), 0.0, 1.0)
let ey = step(y0, p.y) * (1.0 - step(y1, p.y))
let el = step(x0 - px, p.x) * (1.0 - step(x0, p.x))
let er = step(x1, p.x) * (1.0 - step(x1 + px, p.x))
let lit = bz_brass(mix(0.86, 0.5, t))
res = Finish.over(res, vec4(lit, 1.0) * (el + er) * ey)
return res
}
mod.widgets.SegmentedControlFlat.draw_bg.vertex = fn() {
let g = grow_vertex(self.rect_pos, self.rect_size, 6.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.SegmentedControlFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return bz_strip(self.pos, self.rect_size, self.rect_pos, px, 6.0)
}
mod.widgets.SegmentedControlFlat.draw_pill.vertex = fn() {
let g = grow_vertex(self.rect_pos, self.rect_size, 6.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.SegmentedControlFlat.draw_pill.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = 6.0
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
return bz_push_well(p, self.rect_size, self.rect_pos, px)
}
mod.widgets.SegmentedControlFlat.draw_text.color = #261a0a
mod.widgets.SegmentedControlFlat.draw_text_selected.color = #9ccbf2
mod.widgets.SegmentedControlFlat.palette.on_neutral_container = #9ccbf2
// A copper cap for a quiet tab, a pixel in from its neighbours.
let bz_copper_cap = fn(p: vec2, size: vec2, o: vec2, px: float, hover: float) -> vec4 {
let lo = vec2(Finish.snap(o.x + 1.0, px), Finish.snap(o.y + 1.0, px)) - o
let hi = vec2(Finish.snap(o.x + size.x - 1.0, px), Finish.snap(o.y + size.y - 1.0, px)) - o
let h = (hi - lo) * 0.5
let c = lo + h
let d = bz_rbox(p, c, h, 2.0)
let t = clamp((p.y - lo.y) / max(2.0 * h.y, 1.0), 0.0, 1.0)
let q = p + o
let br = (Finish.brushed(q, vec2(1.0, 0.0), px * 1.3) - 0.5) * 0.05
var face = bz_copper(mix(0.50, 0.30, t) + br + 0.05 * hover)
face = face + g3(bz_grain(q / px, 0.010))
let topw = 1.0 - smoothstep(lo.y + 2.5, lo.y + 3.5, p.y)
face = mix(face, bz_copper(0.8), Finish.band(d, px, 2.0 * px, px) * topw * 0.8)
face = mix(face, bz_edge(), Finish.band(d, 0.0, px, px) * 0.9)
var out = vec4(0.0, 0.0, 0.0, 0.0)
let con = Finish.drop(p, c, h, 2.0, vec2(0.0, px), px * 1.2) * 0.6
out = Finish.over(out, vec4(bz_shadow_ink() * con, con))
let a = Finish.cover(d, px)
return Finish.over(out, vec4(face * a, a))
}
mod.widgets.Tabs.height = 28.0
mod.widgets.Tabs.draw_bg.pixel = fn() {
return vec4(0.0, 0.0, 0.0, 0.0)
}
mod.widgets.Tabs.draw_tab.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return bz_copper_cap(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, 0.0)
}
mod.widgets.Tabs.draw_tab_hover.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return bz_copper_cap(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, 1.0)
}
mod.widgets.Tabs.draw_tab_active.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * self.rect_size
let o = self.rect_pos
let lo = vec2(Finish.snap(o.x + 1.0, px), Finish.snap(o.y + 1.0, px)) - o
let hi = vec2(Finish.snap(o.x + self.rect_size.x - 1.0, px), Finish.snap(o.y + self.rect_size.y - 1.0, px)) - o
var out = bz_pushed(p, lo, hi, o, px, 1.0)
// a thin copper rim round the pushed cap
let d = bz_rbox(p, (lo + hi) * 0.5, (hi - lo) * 0.5, 2.0)
let rim = Finish.band(d, 0.0, px, px)
out = Finish.over(out, vec4(bz_copper(0.35) * rim, rim))
return out
}
mod.widgets.Tabs.draw_sep.pixel = fn() {
return vec4(0.0, 0.0, 0.0, 0.0)
}
mod.widgets.Tabs.draw_mark.color = #xe3d8c2
mod.widgets.Tabs.draw_mark.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * self.rect_size
let o = self.rect_pos
let c = vec2(Finish.snap(o.x + self.rect_size.x * 0.5, px), Finish.snap(o.y + self.rect_size.y * 0.5, px)) - o
let a = mix(0.7853981634, 0.0, self.plus)
let r = mix(3.6, 4.5, self.plus)
let u = vec2(cos(a), sin(a)) * r
let v = vec2(-sin(a), cos(a)) * r
let dd = min(bz_seg(p, c - u, c + u), bz_seg(p, c - v, c + v)) - mix(0.5, 0.8, self.plus)
let k = Finish.cover(dd, px) * self.color.a
return vec4(self.color.rgb * k, k)
}
mod.widgets.Tabs.draw_text.text_style = mod.theme.font_regular{font_size: 8.25}
mod.widgets.Tabs.draw_text_active.text_style = mod.theme.font_regular{font_size: 8.25}
mod.widgets.Tabs.draw_text.color = #xf0e6d0
mod.widgets.Tabs.draw_text_active.color = #9ccbf2
// ---------------------------------------------------------------------------
// PROGRESS. A dark glass tube six points tall; the value is a glowing amber
// filament along its middle, white-hot at its core. The unknown value
// cannot move (a face runs no clock): still dim dashes.
mod.widgets.ProgressBarFlat.height = 6.0
mod.widgets.ProgressBarFlat.draw_bg.thickness = 6.0
mod.widgets.ProgressBarFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * self.rect_size
let o = self.rect_pos
let w = self.rect_size.x
let th = 9.0 * px
let top = Finish.snap(o.y + (self.rect_size.y - th) * 0.5, px) - o.y
let bx0 = Finish.snap(o.x, px) - o.x
let bx1 = Finish.snap(o.x + w, px) - o.x
let h = vec2((bx1 - bx0) * 0.5, th * 0.5)
let c = vec2(bx0, top) + h
let r = h.y
let d = bz_rbox(p, c, h, r)
let dis = step(4.5, self.intent)
var ink = bz_amber()
if self.intent > 0.5 {
ink = self.fill_color().rgb
}
ink = mix(ink, vec3(0.30, 0.28, 0.25), dis)
let t = clamp((p.y - top) / th, 0.0, 1.0)
var col = bz_well(p, c, h, r, px, vec3(0.106, 0.067, 0.047), 1.4)
// the glass: a light line along its top
col = col + g3(0.10 * (1.0 - smoothstep(0.12, 0.3, t)) * step(1.5 * px, p.y - top))
let a = Finish.cover(d, px) * self.track
var out = bz_lip(p, c, h, r, px, 0.08 * self.track)
out = Finish.over(out, vec4(col * a, a))
let span = bx1 - bx0 - 4.0 * px
let lx0 = Finish.snap(o.x + bx0 + 2.0 * px + span * self.start, px) - o.x + px * step(0.001, self.start)
let lx1 = Finish.snap(o.x + bx0 + 2.0 * px + span * self.end, px) - o.x
let xv = Finish.snap(o.x + bx0 + 2.0 * px + span * clamp(self.value, self.start, self.end), px) - o.x
var on = step(lx0, p.x) * step(p.x, xv) * step(0.0, self.value)
if self.value < 0.0 {
let sx = floor((p.x - lx0) / px)
on = step(0.0, sx) * step(fract(sx / 16.0), 0.49) * 0.4 * step(p.x, lx1)
}
// the filament: a hot core row, a coloured body, a soft glow in the tube
let cy = c.y
let dy = abs(p.y - cy) / px
let core = 1.0 - smoothstep(0.4, 0.9, dy)
let body = 1.0 - smoothstep(1.2, 1.8, dy)
let glow = exp(-dy * 0.6) * 0.45
let fil = mix(ink, mix(ink, vec3(1.0, 0.96, 0.85), 0.65), core)
let fa = clamp(max(body, glow), 0.0, 1.0) * on * Finish.cover(d + px, px)
out = Finish.over(out, vec4(fil * fa, fa))
return out * self.opacity
}
// ---------------------------------------------------------------------------
// LEVEL METERS. A row of glass tubes: each cell a dark amber glass with its
// filament, glowing amber up to -2 dB and red-orange above; unlit cells
// keep a trace of their glass. The over lamp is a red bead.
mod.widgets.LevelMeter.height = 8.0
mod.widgets.LevelMeterColumn.width = 8.0
mod.widgets.LevelMeter.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * self.rect_size
let vert = self.vertical
let along = mix(self.rect_size.x, self.rect_size.y, vert)
let across = mix(self.rect_size.y, self.rect_size.x, vert)
let a = mix(p.x, self.rect_size.y - p.y, vert)
let b = mix(p.y, p.x, vert)
let q = vec2(a, b)
let lamp_room = self.lamp * (across + 3.0)
let bar = max(4.0, along - lamp_room)
let h = vec2(bar * 0.5, across * 0.5 - px * 0.5)
let c = h
let r = 1.5
let d = bz_rbox(q, c, h, r)
let pad = 1.5 * px
let span = max(1.0, bar - pad * 2.0)
let share = clamp((a - pad) / span, 0.0, 1.0)
// cells three points long with a point between, rounded like tubes
let pitch = 4.0
let ci = floor((a - pad) / pitch)
let cc = vec2(pad + ci * pitch + 1.5, c.y)
let cd = bz_rbox(q, cc, vec2(1.5, h.y - pad), 1.2)
let in_rng = step(pad, a) * step(a, pad + span)
let cell = Finish.cover(cd, px) * in_rng
let lit = step(pad + ci * pitch + 1.5, pad + span * self.level) * cell
let zone = mix(bz_amber(), vec3(1.0, 0.380, 0.180), step(0.88, share))
var col = bz_well(q, c, h, r, px, vec3(0.063, 0.043, 0.031), 1.2)
// the unlit glass and its filament
let fil = 1.0 - smoothstep(0.3 * px, 0.8 * px, abs(q.y - c.y))
col = mix(col, vec3(0.165, 0.086, 0.047), cell * 0.8)
col = mix(col, vec3(0.30, 0.16, 0.07), cell * fil * 0.6)
// lit: amber body, a hot filament
let hot = mix(zone, vec3(1.0, 0.95, 0.80), fil * 0.7)
col = mix(col, mix(zone * 0.85, hot, fil), lit)
if self.hold > 0.002 {
let at = pad + span * self.hold
let hm = step(abs(a - at), 1.5) * cell
col = mix(col, zone, hm)
}
let ca = Finish.cover(d, px)
var out = Finish.over(bz_lip(q, c, h, r, px, 0.07), vec4(col * ca, ca))
if self.lamp > 0.5 {
let la = bar + 3.0 + across * 0.5
let lb = across * 0.5
let lc = vec2(mix(la, lb, vert), mix(lb, self.rect_size.y - la, vert))
out = Finish.over(out, bz_bead(p, lc, min(across * 0.5 - px, 3.0), px, vec3(0.918, 0.300, 0.220), step(0.5, self.over)))
}
return out * self.opacity
}
// ---------------------------------------------------------------------------
// SCROLL BARS. An 8 point iron slot with a 6 point brass thumb, shaded as a
// rod; a held thumb shows a blue centre line.
mod.widgets.ScrollBar.bar_size = 8.0
mod.widgets.ScrollBar.bar_side_margin = 0.0
mod.widgets.ScrollBar.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * self.rect_size
let vert = self.is_vertical
let a = mix(p.x, p.y, vert)
let b = mix(p.y, p.x, vert)
let along = mix(self.rect_size.x, self.rect_size.y, vert)
let across = mix(self.rect_size.y, self.rect_size.x, vert)
let q = vec2(a, b)
let sh = vec2(along * 0.5, across * 0.5)
let sd = bz_rbox(q, sh, sh, 3.0)
let wall = 1.0 - smoothstep(0.0, 2.0, b)
var col = mix(vec3(0.090, 0.078, 0.063), vec3(0.031, 0.024, 0.016), wall * 0.8)
let sa = Finish.cover(sd, px)
var out = vec4(col * sa, sa)
let t0 = along * self.norm_scroll
let tl = along * self.norm_handle
let th = vec2(tl * 0.5 - 1.0, 3.0)
let tc = vec2(t0 + tl * 0.5, across * 0.5)
let td = bz_rbox(q, tc, th, 3.0)
let tt = clamp((b - (tc.y - th.y)) / 6.0, 0.0, 1.0)
let cyl = sin(tt * PI)
var face = bz_brass(0.30 + 0.45 * cyl * (1.0 - 0.35 * tt) + 0.06 * self.hover)
face = mix(face, bz_edge(), Finish.band(td, 0.0, px, px) * 0.7)
let cl = (1.0 - smoothstep(px * 0.5 - 0.01, px * 0.5 + 0.01, abs(b - tc.y))) * step(abs(a - tc.x), th.x - 3.0)
face = mix(face, bz_acc(), cl * self.drag * 0.8)
let ta = Finish.cover(td, px)
out = Finish.over(out, vec4(face * ta, ta))
return out * self.opacity
}
// ---------------------------------------------------------------------------
// VIEWS AND PANELS. A raised plate is dark iron, riveted: a soft shadow
// outside it, a black edge, a lit top line and a dark foot, a mottled
// weathered surface with faint scratches, a domed rivet in each corner and
// more along long edges. The inset panel is an iron frame round a sunk
// floor, the frame riveted at its corners.
let bz_plate = fn(p: vec2, size: vec2, o: vec2, px: float, r: float, sunken: float, alpha: float) -> vec4 {
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o
let hi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y, px)) - o
let h = (hi - lo) * 0.5
let c = lo + h
let d = bz_rbox(p, c, h, r)
let ink = bz_shadow_ink()
var out = vec4(0.0, 0.0, 0.0, 0.0)
let soft = Finish.drop(p, c, h, r, vec2(0.0, 2.5), 2.6) * 0.5
out = Finish.over(out, vec4(ink * soft, soft))
let con = Finish.drop(p, c, h, r, vec2(0.0, px), px) * 0.6
out = Finish.over(out, vec4(ink * con, con))
let t = clamp((p.y - lo.y) / max(2.0 * h.y, 1.0), 0.0, 1.0)
let q = p + o
// weathered iron: a slow mottle, fine grain, faint diagonal scratches
let mot = Finish.fbm(q * 0.045) - 0.5
let mot2 = Finish.fbm(q * 0.16 + vec2(17.0, 3.0)) - 0.5
var l = mix(0.64, 0.56, t) + mot * 0.20 + mot2 * 0.08
l = l - 0.10 * smoothstep(0.12, 0.3, mot2)
let scr = Finish.brushed(q, normalize(vec2(1.0, 0.18)), px * 1.1) - 0.5
l = l + scr * 0.03
var face = bz_iron(l) + g3(bz_grain(q / px, 0.018))
// the sunken floor: darker, the frame's shadow falling in
let fw = 5.0
let ih = h - vec2(fw, fw)
let di = bz_rbox(p, c, ih, max(r - 1.0, 0.5))
var floor_c = bz_iron(mix(0.40, 0.34, t) + mot * 0.08) + g3(bz_grain(q / px, 0.014))
let insh = Finish.inset(p, c, ih, max(r - 1.0, 0.5), vec2(0.0, 2.0), 2.6)
floor_c = mix(floor_c, vec3(0.020, 0.016, 0.012), clamp(insh, 0.0, 1.0) * 0.8)
let infl = Finish.cover(di, px) * sunken
face = mix(face, floor_c, infl)
face = mix(face, bz_edge(), Finish.band(di, -px, 0.0, px) * 0.7 * sunken)
face = mix(face, bz_iron(0.72), Finish.band(di, -2.0 * px, -px, px) * smoothstep(c.y + ih.y - 2.0, c.y + ih.y, p.y) * 0.6 * sunken)
// the plate's edge: a lit top line, a dark foot, a black edge
let topw = 1.0 - smoothstep(lo.y + r + 0.5, lo.y + r + 1.5, p.y)
let low = smoothstep(hi.y - r - 1.5, hi.y - r - 0.5, p.y)
face = mix(face, bz_iron(0.86), Finish.band(d, px, 3.0 * px, px) * topw)
face = mix(face, bz_iron(0.18), Finish.band(d, px, 2.0 * px, px) * low)
face = mix(face, bz_edge(), Finish.band(d, 0.0, px, px))
let a = Finish.cover(d, px) * alpha
out = Finish.over(out, vec4(face * a, a))
// rivets: one in each corner, more along each edge about 80 points apart
let ins = mix(7.0, fw * 0.5, sunken)
let x0 = lo.x + ins
let x1 = hi.x - ins
let y0 = lo.y + ins
let y1 = hi.y - ins
let nx = max(floor((x1 - x0) / 80.0 + 0.5), 1.0)
let ny = max(floor((y1 - y0) / 80.0 + 0.5), 1.0)
let sx = (x1 - x0) / nx
let sy = (y1 - y0) / ny
// the nearest rivet on the edge ring: snap to the nearer edge
let kx = clamp(floor((p.x - x0) / sx + 0.5), 0.0, nx)
let ky = clamp(floor((p.y - y0) / sy + 0.5), 0.0, ny)
let ex = min(p.x - x0, x1 - p.x)
let ey = min(p.y - y0, y1 - p.y)
let on_x = step(ey, ex)
let rc = mix(vec2(mix(x0, x1, step(c.x, p.x)), y0 + ky * sy), vec2(x0 + kx * sx, mix(y0, y1, step(c.y, p.y))), on_x)
let rv = bz_rivet(p, rc, mix(2.4, 1.6, sunken), px, 2.0, 0.18)
return Finish.over(out, rv * alpha)
}
mod.widgets.PanelView.clip_x = false
mod.widgets.PanelView.clip_y = false
mod.widgets.PanelView.draw_bg.vertex = fn() {
let g = grow_vertex(self.rect_pos, self.rect_size, 8.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.PanelView.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = 8.0
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
return bz_plate(p, self.rect_size, self.rect_pos, px, 3.0, self.sunken, 1.0)
}
// The card is the riveted plate too; its palette and shadow numbers from
// Rust are not read.
mod.widgets.Card.radius = 3.0
mod.widgets.Card.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 8.0 bottom: 8.0}
mod.widgets.Card.draw_bg.vertex = fn() {
let g = grow_vertex(self.rect_pos, self.rect_size, 8.0)
return self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.Card.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = 8.0
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let out = bz_plate(p, self.rect_size, self.rect_pos, px, 3.0, 0.0, 1.0)
let d = bz_rbox(p, self.rect_size * 0.5, self.rect_size * 0.5, 3.0)
let l = Finish.cover(d, px) * (0.03 * self.hover - 0.04 * self.down)
return Finish.over(out, vec4(max(l, 0.0), max(l, 0.0), max(l, 0.0), abs(l)))
}
// The plain rounded view keeps the colours its caller gives it; the edge is
// a snapped hairline at the true radius.
mod.widgets.RoundedView.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * self.rect_size
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))
}
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 h = (hi - lo) * 0.5
let c = lo + h
let d = bz_rbox(p, c, h, max(self.border_radius, 1.0))
var col = fill
if self.border_size > 0.0 {
col = mix(col, self.border_color, Finish.band(d, 0.0, max(self.border_size, px), px) * self.border_color.a)
}
let a = Finish.cover(d, px) * col.a
return vec4(col.rgb * a, a)
}
// ---------------------------------------------------------------------------
// LISTS. Parchment: every row is a strip of the same aged sheet (its mottle
// is laid in window space, so rows join), darkened toward its left and
// right edges, ruled with a faint brown line; ink is a dark sepia. The
// chosen row is a darker band with a brass bar at its head.
let bz_parchment = fn(q: vec2, px: float) -> vec3 {
let m1 = Finish.fbm(q * 0.035) - 0.5
let m2 = Finish.fbm(q * 0.11 + vec2(41.0, 7.0)) - 0.5
let fib = Finish.brushed(q, normalize(vec2(1.0, 0.06)), px * 2.0) - 0.5
var col = vec3(0.780, 0.659, 0.443)
col = col * (1.0 + m1 * 0.22 + m2 * 0.10 + fib * 0.03)
// a stain here and there
let st = smoothstep(0.18, 0.30, m1)
col = mix(col, vec3(0.62, 0.48, 0.28), st * 0.35)
return col + g3(bz_grain(q / px, 0.014))
}
mod.widgets.ListItemOne.height = 22.0
mod.widgets.ListItem.padding = mod.turtle.Inset{left: 12.0 right: 8.0 top: 0.0 bottom: 0.0}
mod.widgets.ListItem.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * self.rect_size
let o = self.rect_pos
let live = 1.0 - self.disabled
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o
let hi = vec2(Finish.snap(o.x + self.rect_size.x, px), Finish.snap(o.y + self.rect_size.y, px)) - o
// the sheet stops 10 points short of the row's right end, clear of the
// 8 point scroll bar
let x1 = hi.x - 10.0
var col = bz_parchment(p + o, px)
// burnt toward the sheet's left and right edges
let ex = min(p.x - lo.x, x1 - p.x)
col = col * mix(0.62, 1.0, smoothstep(0.0, 14.0, ex))
col = mix(col, vec3(0.30, 0.19, 0.08), (1.0 - smoothstep(0.0, 2.5, ex)) * 0.6)
// hover darkens the row a little, press more
col = col * (1.0 - 0.06 * self.hover * live - 0.10 * self.down * live)
// the chosen row: a darker band with a brass bar at its head
let sel = self.selected
col = mix(col, col * vec3(0.78, 0.74, 0.68), sel)
let bar = step(lo.x + 3.0, p.x) * step(p.x, lo.x + 5.0) * step(lo.y + 3.0, p.y) * step(p.y, hi.y - 3.0)
let bt = clamp((p.x - lo.x - 3.0) / 2.0, 0.0, 1.0)
col = mix(col, bz_brass(mix(0.85, 0.40, bt)), bar * sel)
// the rule under the row
let yb = hi.y
let rule = step(yb - px, p.y) * step(p.y, yb)
col = mix(col, vec3(0.42, 0.30, 0.16), rule * 0.35)
let a = step(lo.x, p.x) * step(p.x, x1)
return vec4(col * a, a)
}
mod.widgets.ListItem.draw_text.color = #2b1c0c
mod.widgets.ListItem.draw_text_2.color = #4a3620
// The open list of a drop-down: an iron sheet; the row under the pointer
// becomes a well with a blue bar at its head, the chosen row carries a
// small blue jewel.
mod.widgets.PopupMenuFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * self.rect_size
let o = self.rect_pos
let c = self.rect_size * 0.5
let d = bz_rbox(p, c, c, 3.0)
let t = clamp(self.pos.y, 0.0, 1.0)
let q = p + o
var face = bz_iron(mix(0.56, 0.48, t) + (Finish.fbm(q * 0.05) - 0.5) * 0.08) + g3(bz_grain(q / px, 0.014))
face = mix(face, bz_iron(0.8), Finish.band(d, px, 2.0 * px, px) * (1.0 - smoothstep(3.0, 4.0, p.y)))
face = mix(face, bz_edge(), Finish.band(d, 0.0, px, px))
let a = Finish.cover(d, px)
return vec4(face * a, a)
}
mod.widgets.PopupMenuItem.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * self.rect_size
let c = self.rect_size * 0.5
let h = c - vec2(1.0, 0.5)
let d = bz_rbox(p, c, h, 2.0)
let live = 1.0 - self.disabled
let hv = self.hover * live
let col = bz_well(p, c, h, 2.0, px, vec3(0.071, 0.063, 0.055), 2.0)
let a = Finish.cover(d, px) * hv
var out = vec4(col * a, a)
let bar = step(1.0 + px, p.x) * step(p.x, 1.0 + 2.0 * px) * step(4.0, p.y) * step(p.y, self.rect_size.y - 4.0) * hv
let k = bz_acc()
out = Finish.over(out, vec4(k * bar, bar))
let jw = bz_bead(p, vec2(6.0, c.y), 1.6, px, k, 1.0)
return Finish.over(out, jw * self.active)
}
// The dock's tabs: the bar is a darker strip with an engraved foot; the
// tab in use is an iron plate rounded at the top, lit along its top edge.
mod.widgets.Tab.padding = mod.turtle.Inset{left: 10.0 right: 10.0 top: 3.0 bottom: 3.0}
mod.widgets.Tab.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * self.rect_size
let o = self.rect_pos
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y + 2.0, px)) - o
let hi = vec2(Finish.snap(o.x + self.rect_size.x, px), Finish.snap(o.y + self.rect_size.y, px)) - o + vec2(0.0, 4.0)
let h = (hi - lo) * 0.5
let c = lo + h
let d = bz_rbox(p, c, h, 3.0)
let q = p + o
var face = bz_iron(0.56 + (Finish.fbm(q * 0.05) - 0.5) * 0.08) + g3(bz_grain(q / px, 0.012))
face = mix(face, bz_iron(0.82), Finish.band(d, px, 2.0 * px, px) * step(p.y, lo.y + 3.0))
face = mix(face, bz_edge(), Finish.band(d, 0.0, px, px))
let a = Finish.cover(d, px) * self.active
let lift = Finish.cover(d, px) * 0.04 * self.hover * (1.0 - self.active)
return Finish.over(vec4(lift, lift, lift, lift), vec4(face * a, a))
}
mod.widgets.TabBar.draw_fill.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * self.rect_size
let yb = Finish.snap(self.rect_pos.y + self.rect_size.y, px) - self.rect_pos.y
var out = vec4(0.0, 0.0, 0.0, 0.2)
let dk = step(yb - 2.0 * px, p.y) * step(p.y, yb - px) * 0.7
let lt = step(yb - px, p.y) * step(p.y, yb) * 0.06
out = Finish.over(out, vec4(0.0, 0.0, 0.0, dk))
return Finish.over(out, vec4(lt, lt, lt, lt))
}
// ---------------------------------------------------------------------------
// CHIPS AND BADGES. A chip is the 20 point framed face; a chip filled with
// a colour is lit in its own colour. A badge is a small dark window with an
// amber count.
mod.widgets.ChipFlat.size = mod.widgets.ChipSize.Small
mod.widgets.Chip.size = mod.widgets.ChipSize.Small
mod.widgets.ChipFlat.draw_bg.vertex = fn() {
let g = grow_vertex(self.rect_pos, self.rect_size, 8.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.ChipFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = 8.0
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let o = self.rect_pos
let ch = min(20.0, self.rect_size.y)
let dy = (self.rect_size.y - ch) * 0.5
let sz = vec2(self.rect_size.x, ch)
let pp = (p - vec2(0.0, dy) + vec2(m, m)) / (sz + vec2(2.0 * m, 2.0 * m))
let chosen = step(0.08, max(max(self.color.r, self.color.g), self.color.b) - min(min(self.color.r, self.color.g), self.color.b)) * step(0.5, self.color.a)
let lw = vec3(0.2126, 0.7152, 0.0722)
let pale = step(dot(self.ink.rgb, lw), 0.35)
let held = step(0.45, dot(self.color.rgb, lw)) * step(0.5, self.color.a) * (1.0 - pale) * (1.0 - chosen)
if max(chosen, pale) < 0.5 {
return bz_button(pp, sz, o + vec2(0.0, dy), px, self.hover, max(self.down, held), self.keyed, 0.0, 0.0, 0.0, 2.0, 1.0, m)
}
let q = p - vec2(0.0, dy)
let oo = o + vec2(0.0, dy)
let lo = vec2(Finish.snap(oo.x, px), Finish.snap(oo.y, px)) - oo
let hi = vec2(Finish.snap(oo.x + sz.x, px), Finish.snap(oo.y + sz.y, px)) - oo
let h = (hi - lo) * 0.5
let c = lo + h
let d = bz_rbox(q, c, h, 2.0)
let t = clamp((q.y - lo.y) / max(2.0 * h.y, 1.0), 0.0, 1.0)
let yi = q.y - lo.y - 2.0 * px
let k = mix(self.color.rgb, bz_brass(0.72), pale * (1.0 - chosen))
var face = mix(k, mix(k, vec3(1.0, 1.0, 1.0), 0.3), smoothstep(0.45, 1.0, t))
face = mix(k * 0.35, face, smoothstep(0.0, 3.0, yi))
face = face * (1.0 + 0.08 * self.hover - 0.12 * self.down)
let g = bz_grad(q, c, h, 2.0)
let fcol = bz_brass(bz_frame_l(d, g, 2.0 * px) - 0.08)
face = mix(face, fcol, 1.0 - Finish.cover(d + 2.0 * px, px))
let ha = 0.25 * chosen * exp(-max(d - px, 0.0) / (0.6 * px)) * step(0.0, d)
var out = vec4(k * ha, ha)
let a = Finish.cover(d, px)
out = Finish.over(out, vec4(face * a, a))
return Finish.over(out, bz_focus(d, px, self.keyed))
}
mod.widgets.BadgePalette.neutral = #17130f
mod.widgets.BadgePalette.on_neutral = #f0a848
mod.widgets.BadgeFlat.draw_text.text_style = mod.theme.font_bold{font_size: 6.75}
mod.widgets.Badge.draw_text.text_style = mod.theme.font_bold{font_size: 6.75}
mod.widgets.BadgeFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * self.rect_size
let o = self.rect_pos
let bh = min(16.0, self.rect_size.y)
let y0 = Finish.snap(o.y + (self.rect_size.y - bh) * 0.5, px) - o.y
let lo = vec2(Finish.snap(o.x, px) - o.x, y0)
let hi = vec2(Finish.snap(o.x + self.rect_size.x, px) - o.x, Finish.snap(o.y + y0 + bh, px) - o.y - px)
let h = (hi - lo) * 0.5
let c = lo + h
let r = mix(2.0, h.y, step(self.rect_size.x, self.rect_size.y + 0.5) * step(self.rect_size.y, 12.5))
let d = bz_rbox(p, c, h, r)
var col = bz_well(p, c, h, r, px, vec3(0.071, 0.055, 0.043), 2.0)
let g = bz_grad(p, c, h, r)
let bcol = bz_brass(bz_frame_l(d, g, 2.0 * px) - 0.1)
col = mix(col, bcol, 1.0 - Finish.cover(d + 2.0 * px, px))
let a = Finish.cover(d, px) * self.color.a
var out = bz_lip(p, c, h, r, px, 0.07)
return Finish.over(out, vec4(col * a, a))
}
// Rules between groups: an engraved line, one dark row over one light row.
mod.widgets.Hr.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * self.rect_size
let y = Finish.snap(self.rect_pos.y + self.rect_size.y * 0.5, px) - self.rect_pos.y
let dk = step(y - px, p.y) * (1.0 - step(y, p.y)) * 0.5
let lt = step(y, p.y) * (1.0 - step(y + px, p.y)) * 0.08
return Finish.over(vec4(0.0, 0.0, 0.0, dk), vec4(lt, lt * 0.95, lt * 0.85, lt))
}
mod.widgets.Vr.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * self.rect_size
let x = Finish.snap(self.rect_pos.x + self.rect_size.x * 0.5, px) - self.rect_pos.x
let dk = step(x - px, p.x) * (1.0 - step(x, p.x)) * 0.5
let lt = step(x, p.x) * (1.0 - step(x + px, p.x)) * 0.08
return Finish.over(vec4(0.0, 0.0, 0.0, dk), vec4(lt, lt * 0.95, lt * 0.85, lt))
}
// ---------------------------------------------------------------------------
// THE GROUND under everything: brushed dark brown-grey metal, lighter in
// the middle and darkening toward the corners, with a slow weathered
// mottle, fine horizontal streaks and grain. On it the machine room: an
// engraved frame with rivets, engraved scrollwork in the corners and along
// the foot, copper pipes along the top, down the right edge and along the
// foot, and brass gear wheels half hidden at the corners and edges. Every
// piece is placed from the window's edges; the middle stays quiet.
// A brass gear wheel lying on the ground, premultiplied with its cast
// shadow: `teeth` teeth, `spokes` spokes with open windows between them,
// a bossed hub. `rot` turns it (radians).
let bzg_gear = fn(p: vec2, c: vec2, rad: float, teeth: float, spokes: float, rot: float, px: float) -> vec4 {
let q = p - c
let r = length(q)
let cast = Finish.drop(p, c, vec2(rad, rad), rad, vec2(2.5, 5.0), 4.0) * 0.62
var out = vec4(bz_shadow_ink() * cast, cast)
let ang = atan2(q.y, q.x) + rot
let toothh = max(rad * 0.08, 2.2)
let root = rad - toothh
let f = fract(ang / (2.0 * PI) * teeth)
let tw = smoothstep(0.06, 0.20, f) * (1.0 - smoothstep(0.56, 0.70, f))
let d = r - mix(root, rad, tw)
let n = q / max(r, 0.001)
let lt = vec2(-0.38, -0.92)
let facing = dot(n, lt)
let rimw = max(rad * 0.11, 2.4)
let inner_r = root - rimw
let hub_r = max(rad * 0.20, 4.0)
let boss_r = hub_r * 1.45
// the rim: a rounded bead lit from the top left
let rt = clamp((r - inner_r) / max(root - inner_r, 0.001), 0.0, 1.0)
var l = 0.31 + 0.17 * facing * (2.0 * rt - 1.0) + 0.05 * facing
let s0 = (rt - 0.55) / 0.2
l = l + 0.12 * exp(-s0 * s0) * max(facing, 0.0)
// the teeth: their flanks catch the light, their tops are flat
let tooth = smoothstep(root - 0.3, root + 0.3, r)
l = mix(l, 0.30 + 0.12 * facing, tooth)
// the spokes: round bars from the boss to the rim
let ns = max(spokes, 1.0)
let da = (fract(ang / (2.0 * PI) * ns + 0.5) - 0.5) * 2.0 * PI / ns
let across = r * sin(da)
let sw = max(rad * 0.05, 1.3)
let sd = abs(across) - sw
let tn = vec2(-n.y, n.x) * sign(across)
let sl = 0.34 + 0.20 * dot(tn, lt) * clamp(abs(across) / sw, 0.0, 1.0) + 0.05
let inweb = 1.0 - smoothstep(inner_r - 0.3, inner_r + 0.3, r)
l = mix(l, sl, inweb)
// the boss and the axle: a turned dome with a dark bore
let bq = q / max(boss_r, 0.001)
let dome = 0.36 - 0.14 * bq.y - 0.06 * bq.x + 0.24 * exp(-dot(bq + vec2(0.35, 0.45), bq + vec2(0.35, 0.45)) * 4.0)
let inboss = 1.0 - smoothstep(boss_r - 0.3, boss_r + 0.3, r)
l = mix(l, dome, inboss)
// wear: a slow mottle over the metal
l = l + (Finish.noise(p * 0.21 + c) - 0.5) * 0.10
var col = bz_brass(l)
// edges: the outline, the rim's inner edge, the boss and the bore
col = mix(col, bz_edge(), Finish.band(d, 0.0, px * 1.5, px) * 0.75)
col = mix(col, bz_edge(), Finish.band(r - inner_r, -px, 0.0, px) * 0.55)
col = mix(col, bz_edge(), Finish.band(r - boss_r, 0.0, px, px) * 0.6)
col = mix(col, bz_edge(), Finish.band(r - hub_r * 0.95, -px, 0.0, px) * 0.45)
let bolt = 1.0 - smoothstep(hub_r * 0.42 - 0.4, hub_r * 0.42 + 0.4, r)
let bl = 0.40 - 0.35 * (q.y + 0.4 * q.x) / max(hub_r * 0.42, 0.001)
col = mix(col, bz_iron(bl + 0.1), bolt)
col = mix(col, bz_edge(), Finish.band(r - hub_r * 0.42, 0.0, px, px) * 0.8)
// the windows between the spokes are open: the ground shows through
let win = inweb * (1.0 - inboss) * smoothstep(-0.4, 0.4, sd)
col = mix(col, bz_shadow_ink(), win)
let a = Finish.cover(d, px) * (1.0 - win * 0.45)
// a shade in the windows' edge so the web reads as thickness
let lip = Finish.band(sd, -px * 1.5, 0.0, px) * inweb * (1.0 - inboss)
col = mix(col, bz_edge(), lip * 0.5)
return Finish.over(out, vec4(col * a, a))
}
// The light on a round pipe at `q`, the vector from its axis, radius
// `rad`: a cylinder lit from the top left, a hot line off the middle.
let bzg_tube = fn(q: vec2, rad: float) -> float {
let u = q / max(rad, 0.001)
let nz = sqrt(max(1.0 - min(dot(u, u), 1.0), 0.0))
let nrm = vec3(u.x, u.y, nz)
let dif = max(dot(nrm, vec3(-0.26, -0.62, 0.74)), 0.0)
return 0.08 + 0.46 * dif + 0.46 * pow(dif, 28.0)
}
// The nearest-axis vectors of pipe parts: a straight run and a quarter bend
// (centre `ec`, radius `R`, the quarter where x - ec.x has the sign of sx
// and y - ec.y the sign of sy).
let bzg_run = fn(p: vec2, a: vec2, b: vec2) -> vec2 {
let pa = p - a
let ba = b - a
let k = clamp(dot(pa, ba) / max(dot(ba, ba), 0.0001), 0.0, 1.0)
return pa - ba * k
}
let bzg_bend = fn(p: vec2, ec: vec2, R: float, sx: float, sy: float) -> vec2 {
let v = p - ec
let inq = step(0.0, v.x * sx) * step(0.0, v.y * sy)
let lv = max(length(v), 0.001)
return mix(vec2(1000.0, 1000.0), v - v / lv * R, inq)
}
let bzg_near = fn(q1: vec2, q2: vec2) -> vec2 {
return mix(q1, q2, step(length(q2), length(q1)))
}
// A copper pipe from its nearest-axis vector, with its shadow on the plate,
// premultiplied.
let bzg_pipe = fn(q: vec2, rad: float, px: float) -> vec4 {
let dq = length(q) - rad
let sq = length(q - vec2(2.0, 4.5)) - rad
let cast = (1.0 - smoothstep(-3.0, 5.0, sq)) * 0.6
var out = vec4(bz_shadow_ink() * cast, cast)
var col = bz_copper(bzg_tube(q, rad))
col = mix(col, bz_edge(), Finish.band(dq, 0.0, px, px) * 0.6)
let a = Finish.cover(dq, px)
return Finish.over(out, vec4(col * a, a))
}
// A brass collar round a pipe: `along` is the distance along the pipe from
// the collar's middle, `q` the vector from the axis.
let bzg_collar = fn(q: vec2, along: float, rad: float, px: float) -> vec4 {
let cr = rad + 2.2
let dq = max(length(q) - cr, abs(along) - 3.4)
let sq = max(length(q - vec2(2.0, 4.5)) - cr, abs(along) - 3.4)
let cast = (1.0 - smoothstep(-2.0, 4.0, sq)) * 0.35
var out = vec4(bz_shadow_ink() * cast, cast)
var l = bzg_tube(q, cr) * 0.9 + 0.03
l = l * (1.0 - 0.4 * smoothstep(2.0, 3.4, abs(along))) * (1.0 - 0.5 * (1.0 - smoothstep(0.2, 0.2 + px, abs(abs(along) - 1.1))))
var col = bz_brass(l)
col = mix(col, bz_edge(), Finish.band(dq, 0.0, px, px) * 0.7)
let a = Finish.cover(dq, px)
return Finish.over(out, vec4(col * a, a))
}
// An engraved spiral: signed distance across its stroke, faded out of the
// ring r0..r1. Returns (groove, lit wall).
let bzg_curl = fn(p: vec2, c: vec2, pitch: float, r0: float, r1: float, flip: float, px: float) -> vec2 {
let q = p - c
let r = length(q)
let ang = atan2(q.y * flip, q.x)
let e = (fract(r / pitch - ang / (2.0 * PI) + 0.5) - 0.5) * pitch
let w = smoothstep(r0, r0 + 1.5, r) * (1.0 - smoothstep(r1 - 2.5, r1, r))
let dk = 1.0 - smoothstep(0.30, 0.30 + px, abs(e + 0.2))
let li = 1.0 - smoothstep(0.30, 0.30 + px, abs(e - 0.55))
return vec2(dk, li) * w
}
// An engraved arc of radius R about c between the angles a0..a1 (radians,
// atan2 of y, x): (groove, lit wall).
let bzg_arc = fn(p: vec2, c: vec2, R: float, a0: float, a1: float, px: float) -> vec2 {
let q = p - c
let e = length(q) - R
let ang = atan2(q.y, q.x)
let w = smoothstep(a0, a0 + 0.12, ang) * (1.0 - smoothstep(a1 - 0.12, a1, ang))
let dk = 1.0 - smoothstep(0.30, 0.30 + px, abs(e + 0.2))
let li = 1.0 - smoothstep(0.30, 0.30 + px, abs(e - 0.55))
return vec2(dk, li) * w
}
// An engraved stroke from its signed distance across: (groove, lit wall).
let bzg_line = fn(e: float, px: float) -> vec2 {
return vec2(1.0 - smoothstep(0.30, 0.30 + px, abs(e + 0.2)), 1.0 - smoothstep(0.30, 0.30 + px, abs(e - 0.55)))
}
// An engraved leaf: a lens `2 R` round and `wid` deep, its axis at `ang`,
// with a vein down the middle.
let bzg_leaf = fn(m: vec2, c: vec2, ang: float, R: float, wid: float, px: float) -> vec2 {
let ax = vec2(cos(ang), sin(ang))
let n = vec2(-ax.y, ax.x) * (R - wid)
let e = max(length(m - c - n) - R, length(m - c + n) - R)
let hl = sqrt(max(R * R - (R - wid) * (R - wid), 0.0)) * 0.8
let vein = bz_seg(m, c - ax * hl, c + ax * hl)
return max(bzg_line(e, px), bzg_line(vein, px) * 0.7)
}
// A vine along an edge: a wave `A + B sin` in from it, with a curl hanging
// in from every crest and a leaf from every trough, fading out by 178.
let bzg_vine = fn(m: vec2, x0: float, px: float) -> vec2 {
let wv = 2.0 * PI / 44.0
let sv = sin(m.x * wv)
let cv = cos(m.x * wv)
let e = (m.y - (12.0 + 5.0 * sv)) / sqrt(1.0 + 0.51 * cv * cv)
var g = bzg_line(e, px) * smoothstep(x0, x0 + 4.0, m.x) * (1.0 - smoothstep(172.0, 178.0, m.x))
if m.y > 2.0 && m.y < 40.0 {
g = max(g, bzg_curl(m, vec2(55.0, 26.5), 4.6, 1.0, 11.5, 1.0, px))
g = max(g, bzg_curl(m, vec2(99.0, 25.0), 3.8, 1.0, 9.5, 1.0, px))
g = max(g, bzg_curl(m, vec2(143.0, 23.5), 3.0, 0.8, 7.5, 1.0, px))
// the leaf of the nearest trough (at 33, 77, 121)
let lx = clamp(floor((m.x - 33.0) / 44.0 + 0.5), 0.0, 2.0) * 44.0 + 33.0
g = max(g, bzg_leaf(m, vec2(lx + 5.0, 16.0), 1.05, 16.0, 3.2, px))
}
return g
}
// The scrollwork of one corner, in corner space folded about its diagonal
// (m.x along the edge, m.y in from it): the vine, a volute in the corner and
// a sweep round it.
let bzg_scroll = fn(m: vec2, px: float) -> vec2 {
var g = bzg_vine(m, 30.0, px)
g = max(g, bzg_curl(m, vec2(24.0, 24.0), 5.0, 0.8, 15.5, 1.0, px))
g = max(g, bzg_arc(m, vec2(0.0, 0.0), 52.0, 0.25, 0.8, px))
return g
}
// RUST. Water stood on the plate and ran off it: rust gathers round the
// rivets and the corner screws, along the engraved frame, at the foot and in
// the lower corners, runs down in trails from the top row of rivets and the
// frame's top line, and lies in a few patches at random, little of it in the
// middle where the controls sit. Fixed in window space; no clock.
// Run-off trails down from sources `y0` points down, a chance of `prob` in
// each cell `cw` points across (the sources at `x0` and every `cw` on):
// `lmin`..`lmax` long and `wmax` wide at the source, narrowing and fading as
// they run, wandering a little, broken. Under a pixel wide a trail keeps a
// pixel and is dimmed by what it lacks of one.
let bzr_trails = fn(p: vec2, x0: float, cw: float, y0: float, prob: float, lmin: float, lmax: float, wmax: float, seed: float, px: float) -> float {
let cell = floor((p.x - x0) / cw + 0.5)
let h1 = Finish.hash(vec2(cell * 1.37 + seed, seed * 0.71 + 3.1))
let h2 = Finish.hash(vec2(cell * 2.11 + 17.3, seed + 41.9))
let dy = p.y - y0
let along = clamp(dy / mix(lmin, lmax, h2), 0.0, 1.0)
let xs = x0 + cell * cw + (fract(h1 * 7.31) - 0.5) * cw * 0.15 + (Finish.noise(vec2(p.y * 0.05, cell * 3.7 + seed)) - 0.5) * 2.4 * along
let wid = wmax * mix(1.0, 0.25, sqrt(along)) * mix(0.6, 1.0, h2)
let we = max(wid, px * 1.25)
let dx = abs(p.x - xs) / we
let across = exp(-2.5 * dx * dx) * (1.0 - smoothstep(1.2, 1.6, dx))
let fade = (1.0 - along) * (1.0 - along) * smoothstep(-2.0, 2.0, dy)
let broken = 0.35 + 0.65 * Finish.noise(vec2(cell * 5.3 + seed, p.y * 0.06))
return across * fade * broken * (wid / we) * step(h1, prob)
}
// The rust's field: rust lies where `.x` is over the threshold `.y`, the
// further over the thicker. `.z` the run-off trails, `.w` the verdigris
// round the foot of a few rivets. `fd` the engraved frame's distance.
let bzr_field = fn(p: vec2, W: float, H: float, fd: float, px: float) -> vec4 {
// patches: a domain-warped fbm, ragged by two finer octaves
let q = p * 0.0105
let wq = vec2(Finish.noise(q * 0.7 + vec2(13.1, 7.7)), Finish.noise(q * 0.7 + vec2(3.9, 29.3))) - vec2(0.5, 0.5)
var f = Finish.fbm(q + wq * 1.8 + vec2(41.0, 17.0))
f = f + (Finish.noise(p * 0.085 + vec2(5.0, 9.0)) - 0.5) * 0.10 + (Finish.noise(p * 0.29 + vec2(1.0, 3.0)) - 0.5) * 0.05
// where it gathers: toward the edges, the foot more than the top, and
// the lower corners most
let de = min(min(p.x, W - p.x) * 1.1, min(p.y * 5.0, (H - p.y) * 0.5))
let lc = 1.0 - smoothstep(30.0, 380.0, length(vec2(min(p.x, W - p.x), (H - p.y) * 1.3)))
let foot = smoothstep(H * 0.5, H, p.y)
let bias = clamp(0.5 * (1.0 - smoothstep(6.0, 130.0, de)) + 0.8 * lc + 0.25 * foot, 0.0, 1.0)
let thr = mix(0.70, 0.48, bias)
// in the engraved frame, more of it toward the foot
f = f + 0.12 * (1.0 - smoothstep(1.0, 8.0, abs(fd + 2.0))) * mix(0.35, 1.0, smoothstep(0.2, 0.95, p.y / max(H, 1.0)))
// round the rivets of the top row and the foot, each its own
let rc = floor((p.x - 8.0) / 48.0 + 0.5)
let rcx = 8.0 + rc * 48.0
let ry = mix(8.0, H - 8.0, step(H * 0.5, p.y))
let rh = Finish.hash(vec2(rc * 17.13 + 3.3, ry * 0.37 + 7.7))
let rin = step(30.0, rcx) * step(rcx, W - 30.0)
let rd = length(vec2(p.x - rcx, p.y - ry))
let rr = mix(3.5, 9.0, rh) + (Finish.noise(p * 0.45 + vec2(rc * 3.1, 0.0)) - 0.5) * 2.5
f = f + mix(0.16, 0.36, rh) * mix(0.9, 1.2, step(H * 0.5, p.y)) * (1.0 - smoothstep(rr * 0.45, rr, rd)) * rin
// and round the four screws
let sd = length(vec2(min(p.x, W - p.x), min(p.y, H - p.y)) - vec2(9.0, 9.0))
f = f + 0.34 * (1.0 - smoothstep(5.0, 15.0, sd + (Finish.noise(p * 0.4) - 0.5) * 4.0))
// the run-off: down from the top row of rivets, and from the frame's top
// line between the corners
let tr = bzr_trails(p, 8.0, 48.0, 10.0, 0.7, 30.0, 160.0, 5.5, 1.0, px) * rin * (1.0 - step(H * 0.5, p.y))
let tf = bzr_trails(p, 5.0, 16.0, 16.0, 0.16, 20.0, 120.0, 3.6, 2.0, px) * step(26.0, p.x) * step(p.x, W - 26.0)
// a ring of verdigris at the foot of one rivet in five
let vr = (smoothstep(1.7, 2.3, rd) - smoothstep(2.9, 3.7, rd)) * rin * (1.0 - step(0.2, rh))
return vec4(f, thr, max(tr, tf), vr)
}
// The rust's colour at thickness `h` 0..1 (0 at its edge): a powdery
// orange where it is thin, rust orange, a deep orange-brown, and in places a
// dark crust where it is thickest; pitted and granular all through. Matte:
// no sheen of the brass is left on it.
let bzr_ink = fn(p: vec2, h: float) -> vec3 {
let gr = Finish.noise(p * 0.21 + vec2(7.0, 2.0)) - 0.5
let x = clamp(h + gr * 0.25, 0.0, 1.0)
var c = mix(vec3(0.753, 0.416, 0.173), vec3(0.643, 0.278, 0.102), smoothstep(0.0, 0.1, x))
c = mix(c, vec3(0.420, 0.184, 0.071), smoothstep(0.08, 0.32, x))
let crust = smoothstep(0.42, 0.7, Finish.noise(p * 0.06 + vec2(23.0, 5.0))) * smoothstep(0.35, 0.75, x)
c = mix(c, vec3(0.227, 0.110, 0.055), crust)
let pit = smoothstep(0.55, 0.92, Finish.noise(p * 0.42 + vec2(31.0, 11.0)))
return c * (1.0 - 0.32 * pit) * (1.0 + gr * 0.25)
}
mod.widgets.Window.show_bg = true
mod.widgets.Window.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * self.rect_size
let size = self.rect_size
let W = size.x
let H = size.y
let t = clamp(p.y / max(H, 1.0), 0.0, 1.0)
var col = mix(vec3(0.290, 0.259, 0.220), vec3(0.212, 0.184, 0.157), t)
let v = (p / max(size, vec2(1.0, 1.0)) - vec2(0.5, 0.40)) * vec2(1.0, 1.1)
let vig = 1.0 - 0.34 * smoothstep(0.22, 0.95, length(v))
col = col * vig
let mot = Finish.fbm(p * 0.012) - 0.5
col = col * (1.0 + mot * 0.18)
let br = Finish.brushed(p, vec2(1.0, 0.0), px * 1.4) - 0.5
let br2 = Finish.noise(vec2(p.x * 0.004, p.y / px * 0.7)) - 0.5
col = col * (1.0 + br * 0.07 + br2 * 0.04)
// the quiet middle: decoration at full strength near the edges and the
// foot, fading to a fifth toward the middle of the window
let edge = min(min(p.x, W - p.x), min(p.y, (H - p.y) * 0.75))
let k = 1.0 - 0.8 * smoothstep(70.0, 140.0, edge)
// the engraved frame, 16 points in
let fd = bz_rbox(p, size * 0.5, size * 0.5 - vec2(16.0, 16.0), 10.0)
var eng = vec2(1.0 - smoothstep(0.30, 0.30 + px, abs(fd + 0.2)), 1.0 - smoothstep(0.30, 0.30 + px, abs(fd - 0.55)))
// a second, finer line 4 points inside it
eng = max(eng, vec2(1.0 - smoothstep(0.25, 0.25 + px, abs(fd + 4.2)), 1.0 - smoothstep(0.25, 0.25 + px, abs(fd + 3.45))) * 0.7)
// scrollwork in the four corners, 20 points in from the edges
let cx = min(p.x, W - p.x) - 20.0
let cy = min(p.y, H - p.y) - 20.0
if cx < 175.0 && cy < 175.0 {
let m = vec2(max(cx, cy), min(cx, cy))
eng = max(eng, bzg_scroll(m, px))
}
// and a band of scrollwork above the foot's pipes, folded about its
// middle both ways, drawn at one and a half times the corners' size
let fx = abs(p.x - W * 0.6) / 1.5
let fy = abs(p.y - (H - 88.0)) / 1.5 + 3.0
if fx < 180.0 && fy < 42.0 {
var g = bzg_vine(vec2(fx + 22.0, fy), 22.0, px / 1.5)
g = max(g, bzg_curl(vec2(fx, fy), vec2(0.0, 3.0), 4.2, 0.8, 16.0, 1.0, px / 1.5))
eng = max(eng, g)
}
// the rust, lit as the plate is, under the engraving: a pixel's ramp at
// its edge from the field's own slope, a faint stain round it, and the
// trails; at three fifths in the quiet middle
let rf = bzr_field(p, W, H, fd, px)
let rg = abs(dFdx(rf.x)) + abs(dFdy(rf.x))
let redge = clamp(0.5 + (rf.x - rf.y) / max(rg, 0.0001), 0.0, 1.0)
let rh = smoothstep(rf.y, rf.y + 0.12, rf.x)
var ra = max(redge * mix(0.55, 0.95, smoothstep(0.0, 0.5, rh)), smoothstep(rf.y - 0.06, rf.y, rf.x) * 0.2)
ra = max(ra, rf.z * 0.75) * mix(0.6, 1.0, k)
let rlit = vig * mix(0.9, 0.68, t) * (1.0 + mot * 0.12)
col = mix(col, bzr_ink(p, max(rh, rf.z * 0.3)) * rlit, ra)
let ek = mix(0.55, 1.0, k)
col = col * (1.0 - 0.45 * eng.x * ek) + vec3(0.115, 0.100, 0.078) * eng.y * ek * (1.0 - 0.7 * ra)
// verdigris, sparingly: in the engraving just outside the rust, here and
// there, and round the foot of a few rivets
let vg = eng.x * (1.0 - ra) * smoothstep(rf.y - 0.09, rf.y - 0.02, rf.x) * smoothstep(0.5, 0.68, Finish.noise(p * 0.035 + vec2(61.0, 3.0)))
let vcol = mix(vec3(0.247, 0.490, 0.431), vec3(0.369, 0.612, 0.541), Finish.noise(p * 0.3 + vec2(9.0, 4.0))) * rlit
col = mix(col, vcol, clamp(vg * 0.55 * ek + rf.w * 0.5, 0.0, 1.0))
col = col + g3(bz_grain(p / px, 0.020) + bz_grain(p / px + vec2(5.3, 1.7), 0.025) * ra)
// THE MACHINERY, premultiplied, back to front
var dec = vec4(0.0, 0.0, 0.0, 0.0)
// rivets on the frame's outside, along the top and the foot, and screws
// in the four corners
let rp = 48.0
let rxx = (fract((p.x - 8.0) / rp + 0.5) - 0.5) * rp
let ryy = mix(p.y - 8.0, p.y - (H - 8.0), step(H * 0.5, p.y))
if abs(ryy) < 5.0 && p.x > 30.0 && p.x < W - 30.0 {
let rq = vec2(rxx, ryy)
dec = Finish.over(dec, bz_rivet(rq, vec2(0.0, 0.0), 2.0, px, 2.0, 0.0))
}
let sq = vec2(min(p.x, W - p.x), min(p.y, H - p.y)) - vec2(9.0, 9.0)
if length(sq) < 8.0 {
let sv = vec2(mix(p.x - 9.0, p.x - (W - 9.0), step(W * 0.5, p.x)), mix(p.y - 9.0, p.y - (H - 9.0), step(H * 0.5, p.y)))
dec = Finish.over(dec, bz_rivet(sv, vec2(0.0, 0.0), 4.0, px, 0.0, -0.05))
let slot = (1.0 - smoothstep(0.45, 0.45 + px, abs(sq.x + sq.y) * 0.7071)) * (1.0 - step(3.2, length(sq)))
dec = Finish.over(dec, vec4(bz_edge() * slot * 0.85, slot * 0.85))
}
let bx = floor(W * 0.2) + 60.0
let xe = floor(W * 0.55)
// the foot: two pipes from behind the left gears, bending down into the
// edge
if p.y > H - 70.0 && p.x > bx && p.x < xe + 40.0 {
let y1 = H - 52.0
let y2 = H - 30.0
let q1 = bzg_near(bzg_near(bzg_run(p, vec2(bx + 60.0, y1), vec2(xe, y1)), bzg_bend(p, vec2(xe, y1 + 16.0), 16.0, 1.0, -1.0)), bzg_run(p, vec2(xe + 16.0, y1 + 16.0), vec2(xe + 16.0, H + 20.0)))
let q2 = bzg_near(bzg_near(bzg_run(p, vec2(bx + 60.0, y2), vec2(xe - 10.0, y2)), bzg_bend(p, vec2(xe - 10.0, y2 + 8.0), 8.0, 1.0, -1.0)), bzg_run(p, vec2(xe - 2.0, y2 + 8.0), vec2(xe - 2.0, H + 20.0)))
dec = Finish.over(dec, bzg_pipe(q2, 6.0, px))
dec = Finish.over(dec, bzg_collar(q2, p.x - (bx + 130.0), 6.0, px))
dec = Finish.over(dec, bzg_collar(q2, p.x - (xe - 44.0), 6.0, px))
dec = Finish.over(dec, bzg_pipe(q1, 6.5, px))
dec = Finish.over(dec, bzg_collar(q1, p.x - (bx + 130.0), 6.5, px))
dec = Finish.over(dec, bzg_collar(q1, p.x - (xe - 44.0), 6.5, px))
}
// the top and the right edge: one pipe along the top that bends down
// the right edge
let xt = floor(W * 0.64)
if p.y < 36.0 && p.x > xt - 10.0 {
let qt = bzg_near(bzg_run(p, vec2(xt, 6.0), vec2(W - 26.0, 6.0)), bzg_bend(p, vec2(W - 26.0, 24.0), 18.0, 1.0, -1.0))
// a gear behind the bend
dec = Finish.over(dec, bzg_gear(p, vec2(W - 44.0, 30.0), 17.0, 12.0, 5.0, 0.3, px))
dec = Finish.over(dec, bzg_pipe(qt, 4.5, px))
dec = Finish.over(dec, bzg_collar(qt, p.x - xt - 2.0, 4.5, px))
dec = Finish.over(dec, bzg_collar(qt, p.x - (W - 120.0), 4.5, px))
}
if p.x > W - 20.0 && p.y > 20.0 {
let qr = bzg_near(bzg_run(p, vec2(W - 8.0, 24.0), vec2(W - 8.0, H - 40.0)), bzg_bend(p, vec2(W - 26.0, 24.0), 18.0, 1.0, -1.0))
dec = Finish.over(dec, bzg_pipe(qr, 4.5, px))
dec = Finish.over(dec, bzg_collar(qr, p.y - floor(H * 0.3), 4.5, px))
dec = Finish.over(dec, bzg_collar(qr, p.y - floor(H * 0.8), 4.5, px))
}
// gears: the left cluster at the foot
if p.y > H - 110.0 && p.x > bx - 100.0 && p.x < bx + 140.0 {
dec = Finish.over(dec, bzg_gear(p, vec2(bx - 52.0, H - 50.0), 26.0, 18.0, 5.0, 0.2, px))
dec = Finish.over(dec, bzg_gear(p, vec2(bx, H + 6.0), 58.0, 38.0, 6.0, 0.0, px))
dec = Finish.over(dec, bzg_gear(p, vec2(bx + 76.0, H - 42.0), 36.0, 24.0, 6.0, 0.12, px))
}
// the right cluster at the foot, and the corner
let rx = floor(W * 0.72) - 24.0
if p.y > H - 80.0 && p.x > rx - 90.0 {
dec = Finish.over(dec, bzg_gear(p, vec2(rx, H + 6.0), 42.0, 28.0, 6.0, 0.1, px))
dec = Finish.over(dec, bzg_gear(p, vec2(rx - 52.0, H - 26.0), 21.0, 15.0, 5.0, 0.25, px))
dec = Finish.over(dec, bzg_gear(p, vec2(W - 24.0, H + 10.0), 54.0, 36.0, 6.0, 0.05, px))
}
// the right edge, half way down
let ry = floor(H * 0.52)
if p.x > W - 60.0 && p.y > ry - 60.0 && p.y < ry + 100.0 {
dec = Finish.over(dec, bzg_gear(p, vec2(W + 12.0, ry), 46.0, 30.0, 6.0, 0.0, px))
dec = Finish.over(dec, bzg_gear(p, vec2(W - 10.0, ry + 58.0), 24.0, 16.0, 5.0, 0.2, px))
}
col = col * (1.0 - dec.a * k) + dec.rgb * k
return vec4(col, 1.0)
}
// NIXIE TUBES. Every readout cell is a tube standing in a blackened brass
// rack: a glass envelope rounder at the top than at the foot, dark and
// smoky inside, its numerals stacked one behind the other as bent wire
// cathodes, a square anode mesh in front of them, and the foot set into a
// rail. The lit numeral's light falls off as the inverse of the distance
// from its wire: about a device pixel of it at full strength, orange
// (1, 0.2, 0) running to a hot core where it is strongest, and a long dim
// red fall-off filling the tube. A slow value noise over the window sets
// how strongly each part of a numeral glows; the readout has no clock, so
// it stands still. The others stand behind it as dark wire, a shadow
// through the glow, as strong as the readout's ghost. The mesh is a grid of
// single device pixels on the pixel grid that lets a quarter of the light
// through its wires. The point is a small glowing ring at the foot of the
// cell, the colon a narrow tube with two. Disabled, the tubes are dark with
// their wires; a blank cell is a dark tube. Distances in digit heights (the
// digit is 0..1 down) unless named otherwise, `px` one device pixel in
// them.
// A numeral is drawn in a cell 1 wide and 1.5 tall, y up from its foot, as
// a few wire strokes: segments (`bz_seg`), rings and arcs.
// An arc round `c`, `R` round, from the angle `a0` anticlockwise through
// `sw`: the distance to it, or to its nearer end off its sweep.
let nx_arc = fn(p: vec2, c: vec2, R: float, a0: float, sw: float) -> float {
let d = p - c
var df = atan2(d.y, d.x) - (a0 + sw * 0.5)
df = df - 6.2831853 * floor((df + 3.14159265) / 6.2831853)
let e0 = c + vec2(cos(a0), sin(a0)) * R
let e1 = c + vec2(cos(a0 + sw), sin(a0 + sw)) * R
let off = min(length(p - e0), length(p - e1))
return mix(off, abs(length(d) - R), step(abs(df), sw * 0.5))
}
// A ring round `c`, `R` round: the distance to it.
let nx_ring = fn(p: vec2, c: vec2, R: float) -> float {
return abs(length(p - c) - R)
}
// 1 when `a` is the whole number `b`.
let nx_is = fn(a: float, b: float) -> float {
return 1.0 - step(0.5, abs(a - b))
}
// The stack of cathodes, in the numeral's cell: the distance to the lit
// numeral `k` (0..9, 10 the minus, -1 none) and to every numeral standing
// unlit behind it.
let nx_stack = fn(p: vec2, k: float) -> vec2 {
var dl = 10.0
var du = 10.0
let d0 = min(min(bz_seg(p, vec2(1.0, 1.0), vec2(1.0, 0.5)), bz_seg(p, vec2(0.0, 1.0), vec2(0.0, 0.5))), min(nx_arc(p, vec2(0.5, 1.0), 0.5, 0.0, 3.142), nx_arc(p, vec2(0.5, 0.5), 0.5, 3.142, 3.142)))
let d1 = bz_seg(p, vec2(0.5, 1.5), vec2(0.5, 0.0))
let d2 = min(min(bz_seg(p, vec2(1.0, 0.0), vec2(0.0, 0.0)), bz_seg(p, vec2(0.388, 0.561), vec2(0.806, 0.719))), min(nx_arc(p, vec2(0.5, 1.0), 0.5, 0.0, 3.142), min(nx_arc(p, vec2(0.7, 1.0), 0.3, 5.074, 1.209), nx_arc(p, vec2(0.6, 0.0), 0.6, 1.932, 1.209))))
let d3 = min(min(bz_seg(p, vec2(0.0, 1.5), vec2(1.0, 1.5)), bz_seg(p, vec2(1.0, 1.5), vec2(0.5, 1.0))), nx_arc(p, vec2(0.5, 0.5), 0.5, 3.142, 4.712))
let d4 = min(bz_seg(p, vec2(0.7, 1.5), vec2(0.0, 0.5)), min(bz_seg(p, vec2(0.0, 0.5), vec2(1.0, 0.5)), bz_seg(p, vec2(0.7, 1.2), vec2(0.7, 0.0))))
let d5 = min(min(bz_seg(p, vec2(1.0, 1.5), vec2(0.3, 1.5)), bz_seg(p, vec2(0.3, 1.5), vec2(0.2, 0.9))), nx_arc(p, vec2(0.5, 0.5), 0.5, 3.142, 5.356))
let d6 = min(bz_seg(p, vec2(0.067, 0.75), vec2(0.5, 1.5)), nx_ring(p, vec2(0.5, 0.5), 0.5))
let d7 = min(bz_seg(p, vec2(0.0, 1.5), vec2(1.0, 1.5)), bz_seg(p, vec2(1.0, 1.5), vec2(0.5, 0.0)))
let d8 = min(nx_ring(p, vec2(0.5, 0.4), 0.4), nx_ring(p, vec2(0.5, 1.15), 0.35))
let d9 = min(bz_seg(p, vec2(0.933, 0.75), vec2(0.5, 0.0)), nx_ring(p, vec2(0.5, 1.0), 0.5))
dl = min(dl, d0 + 10.0 * (1.0 - nx_is(k, 0.0)))
dl = min(dl, d1 + 10.0 * (1.0 - nx_is(k, 1.0)))
dl = min(dl, d2 + 10.0 * (1.0 - nx_is(k, 2.0)))
dl = min(dl, d3 + 10.0 * (1.0 - nx_is(k, 3.0)))
dl = min(dl, d4 + 10.0 * (1.0 - nx_is(k, 4.0)))
dl = min(dl, d5 + 10.0 * (1.0 - nx_is(k, 5.0)))
dl = min(dl, d6 + 10.0 * (1.0 - nx_is(k, 6.0)))
dl = min(dl, d7 + 10.0 * (1.0 - nx_is(k, 7.0)))
dl = min(dl, d8 + 10.0 * (1.0 - nx_is(k, 8.0)))
dl = min(dl, d9 + 10.0 * (1.0 - nx_is(k, 9.0)))
dl = min(dl, bz_seg(p, vec2(0.15, 0.75), vec2(0.85, 0.75)) + 10.0 * (1.0 - nx_is(k, 10.0)))
du = min(du, d0 + 10.0 * nx_is(k, 0.0))
du = min(du, d1 + 10.0 * nx_is(k, 1.0))
du = min(du, d2 + 10.0 * nx_is(k, 2.0))
du = min(du, d3 + 10.0 * nx_is(k, 3.0))
du = min(du, d4 + 10.0 * nx_is(k, 4.0))
du = min(du, d5 + 10.0 * nx_is(k, 5.0))
du = min(du, d6 + 10.0 * nx_is(k, 6.0))
du = min(du, d7 + 10.0 * nx_is(k, 7.0))
du = min(du, d8 + 10.0 * nx_is(k, 8.0))
du = min(du, d9 + 10.0 * nx_is(k, 9.0))
return vec2(dl, du)
}
// The numeral a cell's seven segments spell: 0..9, 10 the minus, -1 for a
// blank or anything a nixie cannot show (each digit's mask is its own).
let nx_numeral = fn(mask: float) -> float {
var k = -1.0
k = mix(k, 0.0, nx_is(mask, 63.0))
k = mix(k, 1.0, nx_is(mask, 6.0))
k = mix(k, 2.0, nx_is(mask, 91.0))
k = mix(k, 3.0, nx_is(mask, 79.0))
k = mix(k, 4.0, nx_is(mask, 102.0))
k = mix(k, 5.0, nx_is(mask, 109.0))
k = mix(k, 6.0, nx_is(mask, 125.0))
k = mix(k, 7.0, nx_is(mask, 7.0))
k = mix(k, 8.0, nx_is(mask, 127.0))
k = mix(k, 9.0, nx_is(mask, 111.0))
return mix(k, 10.0, nx_is(mask, 64.0))
}
// The quad grows 0.15 of the digit above it for the dome and 0.13 below it
// for the rail, past the clip only where the clip is the readout's own edge
// (a clip that cuts into the readout still cuts the tubes).
mod.widgets.Readout.draw_bg.vertex = fn() {
let h = self.rect_size.y
let mt = h * 0.15
let mb = h * 0.13
let gp = self.rect_pos - vec2(0.0, mt)
let gs = self.rect_size + vec2(0.0, mt + mb)
let clip = self.draw_clip
let own_lo = step(clip.y, self.rect_pos.y + 0.01)
let own_hi = step(self.rect_pos.y + self.rect_size.y - 0.01, clip.w)
let v = self.geom.pos * gs + gp
let c = vec2(clamp(v.x, clip.x, clip.z), clamp(v.y, clip.y - own_lo * mt, clip.w + own_hi * mb))
let w = clamp(c + self.draw_list.view_shift, self.draw_list.view_clip.xy, self.draw_list.view_clip.zw)
self.pos = (w - gp) / gs
self.world = self.draw_list.view_transform * vec4(w.x, w.y, self.draw_depth + self.draw_call.zbias, 1.0)
self.vertex_pos = self.draw_pass.camera_projection * (self.draw_pass.camera_view * self.world)
}
mod.widgets.Readout.draw_bg.pixel = fn() {
let h = max(self.rect_size.y, 1.0)
let over = (self.rect_size.x - self.span) * 0.5
let gs = self.rect_size + vec2(0.0, h * 0.28)
let p = (self.pos * gs - vec2(over, h * 0.15)) / h
// where the pixel is in the window, in points and in device pixels
let wpt = self.rect_pos - vec2(0.0, h * 0.15) + self.pos * gs
let dev = wpt * self.draw_pass.dpi_factor
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) / h
let span = self.span / h
let live = step(0.9, self.opacity)
let ghost = clamp(self.ghost * 8.5, 0.0, 1.0)
let fil = vec3(1.0, 0.2, 0.0)
let core_ink = vec3(1.0, 0.949, 0.753)
// the rack: a blackened brass post between two tubes, half in each cell
let g = max(0.07, px * 2.0)
let dpost = min(p.x, span - p.x) - g * 0.35
let post = Finish.cover(dpost, px) * Finish.cover(-0.12 - p.y, px) * step(0.0, p.x) * step(p.x, span)
let plit = Finish.band(span - p.x - g * 0.35, 0.0, px, px)
let pcol = vec3(0.115, 0.094, 0.068) * mix(1.15, 0.8, clamp(p.y, 0.0, 1.0)) + vec3(0.25, 0.20, 0.12) * plit * 0.6
var out = vec4(pcol * post, post)
// the envelope, rounder at the top than at the foot
let cx = span * 0.5
let tw = max(span * 0.5 - g * 0.5, 0.05)
let tc = vec2(cx, 0.46)
let th = vec2(tw, 0.59)
let rr = mix(min(tw * 0.72, 0.3), min(0.05, tw), step(tc.y, p.y))
let gd = bz_rbox(p, tc, th, rr)
let inner = Finish.cover(gd, px)
var col = mix(vec3(0.066, 0.056, 0.050), vec3(0.036, 0.031, 0.028), clamp(p.y, 0.0, 1.0))
// the cathodes. A numeral stands 0.86 of the digit tall from 0.04 to
// 0.90, its cell drawn at 0.85 of its width so it and the point fit the
// tube; distances come back to digit heights at the mean of the two
// scales. The colon's two rings stand where the numerals' cell has
// them.
let sc = 0.86 / 1.5
let q = vec2(p.x + (p.y - 0.5) * self.slant, p.y)
let k = mix(-1.0, nx_numeral(self.mask), live)
var dl = 10.0
var du = 10.0
if self.colon > 0.5 {
let rd = 0.04 * sc
du = min(abs(length(q - vec2(cx, 0.90 - 0.99 * sc)) - rd), abs(length(q - vec2(cx, 0.90 - 0.44 * sc)) - rd))
dl = du + (1.0 - live) * 10.0
} else {
let ncx = cx - 0.03
let r = vec2((q.x - ncx) / (sc * 0.85) + 0.5, (0.90 - q.y) / sc)
let st = nx_stack(r, k) * (sc * 0.925)
let dp = abs(length(q - vec2(ncx + 0.305, 0.965)) - 0.012)
dl = min(st.x, dp + (1.0 - step(0.5, self.dot) * live) * 10.0)
du = min(st.y, dp)
}
let lit = step(dl, 9.0)
// the light: the inverse of the distance in device pixels, its strength
// the reference's share of the numeral's height and never under 0.6 of
// a pixel, softened inside a pixel of the wire so it stays smooth there,
// times one and the slow noise; over it the filament, a device pixel of
// hot core with an exact edge
let npx = 0.86 / px
let kp = max(0.6, 0.016 * npx)
let dpx = max(dl, 0.0) / px
let pulse = Finish.noise(wpt / (0.86 * h) * 0.625 + vec2(0.5, 0.5))
let s2 = kp / sqrt(dpx * dpx + 0.5) * (1.0 + pulse) * lit
let wire_core = Finish.cover(dpx - 0.5, 1.0) * lit
var light = fil * s2
light = mix(light, core_ink, wire_core * mix(0.55, 0.9, pulse))
// the unlit wires: dark metal catching a little of the light, at the
// ghost's strength, and a shadow through the glow behind them; the lit
// wire stands in front of them
let ru = 0.011
let wu = max(ru, px * 0.5)
let wire = Finish.cover(du - wu, px) * (ru / wu) * ghost
col = mix(col, vec3(0.13, 0.11, 0.095) + min(light, vec3(1.0, 1.0, 1.0)) * 0.18, wire)
col = col + light * (1.0 - 0.7 * wire * smoothstep(1.0, 2.5, dpx))
col = min(col, vec3(1.0, 1.0, 1.0))
// the anode mesh: one device pixel wires every `mp` pixels on the
// window's pixel grid, three and a half cells up a numeral and never
// closer than six pixels, a quarter of the light through them
let mp = max(6.0, floor(npx / 3.5 + 0.5))
let ix = floor(dev.x)
let iy = floor(dev.y)
let mx = 1.0 - step(0.5, ix - mp * floor(ix / mp))
let my = 1.0 - step(0.5, iy - mp * floor(iy / mp))
let mesh = max(mx, my) * Finish.cover(gd + 0.02, px)
col = col * (1.0 - 0.75 * mesh)
// the glass: a soft rim, a streak down the side toward the light and a
// fainter one on the far side, a glint on the dome, its outline
let xr = (p.x - (cx - tw)) / max(tw * 2.0, 0.001)
let vfade = smoothstep(-0.05, 0.15, p.y) * (1.0 - smoothstep(0.7, 0.98, p.y))
let streak = smoothstep(0.08, 0.13, xr) * (1.0 - smoothstep(0.17, 0.26, xr)) * 0.08 + Finish.cover(abs(xr - 0.12) * tw * 2.0 - px * 0.5, px) * 0.10
let far = smoothstep(0.80, 0.85, xr) * (1.0 - smoothstep(0.88, 0.93, xr)) * 0.035
let nrm = normalize(p - tc + vec2(0.00001, 0.00002))
let side = clamp(0.5 - 0.5 * dot(nrm, vec2(0.6, 0.8)), 0.0, 1.0)
let rim = Finish.band(gd, 0.0, px, px) * mix(0.06, 0.24, side) + Finish.band(gd, px, px * 3.0, px) * 0.04
let glint = Finish.band(gd, px * 1.5, px * 3.0, px) * smoothstep(0.55, 0.9, side) * step(p.y, tc.y - th.y + rr) * 0.25
col = min(col + vec3(0.92, 0.88, 0.80) * ((streak + far) * vfade + rim + glint), vec3(1.0, 1.0, 1.0))
out = Finish.over(out, vec4(col * inner, inner))
let edge = Finish.ring_out(gd, 0.0, px, px) * 0.55
out = Finish.over(out, vec4(0.0, 0.0, 0.0, edge))
// the rail the tubes stand in, along the foot of the row
let rl = Finish.cover(1.0 - p.y, px) * Finish.cover(p.y - 1.115, px) * step(0.0, p.x) * step(p.x, span)
let ry = clamp((p.y - 1.0) / 0.115, 0.0, 1.0)
var rcol = mix(vec3(0.19, 0.152, 0.098), vec3(0.075, 0.062, 0.048), ry)
rcol = rcol + vec3(0.30, 0.24, 0.14) * Finish.band(1.0 - p.y, 0.0, px, px) * 0.8
rcol = rcol * (1.0 - 0.5 * Finish.band(1.115 - p.y, 0.0, px, px))
return Finish.over(out, vec4(rcol * rl, rl))
}
// ---------------------------------------------------------------- band
// THE BAND is the sheet's own slider with two thumbs: the same track, the
// same lit value and the same thumb as the Slider, the value lit between
// the two thumbs, its quad grown as the slider's is so the thumbs keep
// their shape. The widget's grab geometry is set to the thumbs' own, so
// what is drawn is what is dragged.
mod.widgets.RangeSliderFlat.clip_x = false
mod.widgets.RangeSliderFlat.clip_y = false
mod.widgets.RangeSliderFlat.track_inset = 1.0
mod.widgets.RangeSliderFlat.handle_size = 18.0
mod.widgets.RangeSliderFlat.draw_bg.vertex = fn() {
let g = grow_vertex(self.rect_pos, self.rect_size, 6.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.RangeSliderFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = 6.0
let size = self.rect_size
let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let o = self.rect_pos
let live = 1.0 - self.disabled
// The gears as large as the slider's.
let tr = 9.0
let cy = size.y - tr - 1.0
let th = 6.0 * px
let top = Finish.snap(o.y + cy, px) - o.y - 3.0 * px
let x0 = Finish.snap(o.x + 1.0, px) - o.x
let w = Finish.snap(o.x + size.x - 1.0, px) - o.x - x0
let k0 = x0 + tr + self.start_pos * (w - 2.0 * tr)
let k1 = x0 + tr + self.end_pos * (w - 2.0 * tr)
let hot = max(self.hover, self.drag) * live
var out = mix(bz_track(p, vec2(x0, top), w, th, px, x0, self.disabled, hot), bz_track(p, vec2(x0, top), w, th, px, k1, self.disabled, hot), step(k0, p.x))
let f0 = bz_fit(vec2(k0, top + th * 0.5), o, tr, px)
let f1 = bz_fit(vec2(k1, top + th * 0.5), o, tr, px)
out = Finish.over(out, bz_gear(p, f0.xy, f0.z, px, 15.0, 6.0, self.hot_start * live, self.drag * self.hot_start * live, self.disabled, max(self.focus, self.drag * self.hot_start) * live))
out = Finish.over(out, bz_gear(p, f1.xy, f1.z, px, 15.0, 6.0, self.hot_end * live, self.drag * self.hot_end * live, self.disabled, max(self.focus, self.drag * self.hot_end) * live))
return Finish.over(out, bz_focus(length(p - f1.xy) - f1.z, px, self.focus * live))
}
// ---------------------------------------------------------------- switches and meter
// THE SWITCHES are the sheet's own toggle, in proportion with the
// labels and the controls beside them: the rocker and the slide switch
// take the toggle's face, its size and shape, its quad and its label
// gap (the library's own faces for them are flat boxes sized for
// another face). The rocker turns on a click; the slide switch follows a
// drag, as the toggle does.
mod.widgets.ToggleRocker.label_walk.margin = mod.turtle.Inset{left: 46.0 right: 3.0 top: 0.0 bottom: 0.0}
mod.widgets.ToggleSlide.label_walk.margin = mod.turtle.Inset{left: 46.0 right: 3.0 top: 0.0 bottom: 0.0}
mod.widgets.ToggleRocker.draw_bg.size = uniform(16.0)
mod.widgets.ToggleSlide.draw_bg.size = uniform(16.0)
mod.widgets.ToggleRocker.min_height = 28.0
mod.widgets.ToggleSlide.min_height = 28.0
mod.widgets.ToggleRocker.padding = mod.turtle.Inset{left: 0.0 right: 3.0 top: 6.0 bottom: 6.0}
mod.widgets.ToggleSlide.padding = mod.turtle.Inset{left: 0.0 right: 3.0 top: 6.0 bottom: 6.0}
mod.widgets.ToggleRocker.draw_bg.pill_aspect = uniform(2.375)
mod.widgets.ToggleSlide.draw_bg.pill_aspect = uniform(2.375)
mod.widgets.ToggleRocker.draw_bg.knob_inset = uniform(0.0)
mod.widgets.ToggleSlide.draw_bg.knob_inset = uniform(0.0)
mod.widgets.ToggleRocker.draw_bg.pixel = mod.widgets.ToggleFlat.draw_bg.pixel
mod.widgets.ToggleSlide.draw_bg.pixel = mod.widgets.ToggleFlat.draw_bg.pixel
// THE METER sits in the same window as the readouts and the screen: the
// library's display housing drawn round its face -- the bezel ring, a shade
// of whatever ground it is set in with a device pixel of dark at its edge;
// the face with the surround's shadow falling across it from the top and a
// device pixel of black where the glass meets the bezel; the glass's quiet
// diagonal band and the light line inside its top edge; the lip of light on
// the ground under it -- with the same corner, so the meter and the
// readouts read as one family. The scale, the red zone, the needle and its
// pivot are the library's own.
mod.widgets.NeedleMeter.draw_bg.border_radius = uniform(theme.corner_radius * 0.5)
mod.widgets.NeedleMeter.draw_bg.color_bezel = uniform(theme.color_d_05)
mod.widgets.NeedleMeter.draw_bg.color_lip = uniform(theme.color_u_15)
mod.widgets.NeedleMeter.draw_bg.pixel = fn() {
let p = self.pos * self.rect_size
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let lo = vec2(Finish.snap(self.rect_pos.x, px), Finish.snap(self.rect_pos.y, px)) - self.rect_pos
let hi = vec2(Finish.snap(self.rect_pos.x + self.rect_size.x, px), Finish.snap(self.rect_pos.y + self.rect_size.y, px) - px) - self.rect_pos
let c = (lo + hi) * 0.5
let ho = (hi - lo) * 0.5
let r_out = min(self.border_radius, min(ho.x, ho.y))
let outer = Material.sd_box(p, c, ho, r_out)
let b = Finish.snap(2.0, px)
let hf = max(ho - vec2(b, b), vec2(0.5, 0.5))
let rf = max(r_out - b, 0.0)
let face_d = Material.sd_box(p, c, hf, rf)
let pv = self.pivot()
let rr = self.scale_radius()
let q = p - pv
let r = length(q)
let ang = atan2(q.x, 0.0 - q.y)
let sweep = self.sweep * 0.017453293
let t = ang / sweep + 0.5
let on_scale = step(-0.0001, t) * step(t, 1.0001)
var face = self.color.rgb
// The scale: a hairline arc and a tick at every division.
let arc = Finish.cover(abs(r - rr) - px * 0.5, px) * on_scale
let n = max(floor(self.majors + 0.5), 1.0) * max(floor(self.minors + 0.5), 1.0)
let k = floor(clamp(t, 0.0, 1.0) * n + 0.5)
let ak = (k / n - 0.5) * sweep
let perp = abs(q.x * cos(ak) + q.y * sin(ak))
let mn = max(floor(self.minors + 0.5), 1.0)
let major = 1.0 - step(0.5, k - mn * floor(k / mn + 0.0001))
let len = mix(self.tick_minor, self.tick_major, major)
let radial = max(rr - len - r, r - rr)
let tick = Finish.cover(perp - px * mix(0.5, 1.0, major), px) * Finish.cover(radial, px) * step(0.0, 0.0 - q.y * cos(ak) + q.x * sin(ak))
face = mix(face, self.color_scale.rgb, max(arc, tick))
if self.zone < 0.999 {
let zone_t = Finish.cover((self.zone - t) * sweep * r, px) * Finish.cover((t - 1.0) * sweep * r, px)
let zone_r = Finish.cover(max(rr + px - r, r - rr - 2.5), px)
face = mix(face, self.color_zone.rgb, zone_t * zone_r)
}
// The needle, from under the pivot cover to just past the scale.
let na = self.needle_angle(self.value)
let nd = vec2(sin(na), 0.0 - cos(na))
let along = dot(q, nd)
let across = abs(q.x * nd.y - q.y * nd.x)
let needle = Finish.cover(across - px * 0.5, px) * Finish.cover(along - (rr + 3.0), px) * Finish.cover(0.0 - along - max(self.pivot_size - px, 0.0), px)
face = mix(face, self.color_needle.rgb, needle)
let pd = r - self.pivot_size
face = mix(face, mix(self.color_needle.rgb, self.color.rgb, 0.35), Finish.cover(pd, px))
face = mix(face, face * 0.55, Finish.band(pd, 0.0, px, px))
// The housing: the surround's shadow across the face from the top, the
// black device pixel at the glass's edge, and the glass over it all.
let insh = Finish.inset(p, c, hf, rf, vec2(0.0, 2.0), 1.8)
face = face * (1.0 - 0.55 * insh)
face = mix(face, face * 0.4, Finish.band(face_d, 0.0, px, px))
let u = (p - (c - hf)) / max(hf * 2.0, vec2(1.0, 1.0))
let diag = u.x + u.y * 0.6
let band = smoothstep(0.0, 0.12, diag) * (1.0 - smoothstep(0.28, 0.5, diag))
let top = Finish.cover(abs(p.y - (c.y - hf.y) - px * 1.5) - px * 0.5, px) * 0.6
let ga = clamp(0.03 * (band + top), 0.0, 1.0) * Finish.cover(face_d + px, px)
face = face * (1.0 - ga) + vec3(ga, ga, ga)
let bz = self.color_bezel
var col = vec4(bz.rgb * bz.a, bz.a)
col = Finish.over(col, vec4(0.0, 0.0, 0.0, 0.45 * Finish.band(outer, 0.0, px, px)))
col = mix(col, vec4(face, 1.0), Finish.cover(face_d, px))
col = col * Finish.cover(outer, px)
let lip = Finish.ring_out(outer, 0.0, px, px) * step(hi.y, p.y)
let la = self.color_lip.a * lip
return Finish.over(vec4(self.color_lip.rgb * la, la), col) * self.color.a * self.opacity
}
// ---------------------------------------------------------------- lamps
// LAMPS: an LED, as the hardware in his pictures has them. The emitter is
// the light: at full current its middle burns toward white and its rim is
// its most saturated colour; at lower current the middle is no longer
// white, the colour deeper, the glow smaller; out, it is dark tinted glass,
// its colour barely there. Round it a bezel: a raised ring lit from the
// upper left, or a socket cut into the surface, its upper wall in shadow
// and a catch light on its lower rim. Over the lens one crisp highlight of
// a dome. Its light is added, never painted: a tight hot halo and a short
// bloom in its own hue, screened over whatever is there and gone in about
// two radii; on a pale ground, where light can barely brighten, its hue is
// also laid round it as a tint that keeps the ground's luminance (the
// library's halo rule: at most 0.45 one device pixel out, halving every
// 0.4 of its reach, gone at the reach), so it never darkens or greys its
// ground. A bar is the same light drawn out into a light pipe.
let bz_led_lamp_add = fn(res: vec4, light: vec3, ground: vec3) -> vec4 {
let est = res.rgb + ground * (1.0 - res.a)
let add = (vec3(1.0, 1.0, 1.0) - exp(-light)) * max(vec3(1.0, 1.0, 1.0) - est, vec3(0.0, 0.0, 0.0))
return vec4(res.rgb + add, res.a)
}
// An LED's colour at full strength: its brightest channel at one.
let bz_led_lamp_full = fn(ink: vec3) -> vec3 {
return ink / max(max(ink.x, max(ink.y, ink.z)), 0.01)
}
// The same colour at the emitter's own chroma: the hue kept and its
// saturation raised by `vivid` (0 leaves a pale colour pale; a colour with
// next to no hue, a white or a plain lamp, stays neutral either way).
let bz_led_lamp_hue = fn(ink: vec3, vivid: float) -> vec3 {
let f = bz_led_lamp_full(ink)
let mn = min(f.x, min(f.y, f.z))
let ch = 1.0 - mn
let pure = (f - vec3(mn, mn, mn)) / max(ch, 0.001)
return mix(f, pure, clamp(vivid, 0.0, 1.0) * 0.72 * smoothstep(0.03, 0.12, ch))
}
// The lamp: its lens a rounded box `hb` either side of `c` (round when the
// two are equal), `lit` its current 0..1, `ink` its colour and `vivid` how
// far its chroma is raised, `ground` what shows through where nothing is
// drawn, `room` how far its light may reach, `bez` and `raised` its bezel
// (raised 0: a recess cut in what is under it; 1: a raised ring of `bez`;
// 2: the same ring polished metal). Premultiplied over `dst`.
let bz_led_lamp = fn(dst: vec4, p: vec2, c: vec2, hb: vec2, lit: float, ink: vec3, vivid: float, px: float, ground: vec3, room: float, bez: vec3, raised: float) -> vec4 {
let rt = max(min(hb.x, hb.y), 0.4)
let d = Material.sd_box(p, c, hb, rt)
let bw = clamp(0.3 * rt, 0.7, 2.4)
let ds = d - bw
let L = clamp(lit, 0.0, 1.0)
let b = bz_led_lamp_full(ink)
let hue = bz_led_lamp_hue(ink, vivid)
let pw = L * sqrt(L)
let white = vec3(1.0, 1.0, 1.0)
let lum = vec3(0.2126, 0.7152, 0.0722)
let pale = smoothstep(0.35, 0.75, dot(ground, lum))
var res = dst
let x = max(ds, 0.0)
let reach = max(room, 1.0)
let win = 1.0 - smoothstep(0.55 * reach, reach, x)
// The lens's own frame: `q` in lens radii from its centre line, `t` its
// distance out, `nrm` the way its outline faces.
let ex = max(hb.x - hb.y, 0.0)
let ey = max(hb.y - hb.x, 0.0)
let qx = p.x - c.x
let qy = p.y - c.y
let q = vec2(qx - clamp(qx, 0.0 - ex, ex), qy - clamp(qy, 0.0 - ey, ey)) / rt
let t = clamp(length(q), 0.0, 1.0)
let nrm = q / max(length(q), 0.001)
// What the bead and its bezel throw on the ground: a soft shadow, down.
let shd = Finish.drop(p, c, hb + vec2(bw, bw), rt + bw, vec2(0.0, 0.22 * rt + 0.3), 0.32 * rt + px) * mix(0.18, 0.34, min(raised, 1.0)) * (1.0 - 0.6 * pw)
res = Finish.over(res, vec4(ground * 0.4 * shd, shd))
if L > 0.001 && pale > 0.001 {
// The tint, on a pale ground only: its hue laid over, never darker.
let h = clamp(0.42 * pw * pow(2.0, -x / max(0.4 * reach, 0.5)) * win, 0.0, 0.45) * pale
let est = res.rgb + ground * (1.0 - res.a)
let w = clamp(dot(hue, lum) / max(dot(est, lum), 0.001), 0.0, 1.0)
res = Finish.over(res, vec4(hue * h * w, h * w))
}
// The bezel: raised, a ring of `bez` rounded over, lit from the upper
// left; or a socket cut into what is under it, its upper wall in shadow
// and a catch light on its lower rim.
let a_s = Finish.cover(ds, px)
let u = clamp(d / max(bw, 0.01), 0.0, 1.0)
let facing = dot(nrm * (u - 0.5) * 2.0, vec2(-0.45, -0.89))
let metal = clamp(raised - 1.0, 0.0, 1.0)
var rc = bez * (0.94 + mix(0.12, 0.55, metal) * facing)
rc = mix(rc, min(bez + vec3(0.30, 0.30, 0.30), white), smoothstep(mix(0.35, 0.55, metal), 0.9, facing) * mix(0.6, 0.85, metal))
rc = mix(rc, rc * 0.8, Finish.band(ds, 0.0, max(px, 0.45), px) * smoothstep(0.0, 0.8, nrm.y) * 0.8)
let under = res.rgb / max(res.a, 0.001) * step(0.001, res.a) + ground * (1.0 - step(0.001, res.a))
let gy = clamp(nrm.y * t, -1.0, 1.0)
var sc = under * mix(0.55, 0.92, smoothstep(-0.9, 0.9, gy))
sc = mix(sc, min(under * 1.06 + vec3(0.28, 0.28, 0.28), white), Finish.band(ds, 0.0, max(px, 0.45), px) * smoothstep(0.2, 0.95, nrm.y) * 0.85)
sc = min(mix(sc, rc, min(raised, 1.0)), white)
res = Finish.over(res, vec4(sc * a_s, a_s))
// The light: a tight hot halo and a short bloom in its hue, gone in
// about two radii, falling on the bezel too.
if L > 0.001 {
let xl = max(d, 0.0)
let halo = exp(-xl / max(0.30 * rt, 0.4)) * 1.5 + exp(-xl / max(0.65 * rt, 0.7)) * 0.32
let hwin = 1.0 - smoothstep(1.1 * rt, 2.0 * rt + 1.0, xl)
res = bz_led_lamp_add(res, mix(hue, white, 0.15 * pw) * halo * pw * hwin * win, ground)
}
// THE BEAD: a glossy dome of coloured glass. Its colour deepens toward
// the top; light pools low inside, where it comes through the dome; lit,
// its middle burns toward white at full current and its rim is its most
// saturated colour.
let top = smoothstep(-1.0, 0.6, q.y)
let pool = exp(-(q.y - 0.55) * (q.y - 0.55) / 0.09 - q.x * q.x / 0.30)
var on = mix(hue * mix(0.55, 0.8, L), mix(b, hue, 0.5), top)
on = mix(on, min(mix(b, hue, 0.4) + vec3(0.35, 0.35, 0.35), white), pool * 0.5)
on = mix(on, vec3(1.0, 0.99, 0.975), exp(-t * t / 0.20) * smoothstep(0.5, 1.0, L) * 0.85)
on = mix(on, hue * (0.82 + 0.18 * L), smoothstep(0.62, 1.0, t))
on = on * (0.42 + 0.58 * L)
// Out: dark tinted glass on a dark ground, a grey bead on a pale one,
// its colour barely there, the same dome, a catch light along its lower
// rim.
let offc = mix(mix(vec3(0.085, 0.09, 0.115), ground * 0.62, pale), b * 0.4, 0.12)
var off = mix(offc * 0.62, offc * 1.08, top)
off = mix(off, min(offc + vec3(0.18, 0.18, 0.18), white), pool * 0.35)
off = off * mix(1.0, 0.75, smoothstep(0.75, 1.0, t))
let catchl = smoothstep(0.62, 0.9, t) * (1.0 - smoothstep(0.9, 1.0, t)) * smoothstep(0.2, 0.9, nrm.y)
off = off + vec3(0.30, 0.30, 0.32) * catchl * (1.0 - 0.5 * pale)
var col = mix(off, min(on, white), smoothstep(0.0, 0.35, L))
// One crisp highlight: a white oval across its upper third.
let e = length(vec2(q.x / 0.62, (q.y + 0.46) / 0.30))
let spec = (1.0 - smoothstep(0.55, 1.0, e)) * mix(1.0, 0.3, clamp((q.y + 0.76) / 0.6, 0.0, 1.0))
col = mix(col, white, spec * mix(0.8, 0.55, L) * smoothstep(0.9, 2.2, rt))
// Out, a faint seam where the dome meets its bezel; lit, none: the rim
// is the colour.
col = mix(col, col * 0.7, Finish.band(d, 0.0, px * 0.6, px) * 0.6 * (1.0 - smoothstep(0.0, 0.25, L)))
col = min(col, white)
let a_l = Finish.cover(d, px)
return Finish.over(res, vec4(col * a_l, a_l))
}
// The Lamp widget and the bar lamp: the lamp above, here in a polished brass ring.
// The halo's room is held in the lamp's own box (the library reserves a
// lens thickness either side once a halo is asked for), so the light is
// never cut at its edge.
mod.widgets.Lamp.halo = 0.3
mod.widgets.Lamp.halo_reach = 1.0
mod.widgets.Lamp.draw_bg.vertex = fn() {
let m = 2.0
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
mod.widgets.Lamp.draw_bg.pixel = fn() {
let m = 2.0
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let reach = clamp(self.reach, 0.0, 1.0)
let t = self.rect_size.y / (1.0 + 2.0 * reach)
let room = t * reach
let c = self.rect_size * 0.5
let half_len = max((self.rect_size.x - 2.0 * room) * 0.5, t * 0.5)
// The lens is the lamp's size; its socket stands round it in the room.
let hb = vec2(mix(t * 0.5, half_len, self.bar), t * 0.5)
let res = bz_led_lamp(vec4(0.0, 0.0, 0.0, 0.0), p, c, hb, self.lit, self.intent_color().rgb, 1.0 - step(3.5, self.intent), px, vec3(0.231, 0.204, 0.169), max(room - 0.5, 2.0), vec3(0.74, 0.56, 0.28), 2.0)
return res * self.opacity
}
true