makepad/widgets/sheets/themes/safety-yellow/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

2567 lines
132 KiB
Text

use mod.prelude.widgets_internal.*
// Worn safety-yellow painted steel. The plate is paint over steel: mottled,
// scratched, chipped to the metal at a few places and grimed toward its
// edges. Every control stands on it in black rubber or dark gunmetal, with a
// warm contact shadow and a short soft one under it; wells are charcoal cut
// into the paint with a paler lip under their foot; knobs are spun chrome in
// knurled rubber skirts; surfaces are yellow plates in black rubber frames
// held by screws. The one lit colour is a sky blue: a lamp, a value line, a
// lit legend, the focus mouth. Every rim is one device pixel, snapped.
mod.widgets.Button.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 3.0 bottom: 3.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 26.
let field_height = 26.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 = #dcaa38
// 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 sy_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
}
// A segment's distance.
let sy_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 sy_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(sy_seg(p, a, b), sy_seg(p, b, e))
}
// A disc laid on the pixel grid: a whole number of device pixels across,
// its edge on a pixel boundary. `c` is quad-local; returns (x, y, radius).
let sy_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)
}
// The one lit colour: the lamp's core, the value line's core, its light and
// dark rims.
let sy_acc = fn() -> vec3 {
return vec3(0.337, 0.722, 0.941)
}
let sy_fill = fn() -> vec3 {
return vec3(0.227, 0.604, 0.847)
}
let sy_rim_hi = fn() -> vec3 {
return vec3(0.580, 0.827, 0.965)
}
let sy_rim_lo = fn() -> vec3 {
return vec3(0.129, 0.380, 0.580)
}
// The lit colour as disabled parts show it: an unlit grey.
let sy_off = fn() -> vec3 {
return vec3(0.200, 0.196, 0.188)
}
// Black rubber.
let sy_bezel = fn() -> vec3 {
return vec3(0.071, 0.067, 0.059)
}
// Shadows on the paint are a warm brown-black.
let sy_shadow_ink = fn() -> vec3 {
return vec3(0.110, 0.055, 0.004)
}
// Signed grain, -amount..amount with a triangular spread, one value per
// device pixel (pass `p / px`); two hashes whose lattices are displaced by
// a first one, so no shift lines them up again.
let sy_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
}
// One scratch per cell, in about a third of the cells: a straight stroke at
// a hashed angle, tapered at both ends. Returns (groove, scuff), each 0..1.
let sy_scratch = fn(q: vec2, cell: float, seed: float, px: float) -> vec2 {
let ci = floor(q / cell)
let h1 = Finish.hash(ci + vec2(seed, seed * 0.37))
let h2 = Finish.hash(ci + vec2(h1 * 91.3 + seed, 17.1))
let h3 = Finish.hash(ci + vec2(33.7, h2 * 71.9 + seed))
let h4 = Finish.hash(ci + vec2(h3 * 53.1, h1 * 29.3))
let on = step(0.74, h1)
let ang = (h2 - 0.5) * 2.6
let dir = vec2(cos(ang), sin(ang))
let len = cell * (0.12 + 0.22 * h3)
let c = (ci + vec2(0.5, 0.5) + (vec2(h4, h3) - vec2(0.5, 0.5)) * 0.25) * cell
let ts = clamp(dot(q - c, dir) / len, -1.0, 1.0)
let d = length(q - c - dir * ts * len)
let taper = 1.0 - ts * ts
let groove = (1.0 - smoothstep(0.0, 0.18 * taper + px * 0.5, d)) * taper
let scuff = exp(-d / 0.7) * taper
return vec2(groove, scuff) * on
}
// The paint, in window coordinates `q`: a slow mottle, rubbed lighter
// patches, scratches through to a dark groove with a pale scuff beside
// them, a few chips down to steel with a rust rim, and the grain. No fall
// and no grime: those belong to the whole window.
let sy_paint = fn(q: vec2, px: float) -> vec3 {
var col = vec3(0.863, 0.667, 0.220)
let m1 = Finish.fbm(q / 130.0 + vec2(3.7, 1.3)) - 0.5
let m2 = Finish.fbm(q / 26.0 + vec2(11.1, 5.9)) - 0.5
col = col * (1.0 + 0.16 * m1 + 0.07 * m2)
// rubbed paint: paler and a little more saturated where the mottle peaks
col = mix(col, vec3(0.925, 0.745, 0.290), smoothstep(0.10, 0.28, m1) * 0.35)
// small chips down to the steel, in a few clusters: a slow field
// decides where paint is tired, a fine one cuts the flakes
let n = Finish.fbm(q / 9.0 + vec2(21.3, 7.7))
let field = Finish.fbm(q / 210.0 + vec2(5.0, 13.0))
let th = 0.90 - 0.15 * smoothstep(0.62, 0.76, field)
let chip = smoothstep(th, th + 0.02, n)
let rim = smoothstep(th - 0.06, th, n) * (1.0 - chip)
let deep = smoothstep(th + 0.03, th + 0.08, n)
var steel = mix(vec3(0.620, 0.470, 0.180), vec3(0.540, 0.370, 0.150), Finish.fbm(q / 5.0 + vec2(2.0, 9.0)))
steel = mix(steel, vec3(0.380, 0.260, 0.120), deep * 0.85)
col = mix(col, col * vec3(0.86, 0.78, 0.64), rim * 0.55)
col = mix(col, steel, chip * 0.9)
// grime: soft brown smudges where a middle field and a fine one agree
let gm = Finish.fbm(q / 48.0 + vec2(8.1, 2.2))
let gf = Finish.fbm(q / 6.0 + vec2(1.7, 4.4))
let grime = smoothstep(0.58, 0.72, gm) * smoothstep(0.45, 0.70, gf)
col = mix(col, col * vec3(0.72, 0.62, 0.46), grime * 0.45)
// the paint's own skin: a fine orange peel
col = col * (1.0 + 0.035 * (Finish.noise(q / 1.6 + vec2(3.3, 9.1)) - 0.5))
// scratches, two grids of cells offset from each other: a thin dark
// groove, a pale scuff beside it
let s1 = sy_scratch(q, 52.0, 0.0, px)
let s2 = sy_scratch(q + vec2(19.3, 31.7), 83.0, 7.3, px)
let groove = max(s1.x, s2.x)
let scuff = max(s1.y, s2.y) * (1.0 - chip)
col = mix(col, vec3(0.965, 0.847, 0.510), scuff * 0.16)
col = mix(col, vec3(0.420, 0.320, 0.170), groove * 0.35)
// dirt specks, one in a few hundred pixels
let i = floor(q / px)
let sp = step(0.9975, Finish.hash(vec2(i.x + 71.3, i.y + 19.7 + Finish.hash(i) * 613.7)))
col = mix(col, vec3(0.420, 0.300, 0.120), sp * 0.35)
return col + g3(sy_grain(q / px, 0.020))
}
// The window's ground: the paint, lit from the top, grimed toward the edges.
let sy_ground = fn(p: vec2, size: vec2, px: float) -> vec3 {
var col = sy_paint(p, px)
let t = clamp(p.y / max(size.y, 1.0), 0.0, 1.0)
col = col * mix(1.03, 0.93, t)
let e = min(min(p.x, size.x - p.x), min(p.y, size.y - p.y))
let grime = (1.0 - smoothstep(0.0, 90.0, e)) * (0.4 + 0.8 * Finish.fbm(p / 24.0 + vec2(1.0, 3.0)))
return mix(col, vec3(0.380, 0.270, 0.090), clamp(grime, 0.0, 1.0) * 0.35)
}
// The face of a raised gunmetal cap by height t 0..1: lit at the top,
// falling 2:1 to its foot.
let sy_face = fn(t: float) -> vec3 {
let top = vec3(0.330, 0.330, 0.325)
let mid = vec3(0.262, 0.262, 0.260)
let foot = vec3(0.180, 0.180, 0.178)
return mix(mix(top, mid, clamp(t * 2.0, 0.0, 1.0)), foot, clamp(t * 2.0 - 1.0, 0.0, 1.0))
}
// The rubber stipple on a cap: a fine diamond knurl, a few levels deep.
let sy_knurl = fn(q: vec2, px: float) -> float {
let k = q / px
return (sin((k.x + k.y) * 2.1) + sin((k.x - k.y) * 2.1)) * 0.25
}
// An LED dash: `lit` 0 shows the unlit bead `off` in its slot, 1 the lit
// core, white-hot over its middle third, with a tight halo.
let sy_dash = fn(p: vec2, c: vec2, hw: float, hh: float, px: float, ink: vec3, lit: float, off: vec3) -> vec4 {
let d = sy_rbox(p, c, vec2(hw, hh), hh)
let a = Finish.cover(d, px)
let across = abs(p.y - c.y) / max(hh, 0.001)
let hot = 1.0 - smoothstep(0.15, 0.6, across)
let core = mix(ink, vec3(1.0, 1.0, 1.0), 0.55 * hot)
let col = mix(off, core, lit)
var out = vec4(col * a, a)
let ha = 0.30 * lit * exp(-max(d - px, 0.0) / (0.8 * px)) * (1.0 - a) * step(0.0, d)
return Finish.over(vec4(ink * ha, ha), out)
}
// A lamp bead: a round lens, lit with a white-hot centre and a tight halo,
// or unlit at a third of its light.
let sy_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.6), ink * 0.85, smoothstep(0.0, 1.0, rr))
let off = mix(ink * 0.30, ink * 0.18, rr) + g3(0.03)
var col = mix(off, on, lit)
col = mix(col, sy_bezel(), Finish.band(d, 0.0, px, px) * 0.6 * (1.0 - lit))
var out = vec4(col * a, a)
let ha = 0.32 * lit * exp(-max(d - px, 0.0) / (0.9 * px)) * (1.0 - a) * step(0.0, d)
return Finish.over(vec4(ink * ha, ha), out)
}
// A well cut into the paint: the fill, the plate's shadow falling in from
// the top wall, a dark mouth darkest on top. Its lip is `sy_lip`.
let sy_well = fn(p: vec2, c: vec2, h: vec2, r: float, px: float, fill: vec3, depth: float) -> vec3 {
let d = sy_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.047, 0.043, 0.039), 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.063, 0.043, 0.020), Finish.band(d, 0.0, px, px) * mix(0.55, 0.95, up))
return col
}
// The lip under a well's lower rim, outside it on the paint, confined to
// the foot and the lower corners: a pale catch of light, premultiplied.
let sy_lip = fn(p: vec2, c: vec2, h: vec2, r: float, px: float, a: float) -> vec4 {
let d = sy_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
let k = vec3(1.0, 0.925, 0.690)
return vec4(k * l, l)
}
// Focus: one device pixel of the lit blue, two device pixels out.
let sy_focus = fn(d: float, px: float, focus: float) -> vec4 {
let f = Finish.ring_out(d, 2.0 * px, 3.0 * px, px) * 0.9 * focus
let k = vec3(0.110, 0.420, 0.700)
return vec4(k * f, f)
}
// Spun chrome: a conic sheen, two bright lobes top and bottom, two dark at
// the sides, over lathe rings one device pixel apart. Returns a luminance.
let sy_conic = fn(q: vec2, px: float, lo: float, hi: float, turn: float) -> float {
let r = max(length(q), 0.0001)
let cs = q.x / r
let sn = q.y / r
let c1 = cs * cos(turn) - sn * sin(turn)
let s1 = sn * cos(turn) + cs * sin(turn)
let c2 = s1 * s1 - c1 * c1
let k = c2 * 0.5 + 0.5
var l = mix(lo, hi, k * k * (3.0 - 2.0 * k))
let rings = Finish.noise(vec2(r / px, 0.37)) - 0.5
l = l + rings * 0.03 * smoothstep(0.0, 2.0, r)
l = l + 0.05 * (1.0 - smoothstep(0.0, px * 1.2, r))
return l
}
// A small chrome disc standing on the paint: contact ring, a warm cast
// shadow, a bright chamfer, the spun face.
let sy_disc = fn(p: vec2, c: vec2, rad: float, px: float, hover: float, press: float, dis: float) -> vec4 {
let hh = vec2(rad, rad)
let dia = 2.0 * rad
var out = vec4(0.0, 0.0, 0.0, 0.0)
let cast = Finish.drop(p, c, hh, rad, vec2(dia * 0.05, dia * 0.10), dia * 0.12) * 0.65 * (1.0 - 0.5 * press) * mix(1.0, 0.4, dis)
let ink = sy_shadow_ink()
out = Finish.over(out, vec4(ink * cast, cast))
let q = p - c
let d = length(q) - rad
let con = Finish.ring_out(d, 0.0, px, px) * 0.7
out = Finish.over(out, vec4(sy_bezel() * con, con))
var face = g3(sy_conic(q, px, 0.42, 0.86, 0.12 * hover))
face = face * vec3(1.0, 0.99, 0.97)
face = face + g3(0.07 * hover - 0.06 * press)
face = mix(face, mix(g3(0.36), face * 0.5, 0.5), dis)
let t = clamp(q.y / rad * 0.5 + 0.5, 0.0, 1.0)
let ch = mix(vec3(0.80, 0.80, 0.79), vec3(0.97, 0.97, 0.96), smoothstep(0.35, 1.0, t))
face = mix(face, vec3(0.30, 0.30, 0.30), Finish.band(d, 2.0 * px, 3.0 * px, px) * 0.9)
face = mix(face, mix(ch, g3(0.38), dis), Finish.band(d, 0.0, 2.0 * px, px) * 0.9)
face = mix(face, vec3(0.20, 0.20, 0.20), Finish.band(d, 0.0, px, px) * 0.7)
let a = Finish.cover(d, px)
return Finish.over(out, vec4(face * a, a))
}
// A ribbed rubber block with chrome ends, the thumb of a bar slider and the
// cap of a fader. `c` its centre, `h` its half size; `along` 1 when its ribs
// run across x (a horizontal slider), 0 when they run across y.
let sy_block = fn(p: vec2, c: vec2, h: vec2, px: float, horiz: float, hover: float, press: float, dis: float, lit: float) -> vec4 {
let ink = sy_shadow_ink()
var out = vec4(0.0, 0.0, 0.0, 0.0)
let r = 1.8
let cast = Finish.drop(p, c, h, r, vec2(0.4, 1.6), 1.4) * 0.60 * (1.0 - 0.4 * press) * mix(1.0, 0.4, dis)
out = Finish.over(out, vec4(ink * cast, cast))
let con = Finish.drop(p, c, h, r, vec2(0.0, px), px) * 0.7
out = Finish.over(out, vec4(ink * con, con))
let d = sy_rbox(p, c, h, r)
// u along the block's length 0..1, v across it 0..1 (top to bottom)
let u = mix((c.y + h.y - p.y) / max(2.0 * h.y, 1.0), (p.x - (c.x - h.x)) / max(2.0 * h.x, 1.0), horiz)
let vv = clamp((p.y - (c.y - h.y)) / max(2.0 * h.y, 1.0), 0.0, 1.0)
let len = mix(2.0 * h.y, 2.0 * h.x, horiz)
let endw = min(3.0, len * 0.18) / max(len, 1.0)
let is_end = 1.0 - step(endw, u) * step(u, 1.0 - endw)
// chrome ends: a cylinder lit from above
var chrome = mix(vec3(0.93, 0.93, 0.92), vec3(0.36, 0.36, 0.36), smoothstep(0.1, 0.95, vv))
chrome = mix(chrome, vec3(0.99, 0.99, 0.98), (1.0 - smoothstep(0.08, 0.30, vv)) * 0.6)
// rubber: ribs across the length, one rib per three device pixels
let along = mix(p.y, p.x, horiz) / px
let rib = 0.5 + 0.5 * sin(along * 2.094)
var rubber = mix(vec3(0.060, 0.058, 0.055), vec3(0.230, 0.228, 0.222), rib)
rubber = rubber * mix(1.25, 0.70, vv)
rubber = rubber + g3(0.10 * hover * (1.0 - press) - 0.04 * press)
var face = mix(rubber, chrome, is_end)
face = mix(face, mix(g3(0.30), face * 0.6, 0.5), dis)
// the held block shows a lit blue line down its middle
let mid = abs(u - 0.5) * len
let ln = (1.0 - smoothstep(px * 0.5, px * 1.5, mid)) * lit * (1.0 - is_end)
face = mix(face, sy_acc(), ln)
face = mix(face, sy_bezel(), Finish.band(d, 0.0, px, px) * 0.8)
let a = Finish.cover(d, px)
return Finish.over(out, vec4(face * a, a))
}
// A steel screw head in the paint: a dark socket ring, a domed head spun
// round its centre, a cross slot at a hashed turn.
let sy_screw = fn(p: vec2, c: vec2, rad: float, px: float, turn: float) -> vec4 {
let ink = sy_shadow_ink()
var out = vec4(0.0, 0.0, 0.0, 0.0)
let q = p - c
let d = length(q) - rad
let cast = Finish.drop(p, c, vec2(rad, rad), rad, vec2(0.3, 0.8), 0.8) * 0.55
out = Finish.over(out, vec4(ink * cast, cast))
let sock = Finish.ring_out(d, 0.0, 1.5 * px, px) * 0.7
out = Finish.over(out, vec4(sy_bezel() * sock, sock))
var face = g3(sy_conic(q, px, 0.38, 0.78, turn))
// domed: lighter toward the upper left, darker toward the lower right
face = face * (1.08 - 0.30 * clamp(dot(q / max(rad, 0.1), vec2(0.45, 0.85)) * 0.5 + 0.5, 0.0, 1.0))
face = face * vec3(1.0, 0.98, 0.94)
// the slot: a cross, a dark stroke with a lit edge below it
let cs = cos(turn)
let sn = sin(turn)
let a1 = vec2(cs, sn) * rad * 0.62
let a2 = vec2(-sn, cs) * rad * 0.62
let sl = min(sy_seg(p, c - a1, c + a1), sy_seg(p, c - a2, c + a2)) - max(rad * 0.13, px * 0.6)
let sl_lit = min(sy_seg(p - vec2(0.0, px), c - a1, c + a1), sy_seg(p - vec2(0.0, px), c - a2, c + a2)) - max(rad * 0.13, px * 0.6)
face = mix(face, g3(0.85), Finish.cover(sl_lit, px) * 0.5)
face = mix(face, g3(0.10), Finish.cover(sl, px))
face = mix(face, g3(0.18), Finish.band(d, 0.0, px, px) * 0.8)
let a = Finish.cover(d, px)
return Finish.over(out, vec4(face * a, a))
}
// ---------------------------------------------------------------------------
// BUTTONS. A rubber keycap: a black rubber base two device pixels wide, and
// on it the gunmetal cap, knurled, lit along its top row, a dark row at its
// foot; a warm contact shadow and a short soft one on the paint. `kind`
// picks the dressing: 0 flat plate (no soft layer), 1 raised, 2 raised with
// a lit dash (primary), 3 raised with the dash unlit (secondary), 4 raised
// with a red dash (danger), 5 an engraved outline on the paint, 6 bare
// until touched, 7 a brushed steel pad. The quad is grown by `m`.
let sy_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 = sy_rbox(p, c, h, r)
let bare = step(5.5, kind) * step(kind, 6.5)
let outl = step(4.5, kind) * step(kind, 5.5)
let pad = step(6.5, kind)
let solid = 1.0 - bare - outl
let bez = 3.0 * px
let soft_on = step(0.5, kind) * solid
var out = vec4(0.0, 0.0, 0.0, 0.0)
let ink = sy_shadow_ink()
// the soft layer: a little right, mostly below, gone within half of
// the eight point gap to the next row
let soft = Finish.drop(p, c, h, r, vec2(0.3, 1.6), 1.3) * 0.55 * soft_on * (1.0 - press) * live
out = Finish.over(out, vec4(ink * soft, soft))
// the contact line right under the edge
let con = Finish.drop(p, c, h, r, vec2(0.0, px), px * 1.2) * 0.75 * solid * mix(1.0, 0.35, press) * mix(1.0, 0.5, disabled)
out = Finish.over(out, vec4(ink * con, con))
// the cap: its top row one device pixel lower while pressed
let sink = press * px
let t = clamp((p.y - lo.y - sink) / max(2.0 * h.y, 1.0), 0.0, 1.0)
let yi = p.y - lo.y - bez - sink
var face = sy_face(t)
if pad > 0.5 {
face = mix(vec3(0.420, 0.420, 0.412), vec3(0.310, 0.310, 0.306), t)
face = face + g3((Finish.brushed(p + o, vec2(1.0, 0.0), px * 1.2) - 0.5) * 0.05)
}
face = face + g3(sy_knurl(p + o, px) * 0.035 * (1.0 - pad))
// a soft sheen over the cap's upper third, the rubber's satin
face = face + g3(0.05 * (1.0 - smoothstep(0.05, 0.40, t)) * (1.0 - press))
face = face + g3(0.065 * hov)
// pressed: a darker cap with the base's shadow inside its top
let sunk = mix(vec3(0.035, 0.033, 0.030), vec3(0.120, 0.120, 0.117), smoothstep(0.0, 3.0 * px + 1.5, yi))
face = mix(face, sunk + g3(sy_knurl(p + o, px) * 0.02), press)
face = mix(face, vec3(0.290, 0.282, 0.263), disabled)
face = face + g3(sy_grain((p + o) / px, 0.014))
// the top highlight: one device pixel, then a short ramp into the face
let topw = 1.0 - smoothstep(lo.y + bez + r + 0.5, lo.y + bez + r + 1.5, p.y)
let botw = smoothstep(hi.y - bez - r - 1.5, hi.y - bez - r - 0.5, p.y)
let hl_row = Finish.band(d, bez, bez + px, px) * topw
let ramp = (1.0 - smoothstep(0.0, 2.0, yi - px)) * step(0.0, yi) * 0.30
let hl = mix(vec3(0.420, 0.416, 0.400), vec3(0.560, 0.556, 0.540), hov)
let lit_top = max(hl_row, ramp) * (1.0 - press) * live
face = mix(face, mix(hl, vec3(0.62, 0.62, 0.60), pad * 0.6), lit_top)
// the foot line
face = mix(face, vec3(0.043, 0.039, 0.035), Finish.band(d, bez, bez + px, px) * botw * (1.0 - press))
// the rubber base, its outer row catching a warm light on top
let up = 1.0 - smoothstep(-0.5, 0.5, p.y - c.y)
var base = sy_bezel()
base = mix(base, vec3(0.290, 0.220, 0.120), Finish.band(d, 0.0, px, px) * up * 0.6 * live)
face = mix(face, base, Finish.band(d, 0.0, bez, px))
// the dash of the primary, secondary and danger faces
if kind > 1.5 && kind < 4.5 {
let hw = clamp(h.x * 0.42, 5.0, 14.0)
let dy = Finish.snap(o.y + hi.y - bez - 3.0, px) - o.y + sink
let is_red = step(3.5, kind)
let lit = step(kind, 2.5) * live + is_red * live
let dink = mix(sy_acc(), vec3(0.980, 0.380, 0.300), is_red)
let doff = vec3(0.071, 0.067, 0.063)
let dash = sy_dash(p, vec2(c.x, dy + px), hw, px, px, mix(dink, sy_off(), disabled), lit, mix(doff, vec3(0.20, 0.20, 0.19), disabled))
face = face * (1.0 - dash.a) + dash.rgb
}
var fa = 1.0
if kind > 4.5 && kind < 6.5 {
fa = 0.0
}
let a = Finish.cover(d, px) * fa
out = Finish.over(out, vec4(face * a, a))
// the outline and the bare face: a shade on the paint; the outline
// is an engraved groove, dark over a pale lip
if kind > 4.5 && kind < 6.5 {
let sh = Finish.cover(d, px) * ((0.08 * hov + 0.16 * press) + outl * 0.04)
out = Finish.over(out, vec4(ink * sh, sh))
let dk = Finish.band(d, 0.0, 1.5 * px, px) * mix(0.9, 0.45, disabled) * mix(outl, hov + press, bare)
out = Finish.over(out, vec4(sy_bezel() * dk, dk))
let low = smoothstep(-0.5, 0.5, p.y - c.y)
let li = Finish.ring_out(d, 0.0, px, px) * low * 0.35 * live * outl
out = Finish.over(out, vec4(vec3(1.0, 0.925, 0.690) * li, li))
}
out = Finish.over(out, sy_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 rr = max(min(h.x, h.y) - 5.0, 2.0)
let a0 = anim * 2.0 * PI
sdf.arc_round_caps(c.x, c.y, rr, a0, a0 + 1.5 * PI, 1.2)
let k = sy_acc()
sdf.fill(vec4(k * loading, loading))
out = Finish.over(out, sdf.result)
}
return out
}
// The drop-down face: the cap with an engraved split 22 points from its
// right end and a stroked chevron behind it; `held` is pressed.
let sy_dropdown = fn(pos: vec2, size: vec2, o: vec2, px: float, hover: float, held: float, focus: float, disabled: float, m: float) -> vec4 {
var out = sy_button(pos, size, o, px, hover, held, focus, disabled, 0.0, 0.0, 2.0, 1.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.2, p.y) * step(p.y, size.y * 0.8)
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.7 * (1.0 - held)
let bz = sy_bezel()
out = Finish.over(out, vec4(bz * div_d, div_d))
let hl = vec3(0.380, 0.376, 0.365)
out = Finish.over(out, vec4(hl * div_l, div_l))
let cc = vec2(size.x - 11.0, Finish.snap(o.y + size.y * 0.5, px) - o.y + held * live * px)
let ch = Finish.cover(sy_chevron(p, cc, 6.0, 3.5, 0.0) - 0.65, px)
let ink = mix(mix(vec3(0.84, 0.83, 0.80), vec3(0.95, 0.94, 0.92), hover * live), vec3(0.56, 0.55, 0.51), 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 raised 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(sy_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(sy_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, 6.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(sy_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, 6.0), 6.0)
}
mod.widgets.ButtonPrimary.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.ButtonPrimary.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return self.host_over(sy_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, 6.0), 6.0)
}
mod.widgets.ButtonSecondary.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return self.host_over(sy_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, 6.0), 6.0)
}
mod.widgets.ButtonTertiary.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return self.host_over(sy_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, 6.0), 6.0)
}
mod.widgets.ButtonDanger.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return self.host_over(sy_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, 6.0), 6.0)
}
mod.widgets.ButtonOutline.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.ButtonOutline.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return self.host_over(sy_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, 6.0), 6.0)
}
mod.widgets.ButtonXs.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.ButtonXs.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return self.host_over(sy_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, 1.0, 6.0), 6.0)
}
mod.widgets.ButtonSm.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.ButtonSm.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return self.host_over(sy_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, 6.0), 6.0)
}
mod.widgets.ButtonLg.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.ButtonLg.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return self.host_over(sy_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, 6.0), 6.0)
}
mod.widgets.ButtonXl.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.ButtonXl.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return self.host_over(sy_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, 6.0), 6.0)
}
mod.widgets.ButtonCompound.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.ButtonCompound.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return self.host_over(sy_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, 6.0), 6.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: split and chevron.
mod.widgets.Select.face.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.Select.face.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return sy_dropdown(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, 6.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: 3.0 bottom: 3.0}
mod.widgets.Select.face.min_height = 26.0
mod.widgets.MultiSelect.face.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.MultiSelect.face.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return sy_dropdown(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, 6.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: 3.0 bottom: 3.0}
mod.widgets.MultiSelect.face.min_height = 26.0
// Button geometry: one 26 point row, a 2 point radius.
mod.widgets.ButtonFlat.padding = mod.turtle.Inset{left: 10.0 right: 10.0 top: 3.0 bottom: 3.0}
mod.widgets.Button.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 3.0 bottom: 3.0}
mod.widgets.ButtonPrimary.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 3.0 bottom: 3.0}
mod.widgets.ButtonTertiary.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 3.0 bottom: 3.0}
mod.widgets.ButtonOutline.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 3.0 bottom: 3.0}
mod.widgets.ButtonIcon.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 3.0 bottom: 3.0}
mod.widgets.Button.icon_walk = mod.turtle.Walk{width: 13.0 height: Fit}
mod.widgets.ButtonFlat.min_height = 26.0
mod.widgets.Button.min_height = 26.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 = 30.0
// Legends: semibold light grey on the caps; the primary legend is the lit
// blue, the danger legend a red; the outline and the bare face stand on the
// paint and carry the paint's dark ink.
mod.widgets.ButtonFlat.draw_text.text_style = mod.theme.font_bold{font_size: 8.25}
mod.widgets.ButtonFlat.draw_bg.loading_color = #56b8f0
mod.widgets.ButtonFlat.draw_icon_end.color = #e4e2dc
mod.widgets.ButtonPrimary.draw_text.color = #7ccaf6
mod.widgets.ButtonPrimary.draw_text.color_hover = #a4dcfa
mod.widgets.ButtonPrimary.draw_text.color_down = #5aa8d8
mod.widgets.ButtonPrimary.draw_text.color_focus = #7ccaf6
mod.widgets.ButtonPrimary.draw_text.color_disabled = #8e8b82
mod.widgets.ButtonPrimary.draw_icon.color = #7ccaf6
mod.widgets.ButtonPrimary.draw_icon_end.color = #7ccaf6
mod.widgets.ButtonPrimary.draw_text.text_style = mod.theme.font_bold{font_size: 8.25}
mod.widgets.ButtonSecondary.draw_text.color = #e4e2dc
mod.widgets.ButtonSecondary.draw_text.color_hover = #f4f2ec
mod.widgets.ButtonSecondary.draw_text.color_down = #c8c6c0
mod.widgets.ButtonSecondary.draw_text.color_focus = #e4e2dc
mod.widgets.ButtonSecondary.draw_icon.color = #e4e2dc
mod.widgets.ButtonTertiary.draw_text.color = #f2f0ea
mod.widgets.ButtonTertiary.draw_text.color_hover = #ffffff
mod.widgets.ButtonTertiary.draw_text.color_down = #d8d6d0
mod.widgets.ButtonTertiary.draw_text.color_focus = #f2f0ea
mod.widgets.ButtonTertiary.draw_icon.color = #f2f0ea
mod.widgets.ButtonDanger.draw_text.color = #ff9a8a
mod.widgets.ButtonDanger.draw_text.color_hover = #ffb8ac
mod.widgets.ButtonDanger.draw_text.color_down = #d87a6c
mod.widgets.ButtonDanger.draw_text.color_focus = #ff9a8a
mod.widgets.ButtonDanger.draw_icon.color = #ff9a8a
mod.widgets.ButtonOutline.draw_text.color = #1c1a14
mod.widgets.ButtonOutline.draw_text.color_hover = #0e0d0a
mod.widgets.ButtonOutline.draw_text.color_down = #0e0d0a
mod.widgets.ButtonOutline.draw_text.color_focus = #1c1a14
mod.widgets.ButtonOutline.draw_text.color_disabled = #7a6630
mod.widgets.ButtonOutline.draw_icon.color = #1c1a14
mod.widgets.ButtonFlatter.draw_text.color = #1c1a14
mod.widgets.ButtonFlatter.draw_text.color_hover = #0e0d0a
mod.widgets.ButtonFlatter.draw_text.color_down = #0e0d0a
mod.widgets.ButtonFlatter.draw_text.color_focus = #1c1a14
mod.widgets.ButtonFlatter.draw_text.color_disabled = #7a6630
mod.widgets.ButtonFlatter.draw_icon.color = #1c1a14
mod.widgets.ButtonFlatterIcon.draw_icon.color = #1c1a14
mod.widgets.ButtonCompound.draw_description.color = #b4b2aa
// A link stands on the paint: a deep blue legend over its hairline.
mod.widgets.LinkLabel.draw_text.color = #163c62
mod.widgets.LinkLabel.draw_text.color_hover = #0c2640
mod.widgets.LinkLabel.draw_text.color_down = #0c2640
mod.widgets.LinkLabel.draw_text.color_focus = #163c62
mod.widgets.LinkLabel.draw_text.color_disabled = #7a6630
mod.widgets.LinkLabel.draw_bg.color = #163c62
mod.widgets.LinkLabel.draw_bg.color_hover = #0c2640
mod.widgets.LinkLabel.draw_bg.color_down = #0c2640
mod.widgets.LinkLabel.draw_bg.color_focus = #163c62
mod.widgets.LinkLabel.draw_bg.color_disabled = #7a6630
// ---------------------------------------------------------------------------
// CHECK BOXES. A 13 point rubber well cut into the paint, a pale lip under
// it; the tick is lit blue, white-hot along its middle.
mod.widgets.CheckBoxFlat.padding = mod.turtle.Inset{left: 0.0 right: 3.0 top: 6.0 bottom: 6.0}
mod.widgets.CheckBoxFlat.label_walk.margin = mod.turtle.Inset{left: 21.0 right: 3.0 top: 0.0 bottom: 0.0}
mod.widgets.CheckBoxFlat.draw_bg.size = uniform(13.0)
mod.widgets.CheckBoxFlat.min_height = 26.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 = sy_rbox(p, c, h, r)
let live = 1.0 - self.disabled
var fill = mix(vec3(0.125, 0.122, 0.118), vec3(0.310, 0.290, 0.240), self.disabled)
fill = fill + g3(0.11 * self.hover * live * (1.0 - self.down) - 0.07 * self.down * live)
var col = sy_well(p, c, h, r, px, fill, (2.2 + 2.4 * self.down) * (1.0 - 0.7 * self.disabled))
col = mix(col, vec3(0.920, 0.300, 0.220), Finish.band(d, 0.0, px, px) * self.error)
// the tick, or the mixed dash, 1.8 points
let k = sz / 13.0
let m0 = lo + vec2(3.3, 6.8) * k
let m1 = lo + vec2(5.6, 9.1) * k
let m2 = lo + vec2(9.8, 4.1) * k
let ck = min(sy_seg(p, m0, m1), sy_seg(p, m1, m2)) - 0.95 * k
let dash = sy_seg(p, lo + vec2(3.4, 6.5) * k, lo + vec2(9.6, 6.5) * 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)
let core = 1.0 - smoothstep(-0.95 * k, -0.2 * k, md)
var tick = mix(sy_acc(), vec3(0.93, 0.97, 1.0), core * 0.7)
tick = mix(tick, vec3(0.52, 0.50, 0.46), self.disabled)
// the tick's light on the well's floor
let glow = exp(-max(md, 0.0) / (1.3 * px)) * 0.35 * (1.0 - mk) * max(show_ck, self.mixed) * live
col = mix(col, sy_acc() * 0.7, glow)
col = mix(col, tick, mk)
let a = Finish.cover(d, px)
var out = sy_lip(p, c, h, r, px, 0.45)
out = Finish.over(out, vec4(col * a, a))
return Finish.over(out, sy_focus(d, px, self.focus * live))
}
// ---------------------------------------------------------------------------
// TOGGLES. A rubber slot 36 by 16 points holding a knurled gunmetal block as
// tall as the slot. The part of the slot the block leaves open carries an
// LED dash: lit blue when on, a dark bead when off.
mod.widgets.ToggleFlat.label_walk.margin = mod.turtle.Inset{left: 44.0 right: 3.0 top: 0.0 bottom: 0.0}
mod.widgets.ToggleFlat.draw_bg.size = uniform(16.0)
mod.widgets.ToggleFlat.min_height = 26.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.25)
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.0
let d = sy_rbox(p, c, h, r)
let live = 1.0 - self.disabled
let knob_t = mix(self.active, self.drag_pos, self.drag)
var fill = mix(vec3(0.110, 0.106, 0.102), vec3(0.300, 0.282, 0.235), self.disabled)
fill = fill + g3(0.08 * self.hover * live * (1.0 - self.down) - 0.05 * self.down * live)
var col = sy_well(p, c, h, r, px, fill, 2.6 + 1.5 * self.down)
col = mix(col, vec3(0.920, 0.300, 0.220), Finish.band(d, 0.0, px, px) * self.error)
let a = Finish.cover(d, px)
var out = sy_lip(p, c, h, r, px, 0.45)
out = Finish.over(out, vec4(col * a, a))
// the block: 15 points wide, its left edge on a pixel boundary
let bw = 15.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 dash in the open part of the slot, and its light on the floor
let open_c = mix(lo.x + sz.x - (sz.x - bw) * 0.5, lo.x + (sz.x - bw) * 0.5, knob_t)
let dsh = sy_dash(p, vec2(open_c, Finish.snap(o.y + c.y, px) - o.y), 4.0, px, px, mix(sy_acc(), sy_off(), self.disabled), self.active * live, vec3(0.050, 0.047, 0.043))
out = Finish.over(out, dsh)
let ink = sy_shadow_ink()
let sd = Finish.drop(p, bc, bh, 1.5, vec2(0.6, 1.2), 1.2) * 0.45 * live
out = Finish.over(out, vec4(vec3(0.0, 0.0, 0.0) * sd, sd))
let cd = Finish.drop(p, bc, bh, 1.5, vec2(0.0, px), px) * 0.7
out = Finish.over(out, vec4(ink * cd, cd))
let bd = sy_rbox(p, bc, bh, 1.5)
let bv = clamp((p.y - (bc.y - bh.y)) / max(2.0 * bh.y, 1.0), 0.0, 1.0)
var face = mix(vec3(0.330, 0.330, 0.326), vec3(0.170, 0.170, 0.168), smoothstep(0.0, 1.0, bv))
face = face + g3(sy_knurl(p + o, px) * 0.06)
face = face + g3(0.11 * self.hover * live * (1.0 - self.down) - 0.10 * self.down * live)
face = mix(face, vec3(0.34, 0.33, 0.31), self.disabled * 0.7)
let bt = p.y - (bc.y - bh.y)
face = mix(face, vec3(0.520, 0.516, 0.500), step(0.0, bt) * step(bt, px - 0.01) * live)
face = mix(face, vec3(0.040, 0.038, 0.035), Finish.band(bd, 0.0, px, px) * smoothstep(-0.5, 0.5, p.y - bc.y))
face = mix(face, sy_bezel(), Finish.band(bd, 0.0, px, px) * 0.7 * (1.0 - step(bt, px - 0.01) * live))
let ba = Finish.cover(bd, px)
out = Finish.over(out, vec4(face * ba, ba))
return Finish.over(out, sy_focus(d, px, self.focus * live))
}
// ---------------------------------------------------------------------------
// RADIOS. A rubber socket holding a steel ball; the chosen one's ball is a
// lit blue lamp, white-hot at its centre, with its light round it.
mod.widgets.RadioButtonFlat.padding = mod.turtle.Inset{left: 0.0 right: 3.0 top: 6.0 bottom: 6.0}
mod.widgets.RadioButtonFlat.label_walk.margin = mod.turtle.Inset{left: 21.0 right: 3.0 top: 0.0 bottom: 0.0}
mod.widgets.RadioButtonFlat.draw_bg.size = uniform(14.0)
mod.widgets.RadioButtonFlat.min_height = 26.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 = sy_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 d = length(p - c) - rad
var fill = mix(vec3(0.110, 0.106, 0.102), vec3(0.300, 0.282, 0.235), self.disabled)
fill = fill + g3(0.08 * self.hover * live * (1.0 - self.down) - 0.05 * self.down * live)
let col = sy_well(p, c, h, rad, px, fill, (2.2 + 2.0 * self.down) * (1.0 - 0.7 * self.disabled))
let a = Finish.cover(d, px)
var out = sy_lip(p, c, h, rad, px, 0.45)
out = Finish.over(out, vec4(col * a, a))
// the steel ball, dull, lit from the upper left
let br = rad * 0.62
let q = (p - c - vec2(0.0, 0.3 * self.down)) / max(br, 0.1)
let bd = length(p - c - vec2(0.0, 0.3 * self.down)) - br
let spec = exp(-dot(q - vec2(-0.35, -0.40), q - vec2(-0.35, -0.40)) * 5.0)
var ball = mix(vec3(0.46, 0.46, 0.45), vec3(0.14, 0.14, 0.14), clamp(q.y * 0.5 + 0.5, 0.0, 1.0))
ball = ball + g3(0.55 * spec + 0.16 * self.hover * live * (1.0 - self.down) - 0.12 * self.down * live)
ball = mix(ball, vec3(0.36, 0.34, 0.30), self.disabled * 0.7)
let ba = Finish.cover(bd, px) * (1.0 - self.active)
out = Finish.over(out, vec4(ball * ba, ba))
let lamp = sy_bead(p, c, rad * 0.55, px, mix(sy_acc(), sy_off(), self.disabled), live)
// its light on the socket's floor
let pool = exp(-max(length(p - c) - rad * 0.55, 0.0) / (1.4 * px)) * 0.45 * self.active * live * a
out = Finish.over(out, vec4(sy_acc() * 0.6 * pool, pool))
out = Finish.over(out, vec4(lamp.rgb * self.active, lamp.a * self.active))
return Finish.over(out, sy_focus(d, px, self.focus * live))
}
// Radio tabs: rubber keycaps side by side; the chosen one is pushed in, its
// legend lit blue and a lit dash under it.
let sy_segment = fn(p: vec2, size: vec2, o: vec2, px: float, hover: float, down: float, focus: float, dis: float, on: float, soft: float) -> vec4 {
let live = 1.0 - dis
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 r = 2.0
let d = sy_rbox(p, c, h, r)
let bez = 3.0 * px
let press = max(down * live, on)
let sink = press * px
let t = clamp((p.y - lo.y - sink) / max(2.0 * h.y, 1.0), 0.0, 1.0)
let yi = p.y - lo.y - bez - sink
var face = sy_face(t) + g3(0.065 * hover * live * (1.0 - press))
face = face + g3(sy_knurl(p + o, px) * 0.035)
let sunk = mix(vec3(0.035, 0.033, 0.030), vec3(0.130, 0.130, 0.127), smoothstep(0.0, 3.0 * px + 1.5, yi))
face = mix(face, sunk, press)
face = mix(face, vec3(0.290, 0.282, 0.263), dis)
face = face + g3(sy_grain((p + o) / px, 0.012))
let topw = 1.0 - smoothstep(lo.y + bez + r + 0.5, lo.y + bez + r + 1.5, p.y)
let botw = smoothstep(hi.y - bez - r - 1.5, hi.y - bez - r - 0.5, p.y)
let ramp = (1.0 - smoothstep(0.0, 2.0, yi - px)) * step(0.0, yi) * 0.30
face = mix(face, vec3(0.420, 0.416, 0.400), max(Finish.band(d, bez, bez + px, px) * topw, ramp) * (1.0 - press) * live)
face = mix(face, vec3(0.043, 0.039, 0.035), Finish.band(d, bez, bez + px, px) * botw * (1.0 - press))
// the chosen cap's lamp: a lit dash under the legend, its light pooling
let k = sy_acc()
let dy = Finish.snap(o.y + hi.y - bez - 3.0, px) - o.y + px
let lit = on * live
let pool_q = vec2((p.x - c.x) / max(h.x * 0.55, 1.0), (p.y - dy) / 3.0)
face = mix(face, k * 0.8, exp(-dot(pool_q, pool_q)) * 0.22 * lit)
let dash = sy_dash(p, vec2(c.x, dy), clamp(h.x * 0.40, 5.0, 12.0), px, px, mix(k, sy_off(), dis), lit, vec3(0.071, 0.067, 0.063))
face = mix(face, dash.rgb / max(dash.a, 0.001), dash.a * on)
// the rubber base
let up = 1.0 - smoothstep(-0.5, 0.5, p.y - c.y)
var base = sy_bezel()
base = mix(base, vec3(0.290, 0.220, 0.120), Finish.band(d, 0.0, px, px) * up * 0.6 * live)
face = mix(face, base, Finish.band(d, 0.0, bez, px))
// focus: one device pixel of the blue on the base's inner edge
face = mix(face, vec3(0.180, 0.520, 0.800), Finish.band(d, bez, bez + px, px) * 0.9 * focus * live)
var out = vec4(0.0, 0.0, 0.0, 0.0)
let ink = sy_shadow_ink()
let sft = Finish.drop(p, c, h, r, vec2(0.3, 1.6), 1.3) * 0.55 * soft * (1.0 - press) * live
out = Finish.over(out, vec4(ink * sft, sft))
let con = Finish.drop(p, c, h, r, vec2(0.0, px), px * 1.2) * 0.75 * (1.0 - 0.6 * press)
out = Finish.over(out, vec4(ink * con, con))
let a = Finish.cover(d, px)
return Finish.over(out, vec4(face * a, a))
}
mod.widgets.RadioButtonTabFlat.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 6.0 bottom: 6.0}
mod.widgets.RadioButtonTabFlat.min_height = 26.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, 6.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 = 6.0
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
return sy_segment(p, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.active, 1.0)
}
mod.widgets.RadioButtonTabFlat.draw_text.text_style = mod.theme.font_bold{font_size: 8.25}
mod.widgets.RadioButtonTabFlat.draw_text.color = #e4e2dc
mod.widgets.RadioButtonTabFlat.draw_text.color_hover = #f4f2ec
mod.widgets.RadioButtonTabFlat.draw_text.color_active = #8fd2f8
mod.widgets.RadioButtonTabFlat.draw_text.color_focus = #e4e2dc
mod.widgets.RadioButtonTabFlat.draw_text.color_down = #c8c6c0
mod.widgets.RadioButtonTabFlat.draw_text.color_disabled = #8e8b82
// ---------------------------------------------------------------------------
// SLIDERS. A thin slot cut into the paint: dark brown-black, the paint's
// shadow falling in from its top wall, a pale lip under its lower rim. No
// fill line: the thumb's place and the value say where it stands. The bar
// thumb is a chunky grip, black rubber ribbed across its length between two
// polished steel end caps, pill cornered, standing on the paint and throwing
// a soft shadow on it; the minimal and round sliders ride a small upright
// steel cap. Every slider grows its quad 16 points for the shadow.
mod.widgets.SliderMinimal.draw_bg.vertex = fn() {
let g = grow_vertex(self.rect_pos, self.rect_size, 16.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
mod.widgets.SliderFlat.draw_bg.vertex = fn() {
let g = grow_vertex(self.rect_pos, self.rect_size, 16.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.SliderFlat.clip_x = false
mod.widgets.SliderFlat.clip_y = false
mod.widgets.SliderRoundFlat.draw_bg.vertex = fn() {
let g = grow_vertex(self.rect_pos, self.rect_size, 16.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.SliderRoundFlat.clip_x = false
mod.widgets.SliderRoundFlat.clip_y = false
// The slot: `c` its centre, `h` its half size, h.y the half thickness.
let sy_slot = fn(p: vec2, c: vec2, h: vec2, px: float, dis: float) -> vec4 {
let r = h.y
let d = sy_rbox(p, c, h, r)
let fill = mix(vec3(0.150, 0.098, 0.030), vec3(0.300, 0.270, 0.210), dis)
var col = sy_well(p, c, h, r, px, fill, 1.4)
// the floor catches a little light just above the lower wall
let low = smoothstep(c.y + h.y - 1.5 * px, c.y + h.y - 0.5 * px, p.y)
col = mix(col, vec3(0.180, 0.120, 0.040), low * 0.5 * (1.0 - dis))
let a = Finish.cover(d, px)
// the paint darkens a little over the top wall, where the slot's edge
// is cut down into it
let above = Finish.ring_out(d, 0.0, 1.5 * px, px) * (1.0 - smoothstep(c.y - h.y, c.y, p.y)) * 0.30
var out = vec4(sy_shadow_ink() * above, above)
out = Finish.over(out, sy_lip(p, c, h, r, px, mix(0.50, 0.30, dis)))
return Finish.over(out, vec4(col * a, a))
}
// The grip: black rubber ribbed across the travel between two polished
// steel end caps, pill cornered, with a soft cast shadow down and a little
// right. `c` its centre, `h` its half size; `horiz` 1 when the travel runs
// along x (steel ends left and right, the ribs upright), 0 when it runs
// along y (steel ends top and bottom, the ribs across). `pitch` is the rib
// pitch in device pixels, `endl` an end cap's length in points.
let sy_grip = fn(p: vec2, o: vec2, c: vec2, h: vec2, px: float, horiz: float, pitch: float, endl: float, hover: float, press: float, dis: float) -> vec4 {
let ink = sy_shadow_ink()
var out = vec4(0.0, 0.0, 0.0, 0.0)
let wid = mix(2.0 * h.x, 2.0 * h.y, horiz)
let len = mix(2.0 * h.y, 2.0 * h.x, horiz)
let r = min(wid * 0.30, 5.0)
// the cast shadow: it stands off the paint, and lies closer while held
let lift = 1.0 - 0.5 * press
let cast = Finish.drop(p, c, h, r, vec2(0.08 * wid, 0.24 * wid) * lift, 0.16 * wid * lift + 0.4) * 0.80 * mix(1.0, 0.45, dis)
out = Finish.over(out, vec4(ink * cast, cast))
let con = Finish.drop(p, c, h, r, vec2(0.0, px), px) * 0.85
out = Finish.over(out, vec4(ink * con, con))
let d = sy_rbox(p, c, h, r)
// `a` along the travel from the top or left end, `b` across 0..1 from
// the top or left side
let a = mix(p.y - (c.y - h.y), p.x - (c.x - h.x), horiz)
let b = clamp(mix((p.x - (c.x - h.x)) / wid, (p.y - (c.y - h.y)) / wid, horiz), 0.0, 1.0)
let ea = min(a, len - a)
let first = 1.0 - step(len * 0.5, a)
// the steel: a polished bar, lit across from the top (horizontal) or a
// rounded column lit left of its middle (upright); brightest where it
// curves in to meet the rubber, dark where it rounds off at its end
let across_h = 0.26 + 0.46 * exp(-pow((b - 0.22) / 0.26, 2.0)) - 0.08 * smoothstep(0.7, 1.0, b)
let across_v = 0.28 + 0.50 * exp(-pow((b - 0.36) / 0.20, 2.0)) + 0.08 * smoothstep(0.8, 1.0, b)
let prof = mix(across_v, across_h, horiz)
let endf = mix(0.70, 1.22, smoothstep(0.0, endl * 0.9, ea))
let side = mix(1.0, mix(0.84, 1.06, first), 1.0 - horiz)
var steel = g3(prof * endf * side) * vec3(0.97, 0.985, 1.02)
// the paint reflected warm in the steel where it turns down to it
let warm = smoothstep(0.45, 1.0, b) * mix(1.0, 0.6, 1.0 - horiz) + (1.0 - smoothstep(0.0, endl * 0.6, ea)) * 0.5
steel = mix(steel, steel * vec3(1.18, 0.98, 0.66), clamp(warm, 0.0, 1.0) * 0.55)
steel = steel + g3(0.11 * hover - 0.07 * press)
// the rubber: ribs one `pitch` apart, each lit on its leading row and
// falling into a dark flute; shaded across like the steel
let q = mix(p.y + o.y, p.x + o.x, horiz) / px
let row = floor(fract(q / pitch) * pitch + 0.001)
let s = (row + 0.5) / pitch
let fine = step(pitch, 3.5)
var rl = mix(0.30, 0.15, clamp(s / 0.7, 0.0, 1.0))
rl = mix(rl, mix(0.045, 0.10, fine), step(0.7, s))
let cross_h = mix(1.20, 0.68, b)
let cross_v = 0.70 + 0.45 * exp(-pow((b - 0.38) / 0.30, 2.0))
rl = rl * mix(cross_v, cross_h, horiz)
var rubber = g3(rl) * vec3(1.0, 1.0, 1.02)
rubber = rubber + g3(0.07 * hover - 0.05 * press)
// the rubber is a pad set into a steel frame: the ends, and a thin
// rail along each side
let rail = 1.5 * px
let ph = mix(vec2(h.x - rail, h.y - endl), vec2(h.x - endl, h.y - rail), horiz)
let pd = sy_rbox(p, c, ph, min(r * 0.6, min(ph.x, ph.y)))
let in_pad = Finish.cover(pd, px)
// the rails catch the light on the top side and fall dark below
let rail_l = mix(0.62, 0.30, smoothstep(c.y - 0.5, c.y + 0.5, p.y))
let is_rail = 1.0 - smoothstep(0.0, 0.01, ea - endl)
steel = mix(g3(rail_l) * vec3(0.97, 0.985, 1.02), steel, is_rail)
var face = mix(steel, rubber, in_pad)
// the seam where the steel meets the rubber: one dark device pixel
face = mix(face, vec3(0.030, 0.028, 0.026), Finish.band(pd, 0.0, px, px) * 0.9)
face = mix(face, mix(g3(0.36), face * 0.55 + g3(0.14), 0.6), dis)
face = face + g3(sy_grain((p + o) / px, 0.012))
// the outline, darkest at the foot
let foot = smoothstep(c.y - 0.5, c.y + 0.5, p.y)
face = mix(face, vec3(0.040, 0.036, 0.030), Finish.band(d, 0.0, px, px) * mix(0.55, 0.95, foot))
let fa = Finish.cover(d, px)
return Finish.over(out, vec4(face * fa, fa))
}
// The small cap: an upright steel block rounded at its corners, turned so
// its middle catches the light, its top edge bright, its foot dark; a soft
// shadow under it.
let sy_cap = fn(p: vec2, o: vec2, c: vec2, h: vec2, px: float, hover: float, press: float, dis: float) -> vec4 {
let ink = sy_shadow_ink()
var out = vec4(0.0, 0.0, 0.0, 0.0)
let w = 2.0 * h.x
let r = min(h.x * 0.55, 3.0)
let lift = 1.0 - 0.5 * press
let cast = Finish.drop(p, c, h, r, vec2(0.10 * w, 0.26 * w) * lift, 0.18 * w * lift + 0.3) * 0.75 * mix(1.0, 0.45, dis)
out = Finish.over(out, vec4(ink * cast, cast))
let con = Finish.drop(p, c, h, r, vec2(0.0, px), px) * 0.85
out = Finish.over(out, vec4(ink * con, con))
let d = sy_rbox(p, c, h, r)
let b = clamp((p.x - (c.x - h.x)) / w, 0.0, 1.0)
let t = clamp((p.y - (c.y - h.y)) / (2.0 * h.y), 0.0, 1.0)
var l = 0.30 + 0.52 * exp(-pow((b - 0.46) / 0.15, 2.0)) + 0.12 * smoothstep(0.78, 1.0, abs(b - 0.5) * 2.0)
l = l * mix(1.06, 0.82, t)
var col = g3(l) * vec3(0.96, 0.985, 1.03)
col = mix(col, col * vec3(1.16, 0.98, 0.70), smoothstep(0.55, 1.0, t) * 0.5)
// the top edge: a bright rim where the flat top turns down
let yi = p.y - (c.y - h.y)
col = mix(col, g3(0.99), (1.0 - smoothstep(px * 1.5, px * 3.0, yi)) * step(0.0, yi) * 0.90 * (1.0 - press))
col = col + g3(0.10 * hover - 0.07 * press)
col = mix(col, mix(g3(0.36), col * 0.55 + g3(0.14), 0.6), dis)
col = col + g3(sy_grain((p + o) / px, 0.012))
let foot = smoothstep(c.y - 0.5, c.y + 0.5, p.y)
col = mix(col, vec3(0.050, 0.045, 0.040), Finish.band(d, 0.0, px, px) * mix(0.45, 0.95, foot))
let a = Finish.cover(d, px)
return Finish.over(out, vec4(col * a, a))
}
// The bar slider: the slot 7 points above its foot, the grip 42 by 18
// points riding it.
mod.widgets.SliderFlat.height = 34.0
mod.widgets.SliderFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = 16.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 bw = 21.0
let cy = size.y - 7.0
let top = Finish.snap(o.y + cy, px) - o.y - 2.0 * px
let x0 = Finish.snap(o.x + 1.0, px) - o.x
let x1 = Finish.snap(o.x + size.x - 1.0, px) - o.x
let sh = vec2((x1 - x0) * 0.5, 2.0 * px)
var out = sy_slot(p, vec2(x0 + sh.x, top + sh.y), sh, px, self.disabled)
let kx = x0 + bw + self.slide_pos * (x1 - x0 - 2.0 * bw)
let bcx = Finish.snap(o.x + kx, px) - o.x
let bcy = top + 2.0 * px + self.drag * live * px
let gh = vec2(bw, 9.5)
out = Finish.over(out, sy_grip(p, o, vec2(bcx, bcy), gh, px, 1.0, 3.0, 6.5, self.hover * live, self.drag * live, self.disabled))
return Finish.over(out, sy_focus(sy_rbox(p, vec2(bcx, bcy), gh, 5.0), px, self.focus * live))
}
mod.widgets.SliderFlat.text_input.draw_text.color = #1c1a14
mod.widgets.SliderFlat.text_input.draw_text.color_hover = #0e0d0a
mod.widgets.SliderFlat.text_input.draw_text.color_focus = #0e0d0a
mod.widgets.SliderFlat.text_input.draw_text.color_empty = #5a4a26
mod.widgets.SliderFlat.text_input.draw_text.color_disabled = #7a6630
// The minimal slider: a slimmer slot and the small steel cap.
mod.widgets.SliderMinimal.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = 16.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 ch = vec2(5.5, 8.0)
let sh = vec2(size.x * 0.5, 2.0 * px)
var out = sy_slot(p, vec2(sh.x, cy), sh, px, self.disabled)
let kx = ch.x + self.slide_pos * (size.x - 2.0 * ch.x)
let kc = vec2(Finish.snap(o.x + kx, px) - o.x, cy + self.drag * live * px)
out = Finish.over(out, sy_cap(p, o, kc, ch, px, self.hover * live, self.drag * live, self.disabled))
return Finish.over(out, sy_focus(sy_rbox(p, kc, ch, 3.0), px, self.focus * live))
}
mod.widgets.SliderMinimal.text_input.draw_text.text_style = mod.theme.font_regular{font_size: 7.5}
mod.widgets.SliderMinimal.text_input.draw_text.color = #1c1a14
mod.widgets.SliderMinimal.text_input.draw_text.color_hover = #0e0d0a
mod.widgets.SliderMinimal.text_input.draw_text.color_focus = #0e0d0a
mod.widgets.SliderMinimal.text_input.draw_text.color_disabled = #7a6630
// The round slider: the slot beside its label, stopping 36 points short of
// the end so the value reads on the paint after it, and the small cap
// riding it.
mod.widgets.SliderRoundFlat.height = 14.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.text_input.draw_text.color = #1c1a14
mod.widgets.SliderRoundFlat.text_input.draw_text.color_hover = #0e0d0a
mod.widgets.SliderRoundFlat.text_input.draw_text.color_focus = #0e0d0a
mod.widgets.SliderRoundFlat.text_input.draw_text.color_disabled = #7a6630
mod.widgets.SliderRoundFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = 16.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 x1 = Finish.snap(o.x + size.x - 36.0, px) - o.x
let cy = Finish.snap(o.y + size.y * 0.5, px) - o.y
let sh = vec2(max(x1 - x0, 4.0) * 0.5, 2.0 * px)
var out = sy_slot(p, vec2(x0 + sh.x, cy), sh, px, self.disabled)
let ch = vec2(5.5, 7.5)
let kx = x0 + ch.x + self.slide_pos * max(x1 - x0 - 2.0 * ch.x, 0.0)
let kc = vec2(Finish.snap(o.x + kx, px) - o.x, cy + self.drag * live * px)
out = Finish.over(out, sy_cap(p, o, kc, ch, px, self.hover * live, self.drag * live, self.disabled))
return Finish.over(out, sy_focus(sy_rbox(p, kc, ch, 3.0), px, self.focus * live))
}
// The range slider: the same slot; the span between the two grips is the
// slot cut wider and blacker, and each end is a grip.
mod.widgets.RangeSliderFlat.handle_size = 30.0
mod.widgets.RangeSliderFlat.clip_x = false
mod.widgets.RangeSliderFlat.clip_y = false
mod.widgets.RangeSliderFlat.draw_bg.vertex = fn() {
let g = grow_vertex(self.rect_pos, self.rect_size, 16.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 = 16.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 top = self.label_px
let cy = Finish.snap(o.y + top + (size.y - top) * 0.5, px) - o.y
let x0 = Finish.snap(o.x + self.inset_px, px) - o.x
let x1 = Finish.snap(o.x + size.x - self.inset_px, px) - o.x
let sh = vec2((x1 - x0) * 0.5, 2.5 * px)
var out = sy_slot(p, vec2(x0 + sh.x, cy), sh, px, self.disabled)
let span_x = size.x - self.inset_px * 2.0 - self.handle_px
let sc = Finish.snap(o.x + self.inset_px + self.handle_px * 0.5 + self.start_pos * span_x, px) - o.x
let ec = Finish.snap(o.x + self.inset_px + self.handle_px * 0.5 + self.end_pos * span_x, px) - o.x
// the span: the slot cut a device pixel wider each side, black
let bh = vec2(max(ec - sc, 1.0) * 0.5, 3.5 * px)
let bd = sy_rbox(p, vec2((sc + ec) * 0.5, cy), bh, bh.y)
let bk = mix(vec3(0.020, 0.016, 0.010), vec3(0.070, 0.060, 0.045), self.hot_band * live)
let ba = Finish.cover(bd, px) * mix(1.0, 0.4, self.disabled)
out = Finish.over(out, vec4(bk * ba, ba))
let gh = vec2(self.handle_px * 0.5, min((size.y - top) * 0.5, 8.0))
let hov = self.hover * live
out = Finish.over(out, sy_grip(p, o, vec2(sc, cy), gh, px, 1.0, 3.0, 5.0, max(self.hot_start, 0.0) * hov, self.drag * live * self.hot_start, self.disabled))
out = Finish.over(out, sy_grip(p, o, vec2(ec, cy), gh, px, 1.0, 3.0, 5.0, max(self.hot_end, 0.0) * hov, self.drag * live * self.hot_end, self.disabled))
return Finish.over(out, sy_focus(sy_rbox(p, vec2((x0 + x1) * 0.5, cy), vec2((x1 - x0) * 0.5, gh.y), 5.0), px, self.focus * live))
}
// ---------------------------------------------------------------------------
// KNOBS. A turned steel cap in a thick black rubber skirt, the skirt four
// tenths of the radius and fluted: broad concave scoops round it with
// rounded ridges between, so from above the rim is a lobed flower; twelve
// from 50 points across, eight below (seven or six on the smallest knobs).
// A scoop takes six tenths of its period on a long period and narrows
// toward half and cuts shallower on a short one, so the ridges stay broad
// and round instead of points. The flutes turn with the knob, a ridge
// under the pointer; the light does not. A scoop is a groove running down
// the skirt, deepest at the rim and gone by the flat ring next to the cap;
// across it the surface turns, so the wall on the light's side is dark and
// the far wall catches the light, and each ridge's crown carries a glossy
// streak where it turns toward the light.
// A soft shadow falls on the paint below and to the right. The value is a
// thin lit arc outside the skirt; its unlit rest is a groove in the paint.
let sy_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 hh = vec2(rad, rad)
let ink = sy_shadow_ink()
var out = vec4(0.0, 0.0, 0.0, 0.0)
let cast = Finish.drop(p, c, hh, rad, vec2(dia * 0.07, dia * 0.14), dia * 0.10) * 0.88 * mix(1.0, 0.4, dis)
out = Finish.over(out, vec4(ink * cast, cast))
let q = p - c
let lq = max(length(q), 0.0001)
let rd = q / lq
let lt = vec2(-0.42, -0.91)
let tang = vec2(-rd.y, rd.x)
// the knob's turn, as the pointer's
let pang = gap * 0.5 + (2.0 * PI - gap) * val
// the flutes, twelve from 50 points across: `t` 0 in a scoop's middle,
// +-0.5 on a ridge's crest, a crest under the pointer
let big = step(50.0, dia)
let nt = mix(clamp(floor(dia / 3.6), 6.0, 8.0), 12.0, big)
let per = 2.0 * PI / nt
let u = (atan2(q.x, -q.y) - pang - PI) / per
let t = u - floor(u) - 0.5
let xl = lq * sin(t * per)
let along = lq * cos(t * per)
// the scoop's share of the period `sw` and its depth: under 50 points
// across 0.5 and 0.115 of the radius (from 0.6 and 0.135 at 50); with
// twelve by the period's length, 0.5 and 0.08 at 14 points to 0.58 and
// 0.09 at 28
let sm = clamp((50.0 - dia) / 14.0, 0.0, 1.0)
let sb = clamp((per * rad - 14.0) / 14.0, 0.0, 1.0)
let sw = mix(mix(0.6, 0.5, sm), mix(0.5, 0.58, sb), big)
// the outline: the disc less a circle per scoop, `dep` deep with a chord
// of `sw` of the period, the corners rounded
let dep = rad * mix(mix(0.135, 0.115, sm), mix(0.08, 0.09, sb), big)
let ch = 0.5 * sw * per * rad
let rho = (ch * ch + dep * dep) / (2.0 * dep)
let kf = dep * 0.9
let da = lq - rad
let db = rho - length(vec2(xl, along - (rad - dep + rho)))
let hk = clamp(0.5 - 0.5 * (db - da) / kf, 0.0, 1.0)
let d = mix(db, da, hk) + kf * hk * (1.0 - hk)
let con = Finish.ring_out(d, 0.0, px, px) * 0.75
out = Finish.over(out, vec4(ink * con, con))
// the skirt, `s` 0 at the cap's edge to 1 at the rim
let skirt = max(floor(rad * 0.34 / px + 0.5), 4.0) * px
let inner = rad - skirt
let s = clamp((lq - inner) / max(skirt, 0.001), 0.0, 1.0)
// across a period the surface sinks by `fl` of the flute's depth: a
// bowl over the scoop's `sw` and a crown over the ridge, two parabolas
// meeting with one slope; `fd` its slope along `t`
let at = abs(t)
let hs = 0.5 * sw
let insc = step(at, hs)
let us = at / hs
let vr = (0.5 - at) / (0.5 - hs)
let fl = mix((1.0 - sw) * vr * vr, 1.0 - sw * us * us, insc)
let fd = -4.0 * mix(vr, us, insc) * sign(t)
// the flute's depth down the skirt: none on the flat ring by the cap,
// full by four fifths of the way to the rim
let r0 = inner + skirt * 0.30
let r1 = r0 + (rad - r0) * 0.8
let fw = clamp((lq - r0) / (r1 - r0), 0.0, 1.0)
let fr = 1.0 - (1.0 - fw) * (1.0 - fw)
let dfr = 2.0 * (1.0 - fw) / (r1 - r0) * step(r0, lq)
// the surface's normal: across the flute and down it
let dz = dep * 2.5
let hx = -dz * fr * fd / (per * lq)
let hr = -dz * dfr * fl
let nz = 1.0 / sqrt(1.0 + hx * hx + hr * hr)
let nxy = -(tang * hx + rd * hr) * nz
let lam = dot(nxy, lt)
let slope = dot(rd, lt) * smoothstep(0.25, 1.0, s)
var sl = 0.080 + 0.090 * slope + 0.22 * lam
// the gloss, on the fluted skirt tilted outward as it slopes down
let bn = normalize(vec3(nxy + rd * (0.45 * fr * nz), nz))
let ndh = clamp(dot(bn, normalize(normalize(vec3(lt, 0.8)) + vec3(0.0, 0.0, 1.0))), 0.0, 1.0)
sl = sl + (0.5 * pow(ndh, 50.0) + 0.06 * pow(ndh, 5.0)) * fr
// no black holes: walls turned from the light take a fill from the far
// side, and steep walls reflect the room
sl = max(sl, 0.035) + 0.06 * max(-lam, 0.0) + 0.03 * (1.0 - nz) * fr
// the skirt's flat top ring next to the cap, lit on the upper side
let ring = 1.0 - smoothstep(0.24, 0.36, s)
let up = -dot(rd, lt)
sl = mix(sl, 0.10 + 0.09 * up + 0.05 * (1.0 - s), ring * 0.9)
var sk = g3(max(sl, 0.012)) * vec3(1.0, 0.985, 0.965)
sk = sk + g3(0.03 * hover)
sk = mix(sk, g3(0.30), dis * 0.6)
// the turned face
var face = g3(sy_conic(q, px, 0.40, 0.88, 0.10 * hover))
face = face * vec3(1.0, 0.99, 0.97)
face = face + g3(0.04 * hover - 0.03 * press)
face = mix(face, mix(g3(0.36), face * 0.5, 0.5), dis)
let di = lq - inner
let cw = max(floor(dia * 0.024 / px + 0.5), 2.0) * px
let chm = mix(vec3(0.70, 0.70, 0.69), vec3(0.98, 0.98, 0.97), smoothstep(-0.2, 0.9, q.y / rad))
face = mix(face, vec3(0.30, 0.30, 0.30), Finish.band(di, cw, cw + px, px))
face = mix(face, mix(chm, g3(0.40), dis), Finish.band(di, 0.0, cw, px))
var col = mix(sk, face, Finish.cover(di, px))
// the dark joint between the cap and the rubber
col = mix(col, vec3(0.015, 0.014, 0.012), Finish.ring_out(di, 0.0, px, px) * 0.9)
// the rim: dark, with a warm catch of the paint on its lower edge
col = mix(col, vec3(0.018, 0.017, 0.015), Finish.band(d, 0.0, px, px))
col = mix(col, vec3(0.34, 0.26, 0.11), Finish.band(d, px, 2.0 * px, px) * smoothstep(0.2, 0.9, q.y / rad) * 0.35 * (1.0 - dis))
// the pointer, engraved: a dark line with a light line on its lower side
let dir = vec2(-sin(pang), cos(pang))
let ic = inner - cw - px
let pa = c + dir * ic * 0.28
let pb = c + dir * ic * 0.92
let pw = max(dia * 0.022, 0.75)
let pl = sy_seg(p, pa, pb) - pw
let pl_lit = sy_seg(p - vec2(0.0, px), pa, pb) - pw
let on_face = step(lq, ic)
col = mix(col, g3(0.98), Finish.cover(pl_lit, px) * 0.55 * (1.0 - dis) * on_face)
col = mix(col, mix(vec3(0.07, 0.07, 0.07), g3(0.3), dis), Finish.cover(pl, px) * on_face)
let a = Finish.cover(d, px)
return Finish.over(out, vec4(col * a, a))
}
// The value arc: `ar` its middle radius, `aw` its width. The unlit part is a
// groove, dark, with a pale lip outside it.
let sy_arc = fn(p: vec2, c: vec2, ar: float, aw: float, px: float, val: float, gap: 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 rd = abs(length(q) - ar) - aw * 0.5
let in_arc = step(start, ang) * step(ang, start + sweep)
let track_d = mix(1000.0, rd, in_arc)
let lit_in = step(start, ang) * step(ang, start + sweep * val)
let lit_d = mix(1000.0, rd, lit_in)
var out = vec4(0.0, 0.0, 0.0, 0.0)
let gd = Finish.cover(track_d, px)
let lipd = mix(1000.0, abs(length(q) - ar - aw * 0.5 - px * 0.5) - px * 0.5, in_arc)
let lip = Finish.cover(lipd, px) * 0.35
out = vec4(vec3(1.0, 0.925, 0.690) * lip, lip)
out = Finish.over(out, vec4(vec3(0.071, 0.067, 0.063) * gd, gd))
let la = Finish.cover(lit_d, px)
let k = mix(sy_fill() * (1.0 + 0.1 * hot), sy_off(), dis)
return Finish.over(out, vec4(k * la, la))
}
mod.widgets.RotaryKnob.clip_x = false
mod.widgets.RotaryKnob.clip_y = false
mod.widgets.RotaryKnob.draw_bg.vertex = fn() {
let g = grow_vertex(self.rect_pos, self.rect_size, 16.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.RotaryKnob.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = 16.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.84 / px) * px
let aw = 2.0 * px
let ar = floor((rad + max(rad * 0.09, 1.8)) / px + 0.5) * px + aw * 0.5
let hot = max(self.hover, self.drag) * live
var out = sy_arc(p, c, ar, aw, px, self.slide_pos, gap, self.disabled, hot)
out = Finish.over(out, sy_knob(p, c, rad, px, self.slide_pos, gap, max(self.hover, self.focus) * live, self.drag * live, self.disabled))
return Finish.over(out, sy_focus(length(p - c) - ar - aw * 0.5, px, self.focus * live))
}
mod.widgets.RotaryFlat.clip_x = false
mod.widgets.RotaryFlat.clip_y = false
mod.widgets.RotaryFlat.draw_bg.vertex = fn() {
let g = grow_vertex(self.rect_pos, self.rect_size, 16.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.RotaryFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = 16.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.84 / px) * px
let aw = 2.0 * px
let ar = floor((rad + max(rad * 0.09, 1.8)) / px + 0.5) * px + aw * 0.5
let hot = max(self.hover, self.drag) * live
var out = sy_arc(p, c, ar, aw, px, self.slide_pos, gap, self.disabled, hot)
out = Finish.over(out, sy_knob(p, c, rad, px, self.slide_pos, gap, max(self.hover, self.focus) * live, self.drag * live, self.disabled))
return Finish.over(out, sy_focus(length(p - c) - ar - aw * 0.5, px, self.focus * live))
}
// The dial's readout: a charcoal window cut into the paint, lit blue 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 = sy_rbox(p, c, h, 2.0)
var col = sy_well(p, c, h, 2.0, px, vec3(0.110, 0.110, 0.114), 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))
let a = Finish.cover(d, px)
var out = sy_lip(p, c, h, 2.0, px, 0.45)
return Finish.over(out, vec4(col * a, a))
}
mod.widgets.RotaryFlat.text_input.draw_text.color = #56b8f0
mod.widgets.RotaryFlat.text_input.draw_text.color_hover = #56b8f0
mod.widgets.RotaryFlat.text_input.draw_text.color_focus = #7ccaf6
mod.widgets.RotaryFlat.text_input.draw_text.color_empty = #2a5a7a
mod.widgets.RotaryFlat.text_input.draw_text.text_style = mod.theme.font_code{font_size: 9.0}
// The knob's caption is a stencilled scale label.
mod.widgets.RotaryKnob.draw_text.text_style = mod.theme.font_bold{font_size: 6.75}
mod.widgets.RotaryKnob.draw_text.color = #1c1a14
// ---------------------------------------------------------------------------
// FADERS. The slider's slot, engraved ticks on one side and scale dots on
// the other, all in the paint's dark ink; the cap is a tall grip, steel
// ends over and under a black rubber body cut with deep ribs across it.
mod.widgets.SliderFader.cap_size = 34.0
mod.widgets.SliderFader.draw_bg.cap_cross = uniform(0.55)
mod.widgets.SliderFader.clip_x = false
mod.widgets.SliderFader.clip_y = false
mod.widgets.SliderFader.draw_bg.vertex = fn() {
let g = grow_vertex(self.rect_pos, self.rect_size, 16.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.SliderFader.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = 16.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 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, drawn in window terms so its lip falls under it
let sw = 5.0 * px
let slen = (along - self.inset_px * 2.0) * 0.5
let sc = mix(vec2(self.inset_px + slen, mid), vec2(mid, size.y - self.inset_px - slen), vert)
let shs = mix(vec2(slen, sw * 0.5), vec2(sw * 0.5, slen), vert)
var out = sy_slot(p, sc, shs, px, self.disabled)
// ticks on the far side: every tenth, a long one at each quarter
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(sy_bezel() * tk * 0.8, tk * 0.8))
// scale dots on the near side
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 dd = length(q - dc) - 1.0
let in_d = step(-0.5, di) * step(foot + di * dp, foot + travel + 0.1)
let dcol = vec3(0.130, 0.100, 0.050)
let da2 = Finish.cover(dd, px) * in_d * 0.85
out = Finish.over(out, vec4(dcol * da2, da2))
// the cap, in window terms
let cap_w = min(max(across * self.cap_cross, 4.0), across)
let chh = mix(vec2(self.cap_px * 0.5, cap_w * 0.5), vec2(cap_w * 0.5, self.cap_px * 0.5), vert)
let cc0 = mix(vec2(at, mid), vec2(mid, size.y - at), vert)
let cc = vec2(Finish.snap(o.x + cc0.x, px) - o.x, Finish.snap(o.y + cc0.y, px) - o.y + self.drag * live * px)
let endl = self.cap_px * 0.16
out = Finish.over(out, sy_grip(p, o, cc, chh, px, 1.0 - vert, 6.0, endl, max(self.hover, self.focus) * live, self.drag * live, self.disabled))
return Finish.over(out, sy_focus(sy_rbox(p, cc, chh, 5.0), px, self.focus * live))
}
// ---------------------------------------------------------------------------
// TEXT FIELDS. A charcoal well 26 points tall cut into the paint: a dark top
// wall with the plate's shadow falling in, a pale lip under its foot. Focus
// lights the mouth in the blue, inside the box.
let sy_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 - vec2(0.0, px)
let h = (hi - lo) * 0.5
let c = lo + h
let d = sy_rbox(p, c, h, r)
let t = clamp((p.y - lo.y) / max(2.0 * h.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.075, 0.071, 0.067), vec3(0.141, 0.141, 0.149), rise)
fill = mix(fill, vec3(0.153, 0.153, 0.161), clamp(t * 2.0 - 1.0, 0.0, 1.0))
fill = fill + g3(0.05 * hover * (1.0 - dis))
fill = mix(fill, vec3(0.270, 0.255, 0.215), dis)
fill = fill + g3(sy_grain((p + o) / px, 0.008))
var col = sy_well(p, c, h, r, px, fill, 2.2)
let k = vec3(0.337, 0.722, 0.941)
col = mix(col, k, Finish.band(d, 0.0, px, px) * focus * 0.85)
let low = smoothstep(-0.5, 0.5, p.y - c.y)
col = mix(col, k, Finish.band(d, px, 2.0 * px, px) * low * focus * 0.35)
let a = Finish.cover(d, px)
var out = sy_lip(p, c, h, r, px, 0.45)
return Finish.over(out, vec4(col * a, a))
}
mod.widgets.TextInputFlat.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 3.0 bottom: 3.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 sy_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 = #e4e2dc
mod.widgets.TextInputFlat.draw_text.color_hover = #f4f2ec
mod.widgets.TextInputFlat.draw_text.color_focus = #f4f2ec
mod.widgets.TextInputFlat.draw_text.color_empty = #8e8b82
mod.widgets.TextInputFlat.draw_text.color_disabled = #a8a080
mod.widgets.TextInputFlat.draw_cursor.color = #56b8f0
mod.widgets.NumberField.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return sy_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 sy_field(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, self.hover, self.focus, self.disabled, 2.0)
}
// The stepper: a raised gunmetal column set into the well's end, split by
// an engraved line, each half with a small stroked chevron.
let sy_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, 2.0 * px)
let hi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y, px)) - o - vec2(2.0 * px, 3.0 * px)
let h = (hi - lo) * 0.5
let c = lo + h
let d = sy_rbox(p, c, h, 1.5)
let live = 1.0 - dis
let t = clamp((p.y - lo.y) / max(2.0 * h.y, 1.0), 0.0, 1.0)
var face = sy_face(t)
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
face = face + g3(0.04 * hh * (1.0 - dn))
face = mix(face, vec3(0.070, 0.067, 0.063), dn)
face = mix(face, vec3(0.290, 0.282, 0.263), dis)
let mid = Finish.snap(o.y + c.y, px) - o.y
face = mix(face, sy_bezel(), step(mid - px, p.y) * step(p.y, mid))
face = mix(face, vec3(0.380, 0.376, 0.365), step(mid, p.y) * step(p.y, mid + px) * live)
face = mix(face, vec3(0.420, 0.416, 0.400), Finish.band(d, px, 2.0 * px, px) * (1.0 - smoothstep(-0.5, 0.5, p.y - lo.y - 1.5)) * live)
face = mix(face, sy_bezel(), Finish.band(d, 0.0, px, px))
let ink = mix(vec3(0.84, 0.83, 0.80), vec3(0.56, 0.55, 0.51), dis)
let cu = vec2(c.x, (lo.y + mid) * 0.5)
let cl = vec2(c.x, (mid + hi.y) * 0.5)
let chd = min(sy_chevron(p, cu, 5.0, 2.5, 1.0), sy_chevron(p, cl, 5.0, 2.5, 0.0)) - 0.65
face = mix(face, ink + g3(0.1 * hh), 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 sy_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 sy_spin(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, self.hot, self.down, self.disabled)
}
// ---------------------------------------------------------------------------
// DROP-DOWNS. The cap with an engraved split 22 points from its right end
// and a stroked chevron behind it; open is pressed.
mod.widgets.DropDownFlat.padding = mod.turtle.Inset{left: 10.0 right: 30.0 top: 3.0 bottom: 3.0}
mod.widgets.DropDown.padding = mod.turtle.Inset{left: 10.0 right: 30.0 top: 3.0 bottom: 3.0}
mod.widgets.DropDownFlat.clip_x = false
mod.widgets.DropDownFlat.clip_y = false
mod.widgets.DropDownFlat.draw_bg.vertex = fn() {
let 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.DropDownFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return sy_dropdown(self.pos, self.rect_size, self.rect_pos, px, self.hover, max(self.down * self.hover, self.active), self.focus, self.disabled, 6.0)
}
mod.widgets.DropDownFlat.draw_text.text_style = mod.theme.font_bold{font_size: 8.25}
mod.widgets.DropDownFlat.draw_text.color = #e4e2dc
mod.widgets.DropDownFlat.draw_text.color_hover = #f4f2ec
mod.widgets.DropDownFlat.draw_text.color_focus = #e4e2dc
mod.widgets.DropDownFlat.draw_text.color_down = #c8c6c0
mod.widgets.DropDownFlat.draw_text.color_disabled = #8e8b82
// The combo box: the well with a raised key joined at its right end.
mod.widgets.ComboBox.padding = mod.turtle.Inset{left: 8.0 right: 26.0 top: 3.0 bottom: 3.0}
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 = sy_field(p, size, o, px, self.hover, self.focus, self.disabled, 2.0)
let kx = Finish.snap(o.x + size.x - 20.0, px) - o.x
let hi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y, px)) - o - vec2(2.0 * px, 3.0 * px)
let klo = vec2(kx, Finish.snap(o.y, px) - o.y + 2.0 * px)
let kh = (hi - klo) * 0.5
let kc = klo + kh
let kd = sy_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 = sy_face(t) + g3(0.035 * self.hover * live * (1.0 - held))
face = face + g3(sy_knurl(p + o, px) * 0.035)
face = mix(face, vec3(0.070, 0.067, 0.063), held)
face = mix(face, vec3(0.290, 0.282, 0.263), self.disabled)
face = mix(face, vec3(0.420, 0.416, 0.400), step(klo.y, p.y) * step(p.y, klo.y + px) * (1.0 - held) * live)
face = mix(face, sy_bezel(), Finish.band(kd, 0.0, px, px))
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(sy_chevron(p, cc, 6.0, 3.5, 0.0) - 0.65, px)
let ink = mix(vec3(0.84, 0.83, 0.80), vec3(0.56, 0.55, 0.51), self.disabled)
return Finish.over(out, vec4(ink * ch, ch))
}
// ---------------------------------------------------------------------------
// SEGMENTS AND TABS. The segmented control is a row of rubber keycaps on
// one base: the chosen answer is pushed in deep between its neighbours, its
// lamp lit under the legend. The tab strip is a row of separate keycaps on
// an engraved rule; the tab in use is pushed in with its lamp lit.
let sy_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
let h = (hi - lo) * 0.5
let c = lo + h
var face = sy_button(pos, size, o, px, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 2.0, 1.0, m)
// Down a column every answer is a cap of its own: each keeps the face's
// fall from top to foot, and where two meet the upper one's foot line,
// a rubber row and the lower one's top line.
let sh = 26.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 kc = clamp(floor((p.y - 2.0) / sh), 0.0, max(n - 1.0, 0.0))
let t0 = clamp((p.y - lo.y) / max(2.0 * h.y, 1.0), 0.0, 1.0)
let tc = clamp((p.y - kc * sh) / (sh + 4.0), 0.0, 1.0)
let bez = 2.0 * px
let inx = step(lo.x + bez, p.x) * (1.0 - step(hi.x - bez, p.x))
let iny = step(lo.y + bez, p.y) * (1.0 - step(hi.y - bez, p.y))
let fall = (sy_face(tc) - sy_face(t0)) * inx * iny * col * face.a
face = vec4(face.rgb + fall, face.a)
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_foot = step(yb - px, p.y) * (1.0 - step(yb, p.y)) * join
let r_bez = step(yb, p.y) * (1.0 - step(yb + px, p.y)) * join
let r_top = step(yb + px, p.y) * (1.0 - step(yb + 2.0 * px, p.y)) * join
face = Finish.over(face, vec4(vec3(0.043, 0.039, 0.035) * r_foot, r_foot))
face = Finish.over(face, vec4(sy_bezel() * r_bez, r_bez))
face = Finish.over(face, vec4(vec3(0.420, 0.416, 0.400) * r_top, r_top))
return face
}
// The chosen answer, pushed in: a well the segment's width under the
// strip's top base row, black under its top edge and shaded along both
// walls, with the neighbours' ends standing lit over it; a small blue lamp
// under the label, its light pooling on the floor.
let sy_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
let inside = step(x0, p.x) * (1.0 - step(x1, p.x)) * step(y0, p.y) * (1.0 - step(y1, p.y))
let t = clamp((p.y - y0) / max(y1 - y0, 1.0), 0.0, 1.0)
var col = mix(vec3(0.150, 0.150, 0.146), vec3(0.120, 0.120, 0.117), t)
col = col + g3(sy_knurl(p + o, px) * 0.025 + sy_grain((p + o) / px, 0.008))
let top = exp(-max(p.y - y0, 0.0) / (2.2 * px))
let wl = 0.8 * exp(-max(p.x - x0, 0.0) / (1.4 * px))
let wr = 0.8 * exp(-max(x1 - px - p.x, 0.0) / (1.4 * px))
let foot = 0.45 * step(y1 - px, p.y)
let dk = 1.0 - (1.0 - top) * (1.0 - wl) * (1.0 - wr) * (1.0 - foot)
col = mix(col, vec3(0.020, 0.018, 0.016), clamp(dk, 0.0, 1.0))
let k = sy_acc()
let lc = vec2(Finish.snap(o.x + (x0 + x1) * 0.5, px) - o.x, Finish.snap(o.y + size.y, px) - o.y - 6.0 * px)
let q = vec2((p.x - lc.x) / max((x1 - x0) * 0.26, 1.0), (p.y - lc.y) / 3.4)
let pool = exp(-dot(q, q)) * 0.30 * (1.0 - 0.6 * top)
col = mix(col, k * 0.85, pool)
var res = vec4(col, 1.0) * inside
let ey = step(y0, p.y) * (1.0 - step(y1 - px, 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 = mix(vec3(0.400, 0.396, 0.380), vec3(0.200, 0.200, 0.196), t)
res = Finish.over(res, vec4(lit, 1.0) * (el + er) * ey)
let ld = sy_rbox(p, lc, vec2(8.0 * px, px), px)
let la = Finish.cover(ld, px)
let hot = mix(mix(k, vec3(1.0, 1.0, 1.0), 0.70), mix(k, vec3(1.0, 1.0, 1.0), 0.25), step(lc.y, p.y))
let near = 0.45 * exp(-max(ld, 0.0) / (1.1 * px)) + 0.16 * exp(-max(ld, 0.0) / (3.0 * px))
let ha = clamp(near, 0.0, 1.0) * (1.0 - la) * step(0.0, ld)
res = Finish.over(res, vec4(k * ha, ha) * inside)
res = Finish.over(res, vec4(hot * la, la) * inside)
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 sy_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 sy_push_well(p, self.rect_size, self.rect_pos, px)
}
mod.widgets.SegmentedControlFlat.draw_text.color = #d4d2cc
mod.widgets.SegmentedControlFlat.draw_text_selected.color = #8fd2f8
mod.widgets.SegmentedControlFlat.palette.on_neutral_container = #8fd2f8
// The tab strip: every tab a keycap; the one in use pushed in, lamp lit.
mod.widgets.Tabs.height = 26.0
mod.widgets.Tabs.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
// an engraved rule along the strip's foot: dark over a pale lip
let yb = Finish.snap(o.y + self.rect_size.y, px) - o.y
let dk = step(yb - 2.0 * px, p.y) * step(p.y, yb - px) * 0.8
let lt = step(yb - px, p.y) * step(p.y, yb) * 0.4
let bz = sy_bezel()
return Finish.over(vec4(bz * dk, dk), vec4(vec3(1.0, 0.925, 0.690) * lt, lt))
}
mod.widgets.Tabs.draw_tab.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * self.rect_size
return sy_segment(p - vec2(1.0, 0.0), self.rect_size - vec2(2.0, 3.0 * px), self.rect_pos + vec2(1.0, 0.0), px, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
}
mod.widgets.Tabs.draw_tab_hover.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * self.rect_size
return sy_segment(p - vec2(1.0, 0.0), self.rect_size - vec2(2.0, 3.0 * px), self.rect_pos + vec2(1.0, 0.0), px, 1.0, 0.0, 0.0, 0.0, 0.0, 0.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
return sy_segment(p - vec2(1.0, 0.0), self.rect_size - vec2(2.0, 3.0 * px), self.rect_pos + vec2(1.0, 0.0), px, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0)
}
mod.widgets.Tabs.draw_sep.pixel = fn() {
return vec4(0.0, 0.0, 0.0, 0.0)
}
// The close cross stands on a cap, the plus on the paint.
mod.widgets.Tabs.draw_mark.color = #d4d2cc
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(sy_seg(p, c - u, c + u), sy_seg(p, c - v, c + v)) - mix(0.5, 0.75, self.plus)
let k = Finish.cover(dd, px) * self.color.a
let ink = mix(self.color.rgb, vec3(0.110, 0.102, 0.078), self.plus)
return vec4(ink * k, k)
}
mod.widgets.Tabs.draw_text.text_style = mod.theme.font_bold{font_size: 8.25}
mod.widgets.Tabs.draw_text_active.text_style = mod.theme.font_bold{font_size: 8.25}
mod.widgets.Tabs.draw_text.color = #d4d2cc
mod.widgets.Tabs.draw_text_active.color = #8fd2f8
// ---------------------------------------------------------------------------
// PROGRESS. The slider's groove in the paint, nine device pixels; the value
// is the slider's own three device-pixel lit line, continuous. The unknown
// value cannot move (a face runs no clock), so it is shown as still 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 = 2.0
let d = sy_rbox(p, c, h, r)
let dis = step(4.5, self.intent)
var ink = sy_fill()
if self.intent > 0.5 {
ink = self.fill_color().rgb
}
ink = mix(ink, sy_off(), dis)
let col = sy_well(p, c, h, r, px, vec3(0.071, 0.067, 0.063), 1.6)
let a = Finish.cover(d, px) * self.track
var out = sy_lip(p, c, h, r, px, 0.45 * self.track)
out = Finish.over(out, vec4(col * a, a))
let row = floor((p.y - top) / px)
let r2 = step(2.5, row) * step(row, 3.5)
let r3 = step(3.5, row) * step(row, 4.5)
let r4 = step(4.5, row) * step(row, 5.5)
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.45 * step(p.x, lx1)
}
var lit = sy_rim_hi() * r2 + sy_fill() * r3 + sy_rim_lo() * r4
if self.intent > 0.5 {
lit = mix(ink, vec3(1.0, 1.0, 1.0), 0.35) * r2 + ink * r3 + ink * 0.55 * r4
}
lit = mix(lit, sy_off() * (1.0 + 0.2 * r2), dis)
let la = (r2 + r3 + r4) * on
out = Finish.over(out, vec4(lit * la, la))
return out * self.opacity
}
// ---------------------------------------------------------------------------
// LEVEL METERS. A ladder of cells in a black well: green to -6 dB, amber to
// -2, red above; unlit cells keep a tenth of their light. The over lamp is
// a red bead, dark until it latches.
mod.widgets.LevelMeter.height = 6.0
mod.widgets.LevelMeterColumn.width = 6.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 = sy_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)
let pitch = 4.0
let cell = fract((a - pad) / pitch)
let in_cell = step(cell, 3.0 / pitch)
let bt = b - (c.y - h.y)
let in_b = step(pad, bt) * step(bt, 2.0 * h.y - pad)
let cell_row = in_cell * in_b * step(pad, a) * step(a, pad + span)
let lit = step(a, pad + span * self.level) * cell_row
let zone = mix(mix(vec3(0.490, 0.860, 0.330), vec3(1.0, 0.690, 0.160), step(0.70, share)), vec3(0.980, 0.330, 0.260), step(0.88, share))
var col = sy_well(q, c, h, r, px, vec3(0.063, 0.059, 0.055), 1.2)
col = mix(col, mix(col, zone, 0.12), cell_row)
col = mix(col, zone, lit)
if self.hold > 0.002 {
let at = pad + span * self.hold
let hm = step(abs(a - at), 1.5) * in_b
col = mix(col, zone, hm)
}
let ca = Finish.cover(d, px)
var out = Finish.over(sy_lip(q, c, h, r, px, 0.45), 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, sy_bead(p, lc, min(across * 0.5 - px, 3.0), px, vec3(0.980, 0.330, 0.260), step(0.5, self.over)))
}
return out * self.opacity
}
// ---------------------------------------------------------------------------
// SCROLL BARS. An 8 point charcoal well with a 6 point gunmetal thumb in it;
// a held thumb shows a lit 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 sc = sh
let sd = sy_rbox(q, sc, sh, 3.0)
let wall = 1.0 - smoothstep(0.0, 2.0, b)
var col = mix(vec3(0.110, 0.106, 0.102), vec3(0.040, 0.038, 0.035), 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 = sy_rbox(q, tc, th, 3.0)
let tt = clamp((b - (tc.y - th.y)) / 6.0, 0.0, 1.0)
var face = mix(vec3(0.330, 0.330, 0.326), vec3(0.200, 0.200, 0.196), tt)
face = face + g3(0.04 * self.hover)
face = mix(face, vec3(0.480, 0.476, 0.460), Finish.band(td, 0.0, px, px) * (1.0 - smoothstep(0.3, 0.6, tt)))
face = mix(face, vec3(0.040, 0.038, 0.035), Finish.band(td, 0.0, px, px) * smoothstep(0.4, 0.7, tt))
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, sy_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 panel is a painted plate in a black rubber frame five
// points wide, rounded like a bead and lit from above, held by screws. The
// raised plate carries the frame's short shadow inside it; the sunken one is
// a floor set deeper, the frame's shadow falling far over it. `screws` 4
// puts one in each corner, 2 the two on the right, 0 none.
let sy_frame = fn(p: vec2, size: vec2, o: vec2, px: float, r: float, sunken: float, screws: 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 = sy_rbox(p, c, h, r)
let ink = sy_shadow_ink()
var out = vec4(0.0, 0.0, 0.0, 0.0)
let soft = Finish.drop(p, c, h, r, vec2(0.3, 1.8), 1.6) * 0.55
out = Finish.over(out, vec4(ink * soft, soft))
let con = Finish.drop(p, c, h, r, vec2(0.0, px), px * 1.2) * 0.7
out = Finish.over(out, vec4(ink * con, con))
// the frame: a rubber bead across its width
let fw = 5.0
let hin = h - vec2(fw, fw)
let rin = max(r - fw, 1.5)
let din = sy_rbox(p, c, hin, rin)
let e = 0.5
let gx = sy_rbox(p + vec2(e, 0.0), c, h, r) - sy_rbox(p - vec2(e, 0.0), c, h, r)
let gy = sy_rbox(p + vec2(0.0, e), c, h, r) - sy_rbox(p - vec2(0.0, e), c, h, r)
let n = normalize(vec2(gx, gy) + vec2(0.0001, 0.0001))
let u = clamp(-d / fw, 0.0, 1.0)
let facing = dot(n, vec2(-0.3, -0.95)) * cos(u * PI)
var rub = vec3(0.105, 0.100, 0.094) + g3(0.11 * facing)
rub = rub + g3(0.10 * pow(max(facing, 0.0), 6.0))
rub = rub + g3(sy_grain((p + o) / px, 0.012))
// the plate inside
let q = p + o
var plate = sy_paint(q, px)
plate = plate * mix(1.0, 0.84, sunken)
let insh = Finish.inset(p, c, hin, rin, mix(vec2(0.0, 1.2), vec2(0.4, 4.0), sunken), mix(1.6, 4.0, sunken))
plate = mix(plate, ink * 1.5, clamp(insh, 0.0, 1.0) * mix(0.45, 0.90, sunken))
// screws, six points in from the frame's inner corners
let sr = 3.2
let sx = hin.x - 5.5
let sy = hin.y - 5.5
let tl = sy_screw(p, c + vec2(-sx, -sy), sr, px, 0.35)
let tr = sy_screw(p, c + vec2(sx, -sy), sr, px, 1.10)
let bl = sy_screw(p, c + vec2(-sx, sy), sr, px, 0.80)
let br = sy_screw(p, c + vec2(sx, sy), sr, px, 0.15)
let four = step(3.0, screws)
let two = step(1.0, screws)
var pl = vec4(plate, 1.0)
pl = Finish.over(pl, tl * four)
pl = Finish.over(pl, bl * four)
pl = Finish.over(pl, tr * two)
pl = Finish.over(pl, br * two)
var col = mix(rub, pl.rgb, Finish.cover(din, px))
// a dark seam where the plate meets the frame
col = mix(col, vec3(0.030, 0.028, 0.025), Finish.ring_out(din, 0.0, px, px) * 0.8)
col = mix(col, vec3(0.030, 0.028, 0.025), Finish.band(d, 0.0, px, px) * 0.8)
let a = Finish.cover(d, px)
return Finish.over(out, vec4(col * a, a))
}
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, 6.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 = 6.0
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
return sy_frame(p, self.rect_size, self.rect_pos, px, 6.0, self.sunken, 4.0)
}
// The card is the framed plate too, held by two screws on its right.
mod.widgets.Card.radius = 6.0
mod.widgets.Card.padding = mod.turtle.Inset{left: 14.0 right: 14.0 top: 10.0 bottom: 10.0}
mod.widgets.Card.draw_bg.vertex = fn() {
let g = grow_vertex(self.rect_pos, self.rect_size, 6.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 = 6.0
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let out = sy_frame(p, self.rect_size, self.rect_pos, px, 6.0, 0.0, 2.0)
let d = sy_rbox(p, self.rect_size * 0.5, self.rect_size * 0.5 - vec2(5.0, 5.0), 1.5)
let l = Finish.cover(d, px) * (0.04 * self.hover - 0.05 * 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 = sy_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. Rows of 22 points on the paint over an engraved hairline; a hovered
// row darkens a little; the chosen row is a strip sunk into the paint, its
// top wall in shadow, with a lit blue bar at its head.
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
let ink = sy_shadow_ink()
let lay = (self.hover * 0.07 + self.down * 0.08) * live
var out = vec4(ink * lay, lay)
if self.selected > 0.5 {
let h = (hi - lo) * 0.5 - vec2(5.0, 1.0)
let c = lo + (hi - lo) * 0.5 - vec2(5.0, 0.0)
let d = sy_rbox(p, c, h, 2.0)
let sh = Finish.inset(p, c, h, 2.0, vec2(0.0, 1.2), 1.4)
let sa = Finish.cover(d, px) * (0.16 + 0.55 * clamp(sh, 0.0, 1.0))
out = Finish.over(out, vec4(ink * sa, sa))
let low = smoothstep(-0.5, 0.5, p.y - c.y)
let li = Finish.ring_out(d, 0.0, px, px) * low * 0.45
out = Finish.over(out, vec4(vec3(1.0, 0.925, 0.690) * li, li))
let bar = step(lo.x + px, p.x) * step(p.x, lo.x + 3.0 * px) * step(lo.y + 4.0, p.y) * step(p.y, hi.y - 4.0) * live
let k = sy_acc()
out = Finish.over(out, vec4(k * bar, bar))
let ba = exp(-max(p.x - lo.x - 3.0 * px, 0.0) / (1.5 * px)) * step(lo.x + 3.0 * px, p.x) * step(lo.y + 4.0, p.y) * step(p.y, hi.y - 4.0) * 0.3 * live
out = Finish.over(out, vec4(k * ba, ba))
}
// the engraved hairline under the row
let yb = hi.y
let dk = step(yb - px, p.y) * step(p.y, yb) * 0.20
return Finish.over(out, vec4(ink * dk, dk))
}
mod.widgets.ListItem.draw_text.color = #1c1a14
// The open list of a drop-down: a gunmetal sheet; the row under the pointer
// is a well with a lit bar at its head, the chosen row a small lit dot.
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 = sy_rbox(p, c, c, 3.0)
let t = clamp(self.pos.y, 0.0, 1.0)
var col = mix(vec3(0.240, 0.240, 0.238), vec3(0.190, 0.190, 0.188), t)
col = col + g3(sy_knurl(p + o, px) * 0.02 + sy_grain((p + o) / px, 0.010))
col = mix(col, vec3(0.420, 0.416, 0.400), Finish.band(d, px, 2.0 * px, px) * step(p.y, 4.0))
col = mix(col, sy_bezel(), Finish.band(d, 0.0, px, px))
let a = Finish.cover(d, px)
return vec4(col * 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 = sy_rbox(p, c, h, 2.0)
let live = 1.0 - self.disabled
let hv = self.hover * live
let col = sy_well(p, c, h, 2.0, px, vec3(0.110, 0.106, 0.102), 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 + 3.0 * px) * step(4.0, p.y) * step(p.y, self.rect_size.y - 4.0) * hv
let k = sy_acc()
out = Finish.over(out, vec4(k * bar, bar))
let dt = Finish.cover(sy_rbox(p, vec2(6.0, c.y), vec2(1.2, 1.2), 0.3), px) * self.active
return Finish.over(out, vec4(k * dt, dt))
}
// The dock's tabs: the bar is a strip of rubber with an engraved foot; the
// tab in use is a gunmetal face 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 = sy_rbox(p, c, h, 3.0)
var face = vec3(0.260, 0.260, 0.256) + g3(sy_knurl(p + o, px) * 0.03 + sy_grain((p + o) / px, 0.012))
face = mix(face, vec3(0.440, 0.436, 0.420), Finish.band(d, px, 2.0 * px, px) * step(p.y, lo.y + 3.0))
face = mix(face, sy_bezel(), Finish.band(d, 0.0, px, px))
let a = Finish.cover(d, px) * self.active
let lift = Finish.cover(d, px) * 0.05 * 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 col = vec3(0.120, 0.116, 0.110) + g3(sy_grain((p + self.rect_pos) / px, 0.010))
col = mix(col, vec3(0.030, 0.028, 0.025), step(yb - px, p.y))
return vec4(col, 1.0)
}
// ---------------------------------------------------------------------------
// CHIPS AND BADGES. A chip is the 20 point keycap; a chosen or coloured chip
// is lit in its own colour. A badge is a small charcoal window with a lit
// 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, 6.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 = 6.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 sy_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 = sy_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, vec3(0.86, 0.84, 0.78), 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.25, face, smoothstep(0.0, 3.0, yi))
face = face * (1.0 + 0.08 * self.hover - 0.12 * self.down)
face = mix(face, sy_bezel(), Finish.band(d, 0.0, 2.0 * px, px))
let ink = sy_shadow_ink()
let con = Finish.drop(q, c, h, 2.0, vec2(0.0, px), px * 1.2) * 0.75
var out = vec4(ink * con, con)
let a = Finish.cover(d, px)
out = Finish.over(out, vec4(face * a, a))
return Finish.over(out, sy_focus(d, px, self.keyed))
}
mod.widgets.ChipFlat.palette.neutral_container = #3a3a3b
mod.widgets.ChipFlat.palette.on_neutral_container = #e4e2dc
mod.widgets.Chip.palette.neutral_container = #3a3a3b
mod.widgets.Chip.palette.on_neutral_container = #e4e2dc
mod.widgets.BadgePalette.neutral = #1e1e20
mod.widgets.BadgePalette.on_neutral = #56b8f0
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 = sy_rbox(p, c, h, r)
let col = sy_well(p, c, h, r, px, vec3(0.110, 0.110, 0.114), 2.0)
let a = Finish.cover(d, px) * self.color.a
var out = sy_lip(p, c, h, r, px, 0.45)
return Finish.over(out, vec4(col * a, a))
}
// Rules between groups: an engraved line, one dark device row over a pale
// one.
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.55
let lt = step(y, p.y) * (1.0 - step(y + px, p.y)) * 0.35
let ink = sy_shadow_ink()
return Finish.over(vec4(ink * dk, dk), vec4(vec3(1.0, 0.925, 0.690) * lt, 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.55
let lt = step(x, p.x) * (1.0 - step(x + px, p.x)) * 0.35
let ink = sy_shadow_ink()
return Finish.over(vec4(ink * dk, dk), vec4(vec3(1.0, 0.925, 0.690) * lt, lt))
}
// ---------------------------------------------------------------------------
// THE PLATE under everything: the window's own ground.
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)
return vec4(sy_ground(self.pos * self.rect_size, self.rect_size, px), 1.0)
}
// ---------------------------------------------------------------- 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: 44.0 right: 3.0 top: 0.0 bottom: 0.0}
mod.widgets.ToggleSlide.label_walk.margin = mod.turtle.Inset{left: 44.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 = 26.0
mod.widgets.ToggleSlide.min_height = 26.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.25)
mod.widgets.ToggleSlide.draw_bg.pill_aspect = uniform(2.25)
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 sy_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 sy_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 sy_led_lamp_hue = fn(ink: vec3, vivid: float) -> vec3 {
let f = sy_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 sy_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 = sy_led_lamp_full(ink)
let hue = sy_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 = sy_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 black rubber ring, as its knobs are.
// 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 = sy_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.863, 0.667, 0.220), max(room - 0.5, 2.0), vec3(0.10, 0.10, 0.10), 1.0)
return res * self.opacity
}
true