makepad/widgets/sheets/themes/clay/widgets.splash

2938 lines
151 KiB
Text

use mod.prelude.widgets_internal.*
// Colourful plasticine under a soft light: a big sky from above and a key
// light from the upper left, wrapped far round, so a shade is the clay's own
// colour a little deeper and richer, never grey. Every part is a lump of
// modelling clay pressed onto a pink clay board: a pill, a snake, a ball, a tile.
// Each lump is matte with a broad soft sheen, its edge rolled thick and
// rounded, its outline a little uneven, its face puffed, with the lumps,
// smudges, fingerprints, air pits and tool marks of the hands that made it,
// different on every part. A lump throws a soft crease all round its foot
// and a soft, light shadow down and to the right in the ground's own pink,
// gone within half the gap to its neighbour. Pressed, a lump is squashed into the slab: its shadow goes and
// the slab's edge shades its top. A chosen part is a pocket pressed into the
// clay, its word lit. Every kind of control has its colour, as the hands
// that made the reference picked one plasticine per kind.
// A ROW OF FIELDS IS ONE HEIGHT. Left to themselves the library's fields each
// come out at a size of their own, so the box metrics are stated ONCE here
// and handed to each field below; a field added to the library later joins
// the row by being named here. A field clips its own text, so it draws its
// slab and the slab's shadow inside its own box: the box is 28 and the slab
// in it 25, the height of a button's lump with its shadow.
let field_height = 28.0
let field_pad_v = mod.theme.mspace_1.top
let field_radius = 7.0
// A hairline, for the few stock fields the faces below do not redraw.
let field_border = 0.34
let field_room = fn(w) {
w.min_height = field_height
w.padding.top = field_pad_v
w.padding.bottom = field_pad_v
}
let field_face = fn(d) {
d.border_radius = field_radius
d.border_size = field_border
}
// A field that lays its own parts out inside its frame takes the height
// alone: a padding there displaces the line instead of insetting it.
let field_frame = fn(w) {
w.min_height = field_height
}
field_room(mod.widgets.TextInput)
field_face(mod.widgets.TextInput.draw_bg)
field_room(mod.widgets.TextInputFlat)
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 imposes no height of its own.
mod.widgets.WellInput.min_height = 0.0
mod.widgets.Window.pass.clear_color = #ffabe1
// Plain views stop clipping, so a lump'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
// ---------------------------------------------------------------------------
// GEOMETRY. Distances in points, negative inside, y down; `px` is one device
// pixel in points. Every texture is laid in the part's own points, never the
// window's, so nothing moves on a part as its page scrolls.
let g3 = fn(v: float) -> vec3 {
return vec3(v, v, v)
}
let cl_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 rounded box with a radius per corner (tl, tr, br, bl).
let cl_rbox4 = fn(p: vec2, c: vec2, h: vec2, r4: vec4) -> float {
let q0 = p - c
let rt = mix(r4.y, r4.x, step(q0.x, 0.0))
let rb = mix(r4.z, r4.w, step(q0.x, 0.0))
let r = clamp(mix(rb, rt, step(q0.y, 0.0)), 0.0, min(h.x, h.y))
let q = abs(q0) - h + vec2(r, r)
return min(max(q.x, q.y), 0.0) + length(max(q, vec2(0.0, 0.0))) - r
}
// The corners a button group asks for: a corner written as 0 is a square
// join, any other the family's radius.
let cl_corners = fn(tl: float, tr: float, br: float, bl: float, r: float) -> vec4 {
return vec4(mix(r, step(0.5, tl) * r, step(0.0, tl)), mix(r, step(0.5, tr) * r, step(0.0, tr)), mix(r, step(0.5, br) * r, step(0.0, br)), mix(r, step(0.5, bl) * r, step(0.0, bl)))
}
let cl_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)
}
let cl_grow = 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)
}
// A triangle hw wide either side, 2 hh tall, apex up (dir -1) or down (1).
let cl_tri = fn(p: vec2, c: vec2, hw: float, hh: float, dir: float) -> float {
let y = (p.y - c.y) * dir
let e = (abs(p.x - c.x) * 2.0 * hh + hw * (y - hh)) / sqrt(4.0 * hh * hh + hw * hw)
return max(-hh - y, e)
}
// Which way a rounded box's outline faces at `p`, for the roll of its edge.
// Taken with a radius at least as wide as the roll, so the roll turns round
// a corner without a mitre.
let cl_grad = fn(p: vec2, c: vec2, h: vec2, r: float) -> vec2 {
let rr = clamp(r, 0.0, min(h.x, h.y))
let s = vec2(mix(-1.0, 1.0, step(c.x, p.x)), mix(-1.0, 1.0, step(c.y, p.y)))
let q = abs(p - c) - h + vec2(rr, rr)
if q.x > 0.0 && q.y > 0.0 {
return normalize(q) * s
}
if q.x > q.y {
return vec2(s.x, 0.0)
}
return vec2(0.0, s.y)
}
// ---------------------------------------------------------------------------
// THE CLAY.
// The outline a hand leaves: the edge wanders in and out by up to `amp`
// points, slowly, with a finer wobble over it.
let cl_wob = fn(q: vec2, seed: float, amp: float) -> float {
let o = vec2(seed * 13.17, seed * 7.31)
return ((Finish.noise(q * 0.075 + o) - 0.5) * 1.6 + (Finish.noise(q * 0.21 + o * 1.9 + vec2(3.1, 5.7)) - 0.5) * 0.7) * amp
}
// The lumps in a face: the slope of a soft height field, as a tilt of the
// face's normal. A broad unevenness about twenty points across, and the
// long soft strokes a thumb or a spatula leaves when it smooths the clay,
// running one way across the part.
let cl_bump = fn(q: vec2, seed: float) -> vec2 {
let o = vec2(seed * 13.7, seed * 29.1)
let e = 0.8
let f1 = 0.05
let a0 = Finish.noise(q * f1 + o)
let ax = Finish.noise((q + vec2(e, 0.0)) * f1 + o)
let ay = Finish.noise((q + vec2(0.0, e)) * f1 + o)
let ang = seed * 6.2831853
let cs = cos(ang)
let sn = sin(ang)
let f2 = vec2(0.035, 0.17)
let o2 = o * 1.73 + vec2(4.1, 7.9)
let u0 = vec2(q.x * cs + q.y * sn, q.y * cs - q.x * sn)
let ux = u0 + vec2(cs, -sn) * e
let uy = u0 + vec2(sn, cs) * e
let b0 = Finish.noise(u0 * f2 + o2)
let bx = Finish.noise(ux * f2 + o2)
let by = Finish.noise(uy * f2 + o2)
// and the fine tooth of the clay itself, a point and a half across
let f3 = 0.65
let o3 = o * 0.71 + vec2(1.3, 2.9)
let qt = vec2(q.x * 0.866 + q.y * 0.5, q.y * 0.866 - q.x * 0.5)
let c0 = Finish.noise(qt * f3 + o3)
let cx = Finish.noise((qt + vec2(0.866, -0.5) * 0.4) * f3 + o3)
let cy = Finish.noise((qt + vec2(0.5, 0.866) * 0.4) * f3 + o3)
// and soft dents a thumb-tip across, where the clay was pushed into shape
let f4 = 0.15
let o4 = o * 0.37 + vec2(8.3, 1.7)
let qd = vec2(q.x * 0.6 - q.y * 0.8, q.y * 0.6 + q.x * 0.8)
let d0 = Finish.noise(qd * f4 + o4)
let dx = Finish.noise((qd + vec2(0.6, 0.8) * e) * f4 + o4)
let dy = Finish.noise((qd + vec2(-0.8, 0.6) * e) * f4 + o4)
return (vec2(ax - a0, ay - a0) * 1.35 + vec2(bx - b0, by - b0) * 0.30 + vec2(dx - d0, dy - d0) * 0.55) / e + vec2(cx - c0, cy - c0) * 0.022 / 0.4
}
// A fingerprint: whorled ridges a point and a half apart in an oval `sz`
// across round `fc`, turned by `ang`, fading at its edge and broken up so
// only part of it took. Each ridge is lit on the flank that faces the light.
// Returns the shading it adds, -1..1.
let cl_print = fn(q: vec2, fc: vec2, sz: float, ang: float, seed: float) -> float {
let dq = q - fc
let cs = cos(ang)
let sn = sin(ang)
let u = vec2(dq.x * cs + dq.y * sn, dq.y * cs - dq.x * sn) / max(sz, 1.0)
let e = vec2(u.x * 1.1, u.y)
let rad = length(e)
if rad > 1.0 {
return 0.0
}
let o = vec2(seed * 5.3, seed * 9.1)
let warp = (Finish.noise(u * 3.5 + o) - 0.5) * 0.22
let ph = (rad + warp) * sz / 2.2 + atan2(e.y, e.x) * 0.0795
let env = (1.0 - smoothstep(0.5, 1.0, rad)) * smoothstep(0.2, 0.6, Finish.noise(u * 1.8 + o * 1.7 + vec2(2.3, 0.7)))
let nd = dq / max(length(dq), 0.001)
let side = dot(nd, vec2(-0.47, -0.88))
return (sin(ph * 6.2831853) * side + cos(ph * 6.2831853) * 0.3) * env
}
// The same print pressed with ridges `pitch` points apart, softer and more
// broken: for a part wide and curved enough that the fine ridges would beat
// against its own shading.
let cl_print_c = fn(q: vec2, fc: vec2, sz: float, ang: float, seed: float, pitch: float) -> float {
let dq = q - fc
let cs = cos(ang)
let sn = sin(ang)
let u = vec2(dq.x * cs + dq.y * sn, dq.y * cs - dq.x * sn) / max(sz, 1.0)
let e = vec2(u.x * 1.1, u.y)
let rad = length(e)
if rad > 1.0 {
return 0.0
}
let o = vec2(seed * 5.3, seed * 9.1)
let warp = (Finish.noise(u * 2.2 + o) - 0.5) * 0.30
let ph = (rad + warp) * sz / pitch + atan2(e.y, e.x) * 0.0795
let env = (1.0 - smoothstep(0.35, 1.0, rad)) * smoothstep(0.25, 0.7, Finish.noise(u * 1.4 + o * 1.7 + vec2(2.3, 0.7)))
let nd = dq / max(length(dq), 0.001)
let side = dot(nd, vec2(-0.47, -0.88))
return (sin(ph * 6.2831853) * side * 0.8 + cos(ph * 6.2831853) * 0.2) * env
}
// The marks on a part `ext` either side of its middle: one fingerprint,
// partly taken; an air pit here and there, its upper wall in shade; and on
// about half the parts one short score of a modelling tool. All of it is
// placed by `seed`, so no two parts of different size carry the same marks.
let cl_marks = fn(q: vec2, ext: vec2, seed: float, pw: float) -> float {
let hs = vec2(Finish.hash(vec2(seed * 91.3, 3.7)), Finish.hash(vec2(seed * 47.9, 11.3)))
let fc = (hs - vec2(0.5, 0.5)) * ext * 1.2
let sz = clamp(min(ext.x, ext.y) * 1.7, 8.0, 24.0)
var s = cl_print(q, fc, sz, hs.x * 6.2831853, seed) * 0.06 * pw
let sc = 2.8
let og = vec2(seed * 7.13, seed * 3.71)
let gq = q / sc + og
let cell = floor(gq)
let f = gq - cell
let pc = vec2(0.25, 0.25) + vec2(Finish.hash(cell + vec2(5.1, 1.3)), Finish.hash(cell + vec2(2.7, 8.4))) * 0.5
let dv = (f - pc) * sc
let dist = length(dv)
let pr = 0.35 + 0.30 * Finish.hash(cell + vec2(9.7, 4.4))
let bowl = (1.0 - smoothstep(pr * 0.3, pr, dist)) * step(0.92, Finish.hash(cell))
s = s - dot(dv / max(dist, 0.001), vec2(-0.47, -0.88)) * bowl * 0.14 - bowl * 0.03
let hk = Finish.hash(vec2(seed * 17.9, 23.1))
if hk > 0.35 {
let a0 = (vec2(Finish.hash(vec2(seed * 5.3, 1.9)), Finish.hash(vec2(seed * 8.1, 4.2))) - vec2(0.5, 0.5)) * ext * 1.4
let ang = hk * 12.0
let len = clamp(min(ext.x, ext.y) * 1.3, 5.0, 26.0)
let ba = vec2(cos(ang), sin(ang)) * len
let k = clamp(dot(q - a0, ba) / max(dot(ba, ba), 0.0001), 0.0, 1.0)
let off = q - a0 - ba * k
let d2 = length(off)
let prof = (1.0 - smoothstep(0.15, 0.75, d2)) * smoothstep(0.0, 0.25, k) * (1.0 - smoothstep(0.7, 1.0, k))
s = s - dot(off / max(d2, 0.001), vec2(-0.47, -0.88)) * prof * 0.13
}
return s
}
// THE LIGHT ON CLAY, as soft as a big sky over a key light from the upper
// left. A shade is the clay's own colour, deeper and a little richer (a pink
// shades to rose, a blue to ultramarine), never toward grey or black, and at
// its darkest only about a fifth to a third darker than the lit side.
let cl_deep = fn(col: vec3) -> vec3 {
let cc = max(col, vec3(0.0001, 0.0001, 0.0001))
return pow(cc, vec3(1.3, 1.3, 1.3)) * 0.84
}
// The form lit at normal `n`: the key light wrapped far round, so the side
// away from it stays light; a sky from above, so tops are full and a touch
// paler; past full light a little paler still; and a faint lighter rim where
// the form turns away from the eye.
let cl_light = fn(n: vec3, col: vec3) -> vec3 {
let key = clamp((dot(n, vec3(-0.30, -0.57, 0.77)) + 0.55) / 1.55, 0.0, 1.0)
let sky = clamp(n.z, 0.0, 1.0)
let f = (key * 0.7 + (0.5 + 0.5 * sky) * 0.3 - 0.15) / 0.745
var c = mix(cl_deep(col), col, clamp(f, 0.0, 1.0))
c = c + (mix(col, vec3(1.0, 1.0, 1.0), 0.6) - c) * clamp((f - 1.0) * 3.0, 0.0, 0.45)
let rim = pow(1.0 - sky, 2.5) * 0.12
return mix(c, mix(col, vec3(1.0, 1.0, 1.0), 0.35), rim)
}
// Fine detail on a lit colour `c`: lumps, dents, prints and strokes, `d` the
// change in light they make (as a fraction). Lighter toward a paler colour;
// darker toward the colour's own deep shade, never toward black.
let cl_detail = fn(c: vec3, d: float) -> vec3 {
if d >= 0.0 {
return c * (1.0 + d * 0.8) + (vec3(1.0, 1.0, 1.0) - c) * (d * 0.2)
}
let dc = cl_deep(c)
let k = max(1.0 - Finish.lum(dc) / max(Finish.lum(c), 0.001), 0.05)
return mix(c, dc, clamp(-d / k, 0.0, 1.5))
}
// A shadow, premultiplied: the ground's pink deeper and richer, laid at `a`.
let cl_sh = fn(a: float) -> vec4 {
return vec4(0.58 * a, 0.28 * a, 0.46 * a, a)
}
// THE SKIN of a lump, premultiplied: `d` its outline's distance, `g` the way
// its outline faces, `w` how wide the rolled edge is, `q` the point in the
// part's own points (its texture), `tilt` the puff of its face, `col` the
// clay, `ext` its half size (where its marks go), `detail` how much of the
// texture shows. Lit from the upper left: a broad shoulder catching the
// light, the far rim dark and a deeper, richer colour, a soft wide sheen,
// the mottle of a hand-mixed colour and one device pixel of grain.
let cl_skin = fn(d: float, g: vec2, w: float, q: vec2, tilt: vec2, col: vec3, seed: float, px: float, detail: float, ext: vec2) -> vec4 {
let a = Finish.cover(d, px)
if a < 0.002 {
return vec4(0.0, 0.0, 0.0, 0.0)
}
let ww = max(w, 0.5)
let u = clamp((ww + d) / ww, 0.0, 1.0)
let z = sqrt(max(1.0 - u * u, 0.02))
let bump = cl_bump(q, seed) * detail
let nf = normalize(vec3(g.x * u + tilt.x * z, g.y * u + tilt.y * z, z))
let n = normalize(vec3(g.x * u + (tilt.x + bump.x) * z, g.y * u + (tilt.y + bump.y) * z, z))
let lk = vec3(-0.30, -0.57, 0.77)
let o = vec2(seed * 31.7, seed * 17.3)
let base = col * (1.0 + (Finish.fbm(q * 0.05 + o) - 0.5) * 0.12 * detail)
// the form in the soft light; the lumps, dents and marks over it
var c3 = cl_light(nf, base)
c3 = cl_detail(c3, (dot(n, lk) - dot(nf, lk)) * 0.53 + cl_marks(q, ext, seed, 1.0) * detail)
// a soft matte bloom on the shoulder, not a shine
let sp = pow(max(dot(n, vec3(-0.159, -0.303, 0.940)), 0.0), 3.0) * 0.025
c3 = c3 + mix(vec3(1.0, 1.0, 1.0), base, 0.55) * sp
c3 = c3 + g3(Finish.grain(q / px, 0.010))
return vec4(clamp(c3, vec3(0.0, 0.0, 0.0), vec3(1.0, 1.0, 1.0)) * a, a)
}
// A lump: the rounded box (c, h, r), its outline wandering by `wob` points,
// its edge rolled over `w`, its face puffed by `puff`.
let cl_lump = fn(p: vec2, c: vec2, h: vec2, r: float, w: float, wob: float, puff: float, col: vec3, seed: float, px: float, detail: float) -> vec4 {
let q = p - c
let d = cl_rbox(p, c, h, r) + cl_wob(q, seed, wob)
let g = cl_grad(p, c, h, max(r, w))
let tilt = vec2(q.x / max(h.x, 1.0), q.y / max(h.y, 1.0)) * puff
return cl_skin(d, g, w, q, tilt, col, seed, px, detail, h)
}
// The same with a corner per side, for the joins of a button group.
let cl_lump4 = fn(p: vec2, c: vec2, h: vec2, r4: vec4, w: float, wob: float, puff: float, col: vec3, seed: float, px: float, detail: float) -> vec4 {
let q = p - c
let r = max(max(r4.x, r4.y), max(r4.z, r4.w))
let d = cl_rbox4(p, c, h, r4) + cl_wob(q, seed, wob)
let g = cl_grad(p, c, h, max(r, w))
let tilt = vec2(q.x / max(h.x, 1.0), q.y / max(h.y, 1.0)) * puff
return cl_skin(d, g, w, q, tilt, col, seed, px, detail, h)
}
// A ball of clay pressed down a little: round, its top flattened by `flat`
// (1 a full dome, less a wider plateau).
let cl_ball = fn(p: vec2, c: vec2, rad: float, flat: float, col: vec3, seed: float, px: float, detail: float, wob: float) -> vec4 {
let q = p - c
let d = length(q) - rad + cl_wob(q, seed, wob)
let g = q / max(length(q), 0.0001)
return cl_skin(d, g, rad * flat, q, q / max(rad, 1.0) * 0.08, col, seed, px, detail, vec2(rad, rad))
}
// A snake rolled between the palms from `a` to `b`, `rad` thick, its girth
// swelling and thinning a little along it.
let cl_snake = fn(p: vec2, a: vec2, b: vec2, rad: float, col: vec3, seed: float, px: float, detail: float) -> vec4 {
let pa = p - a
let ba = b - a
let bl = max(length(ba), 0.0001)
let k = clamp(dot(pa, ba) / (bl * bl), 0.0, 1.0)
let off = pa - ba * k
let dist = length(off)
let along = k * bl
let girth = rad * (1.0 + (Finish.noise(vec2(along * 0.12 + seed * 11.0, seed * 3.0)) - 0.5) * 0.16)
let q = vec2(along, dot(off, vec2(-ba.y, ba.x) / bl))
return cl_skin(dist - girth, off / max(dist, 0.0001), girth, q, vec2(0.0, 0.0), col, seed, px, detail, vec2(bl * 0.5 + rad, rad))
}
// A snake bent once: from `a` to `b` to `e` (a check mark, a chevron).
let cl_hook = fn(p: vec2, a: vec2, b: vec2, e: vec2, rad: float, col: vec3, seed: float, px: float) -> vec4 {
let pa = p - a
let ba = b - a
let k1 = clamp(dot(pa, ba) / max(dot(ba, ba), 0.0001), 0.0, 1.0)
let o1 = pa - ba * k1
let pb = p - b
let eb = e - b
let k2 = clamp(dot(pb, eb) / max(dot(eb, eb), 0.0001), 0.0, 1.0)
let o2 = pb - eb * k2
let off = mix(o1, o2, step(length(o2), length(o1)))
let dist = length(off)
let so = vec2(0.4, 0.75)
let sd = min(cl_seg(p - so, a, b), cl_seg(p - so, b, e)) - rad
let sa = (1.0 - smoothstep(-0.5, 1.3, sd)) * 0.30
var out = cl_sh(sa)
return Finish.over(out, cl_skin(dist - rad, off / max(dist, 0.0001), rad, p - b, vec2(0.0, 0.0), col, seed, px, 0.4, vec2(rad * 3.0, rad * 3.0)))
}
// A coil laid in a ring of radius `rr`, `t` thick.
let cl_ring = fn(p: vec2, c: vec2, rr: float, t: float, col: vec3, seed: float, px: float) -> vec4 {
let q = p - c
let l = length(q)
let d = abs(l - rr) - t
let g = q / max(l, 0.0001) * mix(-1.0, 1.0, step(rr, l))
let sd = abs(length(q - vec2(0.35, 0.7)) - rr) - t
let sa = (1.0 - smoothstep(-0.4, 1.2, sd)) * 0.28
var out = cl_sh(sa)
return Finish.over(out, cl_skin(d, g, t, q, vec2(0.0, 0.0), col, seed, px, 0.4, vec2(rr, rr)))
}
// What a lump throws on what is under it: a dark crease all round its foot
// and a soft shadow down and to the right, `lift` raising it, `amt` its
// strength; gone within about four points.
let cl_drop = fn(p: vec2, c: vec2, h: vec2, r: float, amt: float, lift: float) -> vec4 {
let crease = Finish.drop(p, c, h, r, vec2(0.1, 0.4), 0.9) * 0.24
let soft = Finish.drop(p, c, h, r, vec2(0.55, 1.5) * lift, 1.8 * lift) * 0.26
let a = clamp((crease + soft * (1.0 - crease)) * amt, 0.0, 1.0)
return cl_sh(a)
}
// The same under a snake from `a` to `b`.
let cl_snake_drop = fn(p: vec2, a: vec2, b: vec2, rad: float, amt: float) -> vec4 {
let d0 = cl_seg(p - vec2(0.0, 0.2), a, b) - rad
let d1 = cl_seg(p - vec2(0.5, 1.0), a, b) - rad
let crease = (1.0 - smoothstep(-0.6, 1.4, d0)) * 0.22
let soft = (1.0 - smoothstep(-rad * 0.7, 3.0, d1)) * 0.22
let al = clamp((crease + soft * (1.0 - crease)) * amt, 0.0, 1.0)
return cl_sh(al)
}
// A pocket pressed into the clay: its floor `col`, the slab's edge shading
// its upper part, its lower wall facing the light.
let cl_pocket = fn(p: vec2, c: vec2, h: vec2, r: float, col: vec3, seed: float, px: float) -> vec4 {
let q = p - c
let d = cl_rbox(p, c, h, r) + cl_wob(q, seed, 0.3)
let a = Finish.cover(d, px)
if a < 0.002 {
return vec4(0.0, 0.0, 0.0, 0.0)
}
let sh = clamp(Finish.inset(p, c, h, r, vec2(0.7, 1.3), 1.3), 0.0, 1.0)
var k = col * (1.0 + (Finish.fbm(q * 0.06 + vec2(seed * 9.0, 2.0)) - 0.5) * 0.08)
k = mix(k, cl_deep(k), clamp(sh * 1.6, 0.0, 1.3))
let g = cl_grad(p, c, h, r)
let wall = Finish.band(d, 0.0, 1.4, px) * clamp(dot(g, vec2(0.47, 0.88)), 0.0, 1.0)
k = mix(k, Finish.lift(col, 0.28), wall * 0.6)
k = k + g3(cl_marks(q, h, seed, 1.0) * 0.6 + Finish.grain(q / px, 0.008))
return vec4(clamp(k, vec3(0.0, 0.0, 0.0), vec3(1.0, 1.0, 1.0)) * a, a)
}
// A clay's colour in a state: lighter under the pointer, a shade deeper
// squashed, a dull grey with a hint of its colour when it is off.
let cl_tone = fn(col: vec3, hover: float, press: float, dis: float) -> vec3 {
var c = Finish.lift(col, 0.09 * hover)
c = c * (1.0 - 0.12 * press)
return mix(c, vec3(0.792, 0.773, 0.780), 0.85 * clamp(dis, 0.0, 1.0))
}
// Focus: a thread of sky-blue clay laid two points outside the edge.
let cl_focus = fn(d: float, px: float, focus: float) -> vec4 {
let f = Finish.cover(abs(d - 2.75) - 0.85, px) * 0.9 * focus
let lit = 1.0 - 0.25 * smoothstep(-0.85, 0.85, d - 2.75)
return vec4(0.310 * lit * f, 0.498 * lit * f, 0.847 * lit * f, f)
}
// An indicator lamp set into a part (the over lamp of a level meter): a
// small glossy bead in a socket, dark glass until it is lit.
let cl_led = fn(p: vec2, lc: vec2, rad: float, px: float, ink: vec3, lit: float, glow: float) -> vec4 {
let q = p - lc
let d = length(q) - rad
let k = clamp(length(q) / max(rad, 0.001), 0.0, 1.0)
let v = clamp(q.y / max(rad, 0.001) * 0.5 + 0.5, 0.0, 1.0)
let on = mix(min(ink + vec3(0.45, 0.45, 0.45), vec3(1.0, 1.0, 1.0)), ink * 0.85, smoothstep(0.2, 1.0, k))
let off = mix(vec3(0.16, 0.14, 0.15), vec3(0.28, 0.25, 0.26), v)
var col = mix(off, on, lit)
let hl = 1.0 - smoothstep(0.2, 0.5, length(q / max(rad, 0.001) - vec2(-0.3, -0.4)))
col = mix(col, vec3(1.0, 1.0, 1.0), hl * 0.55)
let a = Finish.cover(d, px)
let ha = glow * lit * exp(-max(d, 0.0) / 0.9) * (1.0 - a)
var out = vec4(ink * ha, ha)
let rim = Finish.ring_out(d, 0.0, 0.9, px) * 0.35
out = Finish.over(out, cl_sh(rim))
return Finish.over(out, vec4(col * a, a))
}
// ---------------------------------------------------------------------------
// BUTTONS. One face for the family, a pill of clay in the role's colour;
// `kind` picks the dressing: 0 the pill, 5 a coil of navy laid round the
// label (outline), 6 bare until touched (flatter). The quad is grown by `m`
// for the shadow and the focus thread.
let cl_button = fn(pos: vec2, size: vec2, o: vec2, px: float, hover: float, down: float, focus: float, dis: float, loading: float, anim: float, r4: vec4, kind: float, m: float, col: vec3) -> vec4 {
let p = pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let live = 1.0 - dis
let press = down * live
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 rr = max(max(r4.x, r4.y), max(r4.z, r4.w))
let seed = fract(size.x * 0.0731 + size.y * 0.1913 + kind * 0.377)
let w = min(h.y * 0.62, 7.0)
let ink = cl_tone(col, hover * live, press, dis)
var out = vec4(0.0, 0.0, 0.0, 0.0)
let d = cl_rbox4(p, c, h, r4)
if kind < 4.5 {
out = cl_drop(p, c, h, rr, mix(1.0, 0.25, press) * mix(1.0, 0.55, dis), 1.0 + 0.18 * hover * live - 0.5 * press)
var face = cl_lump4(p, c, h, r4, w, 0.35, mix(0.10, 0.03, press), ink, seed, px, 1.0)
// squashed in, the slab's edge shades its top
let sh = clamp(Finish.inset(p, c, h, rr, vec2(0.6, 1.8), 1.8), 0.0, 1.0) * 0.45 * press
let fc = face.rgb / max(face.a, 0.001)
face = vec4(mix(fc, cl_deep(fc), clamp(sh / 0.28, 0.0, 1.3)) * face.a, face.a)
out = Finish.over(out, face)
} else {
if kind < 5.5 {
// a coil of clay laid round the label; the slab shows inside it,
// lifting a shade under the pointer and pressed in when held
let t = 1.6
let dc = abs(cl_rbox(p, c, h - vec2(t, t), max(rr - t, 1.0))) - t
let g = cl_grad(p, c, h - vec2(t, t), max(rr - t, t)) * mix(-1.0, 1.0, step(0.0, cl_rbox(p, c, h - vec2(t, t), max(rr - t, 1.0))))
let inner = Finish.cover(d + t, px)
let dent = clamp(Finish.inset(p, c, h - vec2(t, t), max(rr - t, 1.0), vec2(0.4, 1.0), 1.2), 0.0, 1.0)
let la = inner * (0.10 * hover * live)
out = vec4(la, la, la, la)
let pa = inner * dent * 0.22 * press
out = Finish.over(out, cl_sh(pa))
let sd = abs(cl_rbox(p - vec2(0.4, 0.8), c, h - vec2(t, t), max(rr - t, 1.0))) - t
let sa = (1.0 - smoothstep(-0.5, 1.6, sd)) * 0.30 * mix(1.0, 0.4, press)
out = Finish.over(out, cl_sh(sa))
out = Finish.over(out, cl_skin(dc + cl_wob(p - c, seed, 0.25), g, t, p - c, vec2(0.0, 0.0), ink, seed, px, 0.5, h))
} else {
let show = clamp(hover * live * 0.8 + press, 0.0, 1.0)
out = cl_drop(p, c, h, rr, mix(1.0, 0.25, press), 1.0) * show
out = Finish.over(out, cl_lump4(p, c, h, r4, w, 0.35, 0.08, ink, seed, px, 1.0) * show)
}
}
out = Finish.over(out, cl_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 rl = max(min(h.x, h.y) - 5.0, 2.0)
let a0 = anim * 2.0 * PI
sdf.arc_round_caps(c.x, c.y, rl, a0, a0 + 1.5 * PI, 1.4)
sdf.fill(vec4(0.165, 0.129, 0.110, loading))
out = Finish.over(out, sdf.result)
}
return out
}
// The radius of a button's pill: a rounded slab, not a full capsule.
let cl_br = fn(sy: float) -> float {
return min(sy * 0.38, 10.0)
}
// The flat button's clay by its rung: the small rung pink, the large rung
// cornflower, the rest a bright blue clay. The sizes share the flat button's draw,
// so its height is what tells them apart.
let cl_flat_col = fn(sy: float) -> vec3 {
let small = 1.0 - step(23.5, sy)
let large = step(29.0, sy)
return mix(mix(vec3(0.345, 0.541, 1.0), vec3(0.925, 0.537, 0.757), small), vec3(0.443, 0.627, 0.871), large)
}
mod.widgets.ButtonFlat.clip_x = false
mod.widgets.ButtonFlat.clip_y = false
mod.widgets.ButtonFlat.draw_bg.vertex = fn() {
let g = cl_grow(self.rect_pos, self.rect_size, 6.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.ButtonFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return self.host_over(cl_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, cl_corners(self.border_radius_tl, self.border_radius_tr, self.border_radius_br, self.border_radius_bl, cl_br(self.rect_size.y)), 0.0, 6.0, cl_flat_col(self.rect_size.y)), 6.0)
}
// Default: a sunny yellow clay, a touch lighter than the card's.
mod.widgets.Button.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return self.host_over(cl_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, cl_corners(self.border_radius_tl, self.border_radius_tr, self.border_radius_br, self.border_radius_bl, cl_br(self.rect_size.y)), 0.0, 6.0, vec3(0.992, 0.855, 0.420)), 6.0)
}
// Primary: navy, a thread of yellow clay laid under its word.
mod.widgets.ButtonPrimary.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = 6.0
var out = cl_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, cl_corners(self.border_radius_tl, self.border_radius_tr, self.border_radius_br, self.border_radius_bl, cl_br(self.rect_size.y)), 0.0, m, vec3(0.278, 0.333, 0.565))
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let ty = self.rect_size.y - max(self.rect_size.y * 0.15, 3.4) + self.down * 0.5
let half = max(self.rect_size.x * 0.5 - 17.0, 4.0)
let a = vec2(self.rect_size.x * 0.5 - half, ty + 0.3)
let b = vec2(self.rect_size.x * 0.5 + half, ty - 0.3)
let tc = mix(vec3(0.992, 0.855, 0.420), vec3(0.82, 0.80, 0.81), self.disabled)
let show = 1.0 - self.loading
out = Finish.over(out, cl_snake_drop(p, a, b, 1.05, 0.6) * show)
out = Finish.over(out, cl_snake(p, a, b, 1.05, tc, 0.29, px, 0.3) * show)
return self.host_over(out, m)
}
// Secondary: leaf green.
mod.widgets.ButtonSecondary.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return self.host_over(cl_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, cl_corners(self.border_radius_tl, self.border_radius_tr, self.border_radius_br, self.border_radius_bl, cl_br(self.rect_size.y)), 0.0, 6.0, vec3(0.408, 0.718, 0.553)), 6.0)
}
// Tertiary: tangerine.
mod.widgets.ButtonTertiary.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return self.host_over(cl_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, cl_corners(self.border_radius_tl, self.border_radius_tr, self.border_radius_br, self.border_radius_bl, cl_br(self.rect_size.y)), 0.0, 6.0, vec3(0.945, 0.561, 0.341)), 6.0)
}
// Danger: a strawberry red.
mod.widgets.ButtonDanger.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return self.host_over(cl_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, cl_corners(self.border_radius_tl, self.border_radius_tr, self.border_radius_br, self.border_radius_bl, cl_br(self.rect_size.y)), 0.0, 6.0, vec3(0.918, 0.392, 0.427)), 6.0)
}
// Outline: a coil of navy round the word.
mod.widgets.ButtonOutline.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return self.host_over(cl_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, cl_corners(self.border_radius_tl, self.border_radius_tr, self.border_radius_br, self.border_radius_bl, cl_br(self.rect_size.y)), 5.0, 6.0, vec3(0.278, 0.333, 0.565)), 6.0)
}
mod.widgets.ButtonFlatter.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return self.host_over(cl_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, cl_corners(self.border_radius_tl, self.border_radius_tr, self.border_radius_br, self.border_radius_bl, cl_br(self.rect_size.y)), 6.0, 6.0, vec3(0.992, 0.855, 0.420)), 6.0)
}
// Templates that draw a face of their own on top of these roots keep the
// plain quad: the grown one is only for the faces written here.
mod.widgets.CloseButton.draw_bg.vertex = fn() {
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size)
}
mod.widgets.CopyButton.draw_bg.vertex = fn() {
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size)
}
mod.widgets.BurgerButton.draw_bg.vertex = fn() {
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size)
}
mod.widgets.ButtonFloating.draw_bg.vertex = fn() {
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size)
}
mod.widgets.ButtonFloatingSm.draw_bg.vertex = fn() {
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size)
}
mod.widgets.glass.CutButton.draw_bg.vertex = fn() {
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size)
}
// Geometry: one 26 point row, 13 points either side of the word.
mod.widgets.ButtonFlat.padding = mod.turtle.Inset{left: 13.0 right: 13.0 top: 4.0 bottom: 4.0}
mod.widgets.Button.padding = mod.turtle.Inset{left: 13.0 right: 13.0 top: 4.0 bottom: 4.0}
mod.widgets.ButtonPrimary.padding = mod.turtle.Inset{left: 13.0 right: 13.0 top: 4.0 bottom: 4.0}
mod.widgets.ButtonSecondary.padding = mod.turtle.Inset{left: 13.0 right: 13.0 top: 4.0 bottom: 4.0}
mod.widgets.ButtonDanger.padding = mod.turtle.Inset{left: 13.0 right: 13.0 top: 4.0 bottom: 4.0}
mod.widgets.ButtonTertiary.padding = mod.turtle.Inset{left: 13.0 right: 13.0 top: 4.0 bottom: 4.0}
mod.widgets.ButtonOutline.padding = mod.turtle.Inset{left: 13.0 right: 13.0 top: 4.0 bottom: 4.0}
mod.widgets.ButtonIcon.padding = mod.turtle.Inset{left: 7.0 right: 7.0 top: 4.0 bottom: 4.0}
mod.widgets.Button.icon_walk = mod.turtle.Walk{width: 14.0 height: Fit}
mod.widgets.ButtonFlat.min_height = 26.0
mod.widgets.Button.min_height = 26.0
mod.widgets.ButtonXs.min_height = 0.0
mod.widgets.ButtonXs.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 0.0 bottom: 0.0}
mod.widgets.ButtonXs.draw_text.text_style.font_size = 6.0
mod.widgets.ButtonSm.min_height = 0.0
// Words on the clay: the warm near-black, cream on the navy; medium weight.
mod.widgets.ButtonFlat.draw_text.text_style.font_family = mod.theme.font_label.font_family
// the flat buttons' words a dark navy on their blue
mod.widgets.ButtonFlat.draw_text.color = #0b1a44
mod.widgets.ButtonFlat.draw_text.color_hover = #08143a
mod.widgets.ButtonFlat.draw_text.color_down = uniform(#08143a)
mod.widgets.ButtonFlat.draw_text.color_focus = uniform(#0b1a44)
mod.widgets.ButtonFlat.draw_icon.color = #0b1a44
// the other buttons built on the flat one keep the warm near-black
mod.widgets.Button.draw_text.color = #2a211c
mod.widgets.Button.draw_text.color_hover = #1d1612
mod.widgets.Button.draw_text.color_down = uniform(#1d1612)
mod.widgets.Button.draw_text.color_focus = uniform(#2a211c)
mod.widgets.Button.draw_icon.color = #2a211c
mod.widgets.ButtonFlatter.draw_text.color = #2a211c
mod.widgets.ButtonFlatter.draw_text.color_hover = #1d1612
mod.widgets.ButtonFlatter.draw_text.color_down = uniform(#1d1612)
mod.widgets.ButtonFlatter.draw_text.color_focus = uniform(#2a211c)
mod.widgets.ButtonFlatter.draw_icon.color = #2a211c
mod.widgets.ButtonXs.draw_text.color = #2a211c
mod.widgets.ButtonXs.draw_text.color_hover = #1d1612
mod.widgets.ButtonXs.draw_text.color_down = uniform(#1d1612)
mod.widgets.ButtonXs.draw_text.color_focus = uniform(#2a211c)
mod.widgets.ButtonSm.draw_text.color = #2a211c
mod.widgets.ButtonSm.draw_text.color_hover = #1d1612
mod.widgets.ButtonSm.draw_text.color_down = uniform(#1d1612)
mod.widgets.ButtonSm.draw_text.color_focus = uniform(#2a211c)
mod.widgets.ButtonLg.draw_text.color = #2a211c
mod.widgets.ButtonLg.draw_text.color_hover = #1d1612
mod.widgets.ButtonLg.draw_text.color_down = uniform(#1d1612)
mod.widgets.ButtonLg.draw_text.color_focus = uniform(#2a211c)
mod.widgets.ButtonXl.draw_text.color = #2a211c
mod.widgets.ButtonXl.draw_text.color_hover = #1d1612
mod.widgets.ButtonXl.draw_text.color_down = uniform(#1d1612)
mod.widgets.ButtonXl.draw_text.color_focus = uniform(#2a211c)
mod.widgets.ButtonPrimary.draw_text.color = #fbf3e6
mod.widgets.ButtonPrimary.draw_text.color_hover = #ffffff
mod.widgets.ButtonPrimary.draw_text.color_down = #f1e6d4
mod.widgets.ButtonPrimary.draw_text.color_focus = #fbf3e6
mod.widgets.ButtonPrimary.draw_text.color_disabled = #8f8279
mod.widgets.ButtonPrimary.draw_icon.color = #fbf3e6
mod.widgets.ButtonPrimary.draw_text.text_style = mod.theme.font_label{font_size: 8.5}
mod.widgets.ButtonSecondary.draw_text.color = #1f1915
mod.widgets.ButtonSecondary.draw_text.color_hover = #140f0c
mod.widgets.ButtonSecondary.draw_text.color_down = #140f0c
mod.widgets.ButtonSecondary.draw_text.color_focus = #1f1915
mod.widgets.ButtonSecondary.draw_icon.color = #1f1915
mod.widgets.ButtonSecondary.draw_text.text_style = mod.theme.font_label{font_size: 8.5}
mod.widgets.ButtonTertiary.draw_text.color = #1f1915
mod.widgets.ButtonTertiary.draw_text.color_hover = #140f0c
mod.widgets.ButtonTertiary.draw_text.color_down = #140f0c
mod.widgets.ButtonTertiary.draw_text.color_focus = #1f1915
mod.widgets.ButtonTertiary.draw_icon.color = #1f1915
mod.widgets.ButtonTertiary.draw_text.text_style = mod.theme.font_label{font_size: 8.5}
mod.widgets.ButtonDanger.draw_text.color = #1f1915
mod.widgets.ButtonDanger.draw_text.color_hover = #140f0c
mod.widgets.ButtonDanger.draw_text.color_down = #140f0c
mod.widgets.ButtonDanger.draw_text.color_focus = #1f1915
mod.widgets.ButtonDanger.draw_icon.color = #1f1915
mod.widgets.ButtonDanger.draw_text.text_style = mod.theme.font_label{font_size: 8.5}
mod.widgets.ButtonOutline.draw_text.color = #2c3460
mod.widgets.ButtonOutline.draw_text.color_hover = #222a52
mod.widgets.ButtonOutline.draw_text.color_down = #222a52
mod.widgets.ButtonOutline.draw_text.color_focus = #2c3460
mod.widgets.ButtonOutline.draw_text.color_disabled = #8f8279
mod.widgets.ButtonOutline.draw_icon.color = #2c3460
mod.widgets.ButtonFlat.draw_bg.loading_color = #2a211c
// ---------------------------------------------------------------------------
// CHECK BOXES. A 16 point tile of clay ringed with a thin coil: yellow with a
// charcoal coil while clear; set, a green tile with a darker green coil and
// a charcoal snake bent into a check that runs out past its top corner, as
// a quick hand lays one. The quad grows five points for the check, the
// shadow and the focus thread.
mod.widgets.CheckBoxFlat.clip_x = false
mod.widgets.CheckBoxFlat.clip_y = false
mod.widgets.CheckBoxFlat.draw_bg.vertex = fn() {
let g = cl_grow(self.rect_pos, self.rect_size, 5.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.CheckBoxCustom.draw_bg.vertex = fn() {
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size)
}
mod.widgets.CheckBoxFlat.padding = mod.turtle.Inset{left: 0.0 right: 3.0 top: 3.0 bottom: 3.0}
mod.widgets.CheckBoxFlat.label_walk.margin = mod.turtle.Inset{left: 25.0 right: 3.0 top: 0.0 bottom: 0.0}
mod.widgets.CheckBoxFlat.draw_bg.size = uniform(16.0)
mod.widgets.CheckBoxFlat.min_height = 24.0
mod.widgets.CheckBoxFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = 5.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 sz = self.size
let live = 1.0 - self.disabled
let x0 = mix(1.0, size.x - sz - 1.0, self.mark_at_end)
let lo = vec2(Finish.snap(o.x + x0, px), Finish.snap(o.y + (size.y - sz) * 0.5, px)) - o
let h = vec2(sz * 0.5, sz * 0.5)
let c = lo + h
let r = mix(4.0, h.y, step(20.0, self.border_radius))
let on = max(self.active, self.mixed)
let press = self.down * live
var tile = mix(vec3(0.992, 0.855, 0.420), vec3(0.408, 0.718, 0.553), on)
tile = cl_tone(tile, self.hover * live, press, self.disabled)
var coil = mix(vec3(0.290, 0.263, 0.271), vec3(0.247, 0.471, 0.353), on)
coil = mix(coil, vec3(0.839, 0.255, 0.255), self.error)
coil = mix(coil, vec3(0.690, 0.667, 0.675), self.disabled * 0.8)
let seed = 0.13 + on * 0.29
var out = cl_drop(p, c, h, r, mix(1.0, 0.3, press) * mix(1.0, 0.5, self.disabled), 0.9)
out = Finish.over(out, cl_lump(p, c, h, r, 4.0, 0.25, 0.08, tile, seed, px, 0.7))
// the coil round the tile's edge
let t = 1.05
let dc = abs(cl_rbox(p, c, h - vec2(t, t), max(r - t, 0.5))) - t
let g = cl_grad(p, c, h - vec2(t, t), max(r - t, t)) * mix(-1.0, 1.0, step(0.0, cl_rbox(p, c, h - vec2(t, t), max(r - t, 0.5))))
out = Finish.over(out, cl_skin(dc, g, t, p - c, vec2(0.0, 0.0), coil, seed + 0.5, px, 0.4, h))
// the check, or the mixed dash
let k = sz / 16.0
let ink = mix(vec3(0.200, 0.180, 0.188), vec3(0.620, 0.600, 0.610), self.disabled)
if self.mixed > 0.5 {
out = Finish.over(out, cl_snake_drop(p, lo + vec2(4.5, 8.0) * k, lo + vec2(11.5, 8.0) * k, 1.35 * k, 0.8))
out = Finish.over(out, cl_snake(p, lo + vec2(4.5, 8.0) * k, lo + vec2(11.5, 8.0) * k, 1.35 * k, ink, 0.71, px, 0.3))
} else {
if self.active > 0.5 {
out = Finish.over(out, cl_hook(p, lo + vec2(3.6, 8.2) * k, lo + vec2(7.0, 12.0) * k, lo + vec2(16.5, -1.5) * k, 1.45 * k, ink, 0.71, px))
}
}
return Finish.over(out, cl_focus(cl_rbox(p, c, h, r), px, self.focus * live))
}
// ---------------------------------------------------------------------------
// TOGGLES. A 38 by 20 point pill of clay, charcoal while off and green while
// on, with a yellow ball riding in it, which throws its own shadow on the
// pill. The switches below wear the same face.
mod.widgets.ToggleFlat.label_walk.margin = mod.turtle.Inset{left: 48.0 right: 3.0 top: 0.0 bottom: 0.0}
mod.widgets.ToggleFlat.draw_bg.size = 14.0
mod.widgets.ToggleFlat.min_height = 24.0
mod.widgets.ToggleFlat.draw_bg.pill_aspect = 2.7143
mod.widgets.ToggleFlat.draw_bg.knob_inset = 7.0
mod.widgets.ToggleFlat.clip_x = false
mod.widgets.ToggleFlat.clip_y = false
mod.widgets.ToggleFlat.draw_bg.vertex = fn() {
let g = cl_grow(self.rect_pos, self.rect_size, 5.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.ToggleFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = 5.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 tw = 38.0
let th = 20.0
let x0 = mix(1.0, size.x - tw - 1.0, self.mark_at_end)
let lo = vec2(Finish.snap(o.x + x0, px), Finish.snap(o.y + (size.y - th) * 0.5, px)) - o
let h = vec2(tw * 0.5, th * 0.5)
let c = lo + h
let on = self.active
var col = mix(vec3(0.329, 0.306, 0.314), vec3(0.408, 0.718, 0.553), on)
col = mix(col, vec3(0.839, 0.255, 0.255), self.error * 0.8)
col = cl_tone(col, self.hover * live, 0.0, self.disabled)
let seed = 0.31 + on * 0.17
var out = cl_drop(p, c, h, h.y, mix(1.0, 0.5, self.disabled), 1.0)
out = Finish.over(out, cl_lump(p, c, h, h.y, 6.0, 0.3, 0.08, col, seed, px, 0.8))
// the ball
let kt = clamp(mix(on, self.drag_pos, self.drag), 0.0, 1.0)
let kr = 7.4
let kc = vec2(lo.x + 10.0 + kt * (tw - 20.0), c.y)
let press = self.down * live
out = Finish.over(out, cl_drop(p, kc, vec2(kr, kr), kr, (1.0 - 0.5 * press) * mix(1.0, 0.5, self.disabled), 0.8 + 0.3 * self.hover * live))
let kcol = cl_tone(vec3(0.992, 0.855, 0.420), self.hover * live, press, self.disabled)
out = Finish.over(out, cl_ball(p, kc, kr, 0.85, kcol, 0.57 + kt * 0.2, px, 0.6, 0.2))
return Finish.over(out, cl_focus(cl_rbox(p, c, h, h.y), px, self.focus * live))
}
// ---------------------------------------------------------------------------
// RADIOS. A ball of clay with a thin coil laid round its middle: yellow with
// a charcoal coil while not chosen; chosen, green with a pale green coil and
// a charcoal bead in the middle.
let cl_radio = fn(p: vec2, c: vec2, rad: float, px: float, hover: float, down: float, focus: float, active: float, dis: float) -> vec4 {
let live = 1.0 - dis
let press = down * live
var col = mix(vec3(0.992, 0.855, 0.420), vec3(0.408, 0.718, 0.553), active)
col = cl_tone(col, hover * live, press, dis)
let seed = 0.43 + active * 0.21
var out = cl_drop(p, c, vec2(rad, rad), rad, mix(1.0, 0.3, press) * mix(1.0, 0.5, dis), 0.9)
out = Finish.over(out, cl_ball(p, c, rad, 0.7, col, seed, px, 0.6, 0.25))
var coil = mix(vec3(0.290, 0.263, 0.271), vec3(0.792, 0.910, 0.835), active)
coil = mix(coil, vec3(0.690, 0.667, 0.675), dis * 0.8)
out = Finish.over(out, cl_ring(p, c, rad * 0.56, max(rad * 0.13, 0.8), coil, seed + 0.3, px))
if active > 0.5 {
let bc = mix(vec3(0.200, 0.180, 0.188), vec3(0.620, 0.600, 0.610), dis)
let br = rad * 0.27
out = Finish.over(out, cl_drop(p, c, vec2(br, br), br, 0.7, 0.5))
out = Finish.over(out, cl_ball(p, c, br, 1.0, bc, seed + 0.6, px, 0.3, 0.1))
}
return Finish.over(out, cl_focus(length(p - c) - rad, px, focus * live))
}
mod.widgets.RadioButtonFlat.clip_x = false
mod.widgets.RadioButtonFlat.clip_y = false
mod.widgets.RadioButtonFlat.draw_bg.vertex = fn() {
let g = cl_grow(self.rect_pos, self.rect_size, 5.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.glass.RadioButton.draw_bg.vertex = fn() {
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size)
}
mod.widgets.RadioButtonFlat.padding = mod.turtle.Inset{left: 0.0 right: 3.0 top: 3.0 bottom: 3.0}
mod.widgets.RadioButtonFlat.label_walk.margin = mod.turtle.Inset{left: 25.0 right: 3.0 top: 0.0 bottom: 0.0}
mod.widgets.RadioButtonFlat.draw_bg.size = uniform(17.0)
mod.widgets.RadioButtonFlat.min_height = 24.0
mod.widgets.RadioButtonFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = 5.0
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let o = self.rect_pos
let sz = self.size
let lo = vec2(Finish.snap(o.x + 1.0, px), Finish.snap(o.y + (self.rect_size.y - sz) * 0.5, px)) - o
let c = lo + vec2(sz * 0.5, sz * 0.5)
return cl_radio(p, c, sz * 0.5, px, self.hover, self.down, self.focus, self.active, self.disabled)
}
// The flatter radio is its word alone: chosen, the word sits in a pocket of
// lavender pressed into the slab; under the pointer the slab lifts behind it.
mod.widgets.RadioButtonFlatter.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = 5.0
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let o = self.rect_pos
let live = 1.0 - self.disabled
let lo = vec2(Finish.snap(o.x - 3.0, px), Finish.snap(o.y + 1.0, px)) - o
let hi = vec2(Finish.snap(o.x + self.rect_size.x + 3.0, px), Finish.snap(o.y + self.rect_size.y - 1.0, px)) - o
let h = (hi - lo) * 0.5
let c = lo + h
let d = cl_rbox(p, c, h, 6.0)
let lift = Finish.cover(d, px) * 0.16 * self.hover * live * (1.0 - self.active)
var out = vec4(lift, lift, lift, lift)
out = Finish.over(out, cl_pocket(p, c, h, 6.0, vec3(0.765, 0.706, 0.882), 0.77, px) * self.active)
return Finish.over(out, cl_focus(d, px, self.focus * live))
}
// Radio tabs: each a pill of yellow clay; the chosen one is a pocket of
// charcoal pressed into the slab, its word lit cream.
mod.widgets.RadioButtonTabFlat.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 6.0 bottom: 2.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.min_height = 26.0
mod.widgets.RadioButtonTabFlat.draw_bg.vertex = fn() {
let g = cl_grow(self.rect_pos, self.rect_size, 5.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 = 5.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 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 r = cl_br(size.y)
let held = clamp(max(self.active, self.down * live), 0.0, 1.0)
let seed = fract(size.x * 0.0731 + 0.29)
var out = vec4(0.0, 0.0, 0.0, 0.0)
if held > 0.5 {
let rim = Finish.ring_out(cl_rbox(p, c, h, r), 0.0, 1.2, px) * 0.35
out = vec4(rim, rim, rim, rim)
// chosen, a charcoal pocket; held before it is chosen, the yellow
// clay pressed in, so its dark word still reads
let pk = mix(vec3(0.930, 0.784, 0.380), vec3(0.314, 0.290, 0.298), self.active)
out = Finish.over(out, cl_pocket(p, c, h, r, cl_tone(pk, self.hover * live, 0.0, self.disabled), seed, px))
} else {
out = cl_drop(p, c, h, r, mix(1.0, 0.5, self.disabled), 1.0 + 0.3 * self.hover * live)
out = Finish.over(out, cl_lump(p, c, h, r, min(h.y * 0.62, 7.0), 0.35, 0.10, cl_tone(vec3(0.992, 0.855, 0.420), self.hover * live, 0.0, self.disabled), seed, px, 1.0))
}
return Finish.over(out, cl_focus(cl_rbox(p, c, h, r), px, self.focus * live))
}
mod.widgets.RadioButtonTabFlat.draw_text.color = #2a211c
mod.widgets.RadioButtonTabFlat.draw_text.color_active = #fbf1dc
mod.widgets.RadioButtonTabFlat.draw_text.color_disabled = #8f8279
// ---------------------------------------------------------------------------
// SLIDERS. A snake of coloured clay, a yellow pebble riding on it. The snake
// is whole up to the pebble; past it the snake is rolled thinner and paler,
// so the value reads. Each slider kind is its own clay: the slider
// cornflower, the minimal one violet, the round one teal, the band pink.
mod.widgets.SliderMinimal.clip_x = false
mod.widgets.SliderMinimal.clip_y = false
mod.widgets.SliderMinimal.draw_bg.vertex = fn() {
let g = cl_grow(self.rect_pos, self.rect_size, 5.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
// The riding pebble: a yellow lump a little taller than wide, lifting under
// the pointer and squashed while it is held.
let cl_thumb = fn(p: vec2, c: vec2, h: vec2, px: float, hover: float, press: float, dis: float, seed: float) -> vec4 {
let live = 1.0 - dis
let r = min(h.x, h.y) * 0.95
var out = cl_drop(p, c, h, r, (1.0 - 0.6 * press) * mix(1.0, 0.5, dis), 1.0 + 0.3 * hover * live)
let col = cl_tone(vec3(0.992, 0.855, 0.420), hover * live, press, dis)
return Finish.over(out, cl_lump(p, c, h, r, min(h.x, h.y) * 0.85, 0.25, 0.10, col, seed, px, 0.7))
}
// The snake from `x0` to `x1` at height `cy`, `rad` thick, whole between
// `f0` and `f1` and thinner and paler elsewhere.
let cl_track = fn(p: vec2, x0: float, x1: float, cy: float, rad: float, f0: float, f1: float, col: vec3, dis: float, seed: float, px: float) -> vec4 {
let k = cl_tone(col, 0.0, 0.0, dis)
let pale = mix(k, cl_ground_col(), 0.45)
let thin = rad * 0.72
var out = cl_snake_drop(p, vec2(x0 + thin, cy), vec2(x1 - thin, cy), thin, 0.8)
out = Finish.over(out, cl_snake(p, vec2(x0 + thin, cy), vec2(x1 - thin, cy), thin, pale, seed + 0.4, px, 0.5))
if f1 - f0 > 0.5 {
let a = vec2(max(f0, x0 + rad), cy)
let b = vec2(max(min(f1, x1 - rad), a.x), cy)
out = Finish.over(out, cl_snake_drop(p, a, b, rad, 1.0))
out = Finish.over(out, cl_snake(p, a, b, rad, k, seed, px, 0.6))
}
return out
}
mod.widgets.SliderFlat.height = 32.0
mod.widgets.SliderFlat.clip_x = false
mod.widgets.SliderFlat.clip_y = false
mod.widgets.SliderFlat.draw_bg.vertex = fn() {
let g = cl_grow(self.rect_pos, self.rect_size, 5.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.SliderFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = 5.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 - 8.5, px) - o.y
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 span = x1 - x0 - 18.0
let kx = x0 + 9.0 + self.slide_pos * span
let ox = x0 + 9.0 + self.origin_pos * span
let f0 = mix(x0, min(ox, kx), self.arc_origin)
let f1 = mix(kx, max(ox, kx), self.arc_origin)
let seed = fract(size.x * 0.0517 + 0.11)
var out = cl_track(p, x0, x1, cy, 3.7, f0, f1, Finish.lift(vec3(0.290, 0.451, 0.788), 0.08 * max(self.hover, self.drag) * live), self.disabled, seed, px)
let kc = vec2(Finish.snap(o.x + kx, px) - o.x, cy)
let kh = vec2(8.0, 9.0)
out = Finish.over(out, cl_thumb(p, kc, kh, px, max(self.hover, self.focus * 0.5), self.drag * live, self.disabled, 0.53 + self.slide_pos * 0.3))
return Finish.over(out, cl_focus(cl_rbox(p, kc, kh, 7.0), px, self.focus * live))
}
mod.widgets.SliderFlat.text_input.draw_text.text_style = mod.theme.font_regular{font_size: 8.0}
// The range slider: a pink snake, whole between the two ends and thin
// outside them, a round yellow ball on each end, squashed while it is held.
mod.widgets.RangeSliderFlat.handle_size = 18.0
mod.widgets.RangeSliderFlat.track_inset = 1.0
mod.widgets.RangeSliderFlat.draw_bg.vertex = fn() {
let g = cl_grow(self.rect_pos, self.rect_size, 5.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 = 5.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 + 1.0, px) - o.x
let x1 = Finish.snap(o.x + size.x - 1.0, px) - o.x
let span = max(size.x - self.inset_px * 2.0 - self.handle_px, 1.0)
let sx = self.inset_px + self.handle_px * 0.5 + self.start_pos * span
let ex = self.inset_px + self.handle_px * 0.5 + self.end_pos * span
var out = cl_track(p, x0, x1, cy, 3.7, sx, ex, Finish.lift(vec3(0.878, 0.478, 0.627), 0.08 * max(self.hover, self.drag) * live), self.disabled, 0.37, px)
let kr = 8.0
let k0 = vec2(Finish.snap(o.x + sx, px) - o.x, cy)
let k1 = vec2(Finish.snap(o.x + ex, px) - o.x, cy)
out = Finish.over(out, cl_thumb(p, k0, vec2(kr, kr), px, max(self.hot_start, self.focus * 0.5) * live, self.drag * self.hot_start * live, self.disabled, 0.21))
out = Finish.over(out, cl_thumb(p, k1, vec2(kr, kr), px, max(self.hot_end, self.focus * 0.5) * live, self.drag * self.hot_end * live, self.disabled, 0.67))
return out
}
// The minimal slider: a thin violet snake and a small yellow ball.
mod.widgets.SliderMinimal.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = 5.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 - 3.5, px) - o.y
let kr = 5.5
let kx = kr + self.slide_pos * (size.x - 2.0 * kr)
var out = cl_track(p, 0.0, size.x, cy, 2.6, 0.0, kx, Finish.lift(vec3(0.478, 0.333, 0.722), 0.08 * max(self.hover, self.drag) * live), self.disabled, 0.83, px)
let kc = vec2(Finish.snap(o.x + kx, px) - o.x, cy)
out = Finish.over(out, cl_thumb(p, kc, vec2(kr, kr), px, self.hover, self.drag * live, self.disabled, 0.19))
return Finish.over(out, cl_focus(length(p - kc) - kr, px, self.focus * live))
}
mod.widgets.SliderMinimal.text_input.draw_text.text_style = mod.theme.font_regular{font_size: 8.0}
// The round slider: a teal snake beside its word, the value reading after
// it, a yellow ball riding on it.
mod.widgets.SliderRoundFlat.height = 18.0
mod.widgets.SliderRoundFlat.clip_x = false
mod.widgets.SliderRoundFlat.clip_y = false
mod.widgets.SliderRoundFlat.draw_bg.vertex = fn() {
let g = cl_grow(self.rect_pos, self.rect_size, 5.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
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.width = 30.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: 8.0}
mod.widgets.SliderRoundFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = 5.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 + 4.0, px) - o.x
let x1 = Finish.snap(o.x + size.x - 44.0, px) - o.x
let cy = Finish.snap(o.y + size.y * 0.5, px) - o.y
let kr = 7.0
let kx = x0 + kr + self.slide_pos * (x1 - x0 - 2.0 * kr)
var out = cl_track(p, x0, x1, cy, 3.5, x0, kx, Finish.lift(vec3(0.184, 0.647, 0.604), 0.08 * self.hover * live), self.disabled, 0.47, px)
let kc = vec2(Finish.snap(o.x + kx, px) - o.x, cy)
out = Finish.over(out, cl_thumb(p, kc, vec2(kr, kr), px, self.hover, self.drag * live, self.disabled, 0.61))
return Finish.over(out, cl_focus(length(p - kc) - kr, px, self.focus * live))
}
// ---------------------------------------------------------------------------
// FADERS. A charcoal snake for the slot, a row of small clay dots either
// side for the scale (a bigger one at each quarter), and a yellow cap 22
// points across and 12 along the travel, a groove pressed across its middle.
mod.widgets.SliderFader.cap_size = 12.0
mod.widgets.SliderFader.clip_x = false
mod.widgets.SliderFader.clip_y = false
mod.widgets.SliderFader.draw_bg.vertex = fn() {
let g = cl_grow(self.rect_pos, self.rect_size, 5.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 = 5.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 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 / px) * px
// screen point of a place `a` along the travel, `b` across it
let a_lo = self.inset_px + 2.5
let a_hi = along - self.inset_px - 2.5
let s0 = mix(vec2(a_lo, mid), vec2(mid, size.y - a_lo), vert)
let s1 = mix(vec2(a_hi, mid), vec2(mid, size.y - a_hi), vert)
let slot = mix(vec3(0.290, 0.263, 0.271), vec3(0.690, 0.667, 0.675), self.disabled * 0.8)
var out = cl_snake_drop(p, s0, s1, 2.6, 0.9)
out = Finish.over(out, cl_snake(p, s0, s1, 2.6, slot, 0.91, px, 0.5))
// the scale: dots either side of the slot
let a = mix(p.x, size.y - p.y, vert)
let b = mix(p.y, p.x, vert)
let nt = 8.0
let fi = clamp(floor((a - foot) / travel * nt + 0.5), 0.0, nt)
let ta = foot + fi / nt * travel
let major = 1.0 - step(0.1, abs(fi * 0.5 - floor(fi * 0.5 + 0.5)))
let dr = mix(0.85, 1.25, major)
let side = mix(-1.0, 1.0, step(mid, b))
let tb = mid + side * 7.5
let dq = vec2(a - ta, b - tb)
let ds = length(dq) - dr
let dsh = length(dq - mix(vec2(-0.8, 0.4), vec2(0.4, 0.8), 1.0 - vert)) - dr
let sha = (1.0 - smoothstep(-0.3, 1.0, dsh)) * 0.25
out = Finish.over(out, cl_sh(sha))
let dcol = mix(vec3(0.290, 0.263, 0.271), vec3(0.690, 0.667, 0.675), self.disabled * 0.8)
let dl = 1.0 - 0.35 * smoothstep(-dr, dr, mix(dq.y, -dq.x, vert))
let da = Finish.cover(ds, px)
out = Finish.over(out, vec4(dcol * dl * da, da))
// the cap, drawn in screen space so its light stays on the upper left
let at = foot + self.slide_pos * travel
let cw = min(22.0, across)
let cs = mix(vec2(at, mid), vec2(mid, size.y - at), vert)
let ch = mix(vec2(self.cap_px * 0.5, cw * 0.5), vec2(cw * 0.5, self.cap_px * 0.5), vert)
let cc = vec2(Finish.snap(o.x + cs.x, px) - o.x, Finish.snap(o.y + cs.y, px) - o.y)
let press = self.drag * live
let r = 3.5
out = Finish.over(out, cl_drop(p, cc, ch, r, (1.0 - press * 0.6) * mix(1.0, 0.5, self.disabled), 1.0 + 0.3 * self.hover * live))
let col = cl_tone(vec3(0.992, 0.855, 0.420), max(self.hover, self.focus * 0.5) * live, press, self.disabled)
out = Finish.over(out, cl_lump(p, cc, ch, r, 3.5, 0.2, 0.08, col, 0.27 + self.slide_pos * 0.4, px, 0.6))
// the groove across the cap
let gq = p - cc
let gl = mix(gq.x, gq.y, vert)
let gx = mix(gq.y, gq.x, vert)
let groove = (1.0 - smoothstep(0.3, 0.8, abs(gl))) * step(abs(gx), mix(ch.y, ch.x, vert) - 3.0)
let gd = groove * (0.45 - 0.25 * step(0.0, gl))
out = Finish.over(out, cl_sh(gd))
return Finish.over(out, cl_focus(cl_rbox(p, cc, ch, r), px, self.focus * live))
}
// ---------------------------------------------------------------------------
// KNOBS. A fat disc of clay, its top flattened, turned with its value (its
// lumps and marks turn with it), a short white snake pressed on for the
// pointer. Round the small knobs a ring of charcoal ticks, up to the value
// laid on in clay and past it only pressed in; round the big knobs a
// charcoal snake bent round the scale up to the value, the rest of the
// scale a groove.
let cl_knob = fn(p: vec2, c: vec2, rad: float, px: float, ang: float, hover: float, press: float, dis: float, col: vec3) -> vec4 {
let live = 1.0 - dis
var out = cl_drop(p, c, vec2(rad, rad), rad, (1.0 - 0.5 * press) * mix(1.0, 0.5, dis), 1.0 + rad * 0.025 + 0.3 * hover * live)
let k = cl_tone(col, hover * live, press, dis)
let cs = cos(ang)
let sn = sin(ang)
let q = p - c
let qr = vec2(q.x * cs + q.y * sn, q.y * cs - q.x * sn)
let d = length(q) - rad + cl_wob(qr, 0.61, clamp(rad * 0.025, 0.2, 0.7))
let g = q / max(length(q), 0.0001)
out = Finish.over(out, cl_skin(d, g, rad * 0.5, qr, q / max(rad, 1.0) * 0.10, k, 0.61, px, 1.6, vec2(rad, rad)))
let dir = vec2(-sin(ang), cos(ang))
let a0 = c + dir * rad * 0.16
let a1 = c + dir * rad * 0.66
let pr = max(rad * 0.085, 1.2)
let wcol = mix(vec3(0.992, 0.855, 0.420), vec3(0.85, 0.83, 0.84), dis)
out = Finish.over(out, cl_snake_drop(p, a0, a1, pr, 0.7))
return Finish.over(out, cl_snake(p, a0, a1, pr, wcol, 0.23, px, 0.3))
}
// The angle of a point round `c`, 0 at the bottom, clockwise, 0..2 pi.
let cl_ang = fn(q: vec2) -> float {
var ang = atan2(-q.x, q.y)
if ang < 0.0 {
ang = ang + 2.0 * PI
}
return ang
}
// The ticks of a small knob: `n` of them over the sweep, from `r0` to `r1`.
let cl_ticks = fn(p: vec2, c: vec2, r0: float, r1: float, px: float, v: float, gap: float, dis: float) -> vec4 {
let start = gap * 0.5
let sweep = 2.0 * PI - gap
let q = p - c
let ang = cl_ang(q)
let n = 10.0
let fi = clamp(floor((ang - start) / sweep * n + 0.5), 0.0, n)
let ta = start + fi / n * sweep
let dir = vec2(-sin(ta), cos(ta))
let e0 = c + dir * r0
let e1 = c + dir * r1
let td = cl_seg(p, e0, e1) - 0.75
let on = step(fi / n, v + 0.001)
let ink = mix(vec3(0.231, 0.208, 0.216), vec3(0.690, 0.667, 0.675), dis * 0.8)
let sd = cl_seg(p - vec2(0.3, 0.6), e0, e1) - 0.75
let sa = (1.0 - smoothstep(-0.3, 0.9, sd)) * 0.28 * on
var out = cl_sh(sa)
let tq = clamp(dot(p - e0, e1 - e0) / max(dot(e1 - e0, e1 - e0), 0.0001), 0.0, 1.0)
let lit = 1.0 - 0.3 * smoothstep(-0.75, 0.75, dot(p - (e0 + (e1 - e0) * tq), vec2(0.47, 0.88)))
let ta2 = Finish.cover(td, px)
out = Finish.over(out, vec4(ink * lit * ta2 * on, ta2 * on))
// past the value, the tick is only pressed into the slab
let dent = ta2 * (1.0 - on) * 0.16
return Finish.over(out, cl_sh(dent))
}
// The value of a big knob: a charcoal snake bent round the scale from its
// start (or its origin) to the value, the rest of the scale a groove.
let cl_arc = fn(p: vec2, c: vec2, ar: float, aw: float, px: float, v0: float, v1: float, gap: float, dis: float) -> vec4 {
let start = gap * 0.5
let sweep = 2.0 * PI - gap
let q = p - c
let ang = cl_ang(q)
let rd = abs(length(q) - ar)
let in_arc = step(start, ang) * step(ang, start + sweep)
let e0 = c + vec2(-sin(start), cos(start)) * ar
let e1 = c + vec2(-sin(start + sweep), cos(start + sweep)) * ar
let track_d = min(mix(1000.0, rd, in_arc), min(length(p - e0), length(p - e1))) - aw * 0.35
let gr = Finish.cover(track_d, px)
let lip = Finish.ring_out(track_d, 0.0, px * 1.5, px) * step(0.0, dot(q, vec2(0.47, 0.88))) * 0.45
var out = vec4(lip, lip, lip, lip)
let ga = gr * 0.20
out = Finish.over(out, cl_sh(ga))
// the snake
let a0 = start + sweep * v0
let a1 = start + sweep * max(v1, v0 + 0.002)
let am = clamp(ang, a0, a1)
let l0 = c + vec2(-sin(a0), cos(a0)) * ar
let l1 = c + vec2(-sin(a1), cos(a1)) * ar
let inside = step(a0, ang) * step(ang, a1)
let np = mix(mix(l0, l1, step(length(p - l1), length(p - l0))), c + q / max(length(q), 0.0001) * ar, inside)
let off = p - np
let dist = length(off)
let rad = aw * 0.5
let ink = mix(vec3(0.255, 0.231, 0.239), vec3(0.690, 0.667, 0.675), dis * 0.8)
let sq = p - vec2(0.4, 0.8) - c
let sang = cl_ang(sq)
let sin_arc = step(a0, sang) * step(sang, a1)
let sdist = min(mix(1000.0, abs(length(sq) - ar), sin_arc), min(length(p - vec2(0.4, 0.8) - l0), length(p - vec2(0.4, 0.8) - l1)))
let sa = (1.0 - smoothstep(rad - 0.5, rad + 1.6, sdist)) * 0.28 * step(0.002, v1 - v0)
out = Finish.over(out, cl_sh(sa))
let body = cl_skin(dist - rad, off / max(dist, 0.0001), rad, vec2(am * ar, length(q) - ar), vec2(0.0, 0.0), ink, 0.87, px, 0.4, vec2(ar, rad))
return Finish.over(out, body * step(0.002, v1 - v0))
}
mod.widgets.RotaryKnob.clip_x = false
mod.widgets.RotaryKnob.clip_y = false
mod.widgets.RotaryKnob.draw_bg.vertex = fn() {
let g = cl_grow(self.rect_pos, self.rect_size, 5.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
// The small knobs: sunny yellow-orange clay.
mod.widgets.RotaryKnob.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = 5.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 - 1.0
let c = vec2(Finish.snap(o.x + size.x * 0.5, px) - o.x, Finish.snap(o.y + size.y - dia * 0.5, px) - o.y)
let gap = self.gap * PI / 180.0
let rad = floor(rr / 1.24 / px) * px
var out = cl_ticks(p, c, rad + 2.0, rr + 0.5, px, self.slide_pos, gap, self.disabled)
let ang = gap * 0.5 + (2.0 * PI - gap) * self.slide_pos
out = Finish.over(out, cl_knob(p, c, rad, px, ang, max(self.hover, self.focus * 0.5), self.drag * live, self.disabled, vec3(0.953, 0.659, 0.278)))
return Finish.over(out, cl_focus(length(p - c) - rad, px, self.focus * live))
}
// Knob captions are letters pressed into the slab: small semibold words.
mod.widgets.RotaryKnob.draw_text.color = #3b302a
mod.widgets.RotaryKnob.draw_text.text_style = mod.theme.font_bold{font_size: 7.75}
// The big knobs: a thick snake of bright blue plasticine rolled up by hand
// into a coil inside its scale, as such coils come out: the tube about a
// fifth of the coil across, so a little over two turns; each turn pressed
// tight against the next, so their sections meet in a narrow crease. Every
// turn sits a little off the middle by the same sway, so the outline, which
// a spiral grows by a whole tube width round the coil, lies close to one
// circle and steps only at the tail's tip. The tube is pressed flat on top:
// a broad soft top, its sides rounding down into the crease. A fine ripple
// runs along its length. The inner end a fat blob in the middle; the outer
// end tapering a little and tucking in against the turn inside it in a
// rounded tip. Lit from the upper left: a broad soft light across each
// turn's top, its far side shading down into the crease, a little light
// bounced up from the slab. Its skin carries the marks every part of the
// sheet has: soft lumps and thumb dents, fingerprints, an air pit, a few
// smoothing strokes along the tube, the mottle of the mix and a fine grain,
// all turning with it. The whole coil turns with the value and its tip is
// the pointer.
// How far along the tube the angle `th` lies, in points.
let cl_coil_s = fn(th: float, r0: float, b: float) -> float {
return r0 * th + 0.5 * b * th * th
}
// The coil's centre line at angle `th`: an Archimedean spiral, `pitch` a
// turn; every turn swayed off the middle by `kap` times the sway that rounds
// the last turn's outline into a circle round the coil's middle; the tail
// tucking in over its last quarter turn; a fine sideways ripple every tube
// width and a half or so, as a snake rolled by hand comes out, growing in
// over the first turn so the middle stays closed. With the radius's rate of
// change.
let cl_coil_r = fn(th: float, r0: float, b: float, pitch: float, t0: float, tmax: float, kap: float) -> float {
let gr = smoothstep(0.0, 6.2831853, th)
let s = cl_coil_s(th, r0, b) / (2.0 * t0)
let ty = clamp((th - tmax + 1.5707963) / 1.5707963, 0.0, 1.0)
let tuck = 0.30 * pitch * ty * ty * (3.0 - 2.0 * ty)
return r0 + b * th + 2.0 * b * kap * sin(th - tmax) - tuck + pitch * gr * (0.0175 * sin(s * 4.2 + 0.7) + 0.0125 * sin(s * 5.3 + 2.1))
}
let cl_coil_dr = fn(th: float, r0: float, b: float, pitch: float, t0: float, tmax: float, kap: float) -> float {
let gr = smoothstep(0.0, 6.2831853, th)
let s = cl_coil_s(th, r0, b) / (2.0 * t0)
let ds = (r0 + b * th) / (2.0 * t0)
let ty = clamp((th - tmax + 1.5707963) / 1.5707963, 0.0, 1.0)
let dtuck = 0.30 * pitch * 6.0 * ty * (1.0 - ty) / 1.5707963
return b + 2.0 * b * kap * cos(th - tmax) - dtuck + pitch * gr * (0.0175 * 4.2 * cos(s * 4.2 + 0.7) + 0.0125 * 5.3 * cos(s * 5.3 + 2.1)) * ds
}
// The tube's girth at `th`: a fine ripple of swells and pinches of about a
// twentieth, one every tube width or so; the inner end a fatter blob, the
// tail tapering a little toward its tip.
let cl_coil_t = fn(th: float, r0: float, b: float, t0: float, tmax: float) -> float {
let s = cl_coil_s(th, r0, b) / (2.0 * t0)
let sw = 1.0 + 0.05 * (0.6 * sin(s * 4.0 + 1.1) + 0.4 * sin(s * 5.6 + 0.3))
let inner = 1.0 + 0.30 * exp(-th / 1.8)
let tail = 1.0 - 0.14 * smoothstep(tmax - 1.6, tmax, th)
return t0 * sw * inner * tail
}
// One candidate turn for the point `q`: the point of the centre line
// nearest `q` on the turn that crosses its ray at angle `th`, found by two
// Gauss-Newton steps from there along the line (clamped to the coil's ends,
// where the nearest point is the end itself and the tube a rounded tip).
// Returns the distance to the tube's surface (.x, negative inside), the way
// out from the centre line (.yz) and the angle of that nearest point (.w).
let cl_coil_c = fn(q: vec2, th: float, r0: float, b: float, pitch: float, t0: float, tmax: float, kap: float) -> vec4 {
var a = clamp(th, 0.0, tmax)
let r1 = cl_coil_r(a, r0, b, pitch, t0, tmax, kap)
let d1 = cl_coil_dr(a, r0, b, pitch, t0, tmax, kap)
let u1 = vec2(cos(a), sin(a))
let v1 = u1 * d1 + vec2(-u1.y, u1.x) * r1
a = clamp(a + clamp(dot(q - u1 * r1, v1) / max(dot(v1, v1), 0.0001), -1.0, 1.0), 0.0, tmax)
let r2 = cl_coil_r(a, r0, b, pitch, t0, tmax, kap)
let d2 = cl_coil_dr(a, r0, b, pitch, t0, tmax, kap)
let u2 = vec2(cos(a), sin(a))
let v2 = u2 * d2 + vec2(-u2.y, u2.x) * r2
a = clamp(a + clamp(dot(q - u2 * r2, v2) / max(dot(v2, v2), 0.0001), -1.0, 1.0), 0.0, tmax)
let rr = cl_coil_r(a, r0, b, pitch, t0, tmax, kap)
let tt = cl_coil_t(a, r0, b, t0, tmax)
let pc = vec2(cos(a), sin(a)) * rr
let lp = length(q - pc)
let gg = (q - pc) / max(lp, 0.0001)
return vec4(lp - tt, gg.x, gg.y, a)
}
// The turn of the coil nearest `q`, as `cl_coil_c` gives it, among the
// four turns that can lie along its ray.
let cl_coil = fn(q: vec2, r0: float, b: float, pitch: float, t0: float, tmax: float, kap: float) -> vec4 {
let tau = 6.2831853
var ph = atan2(q.y, q.x)
if ph < 0.0 {
ph = ph + tau
}
let k0 = floor(((length(q) - r0) / b - ph) / tau)
let c0 = cl_coil_c(q, ph + tau * (k0 - 1.0), r0, b, pitch, t0, tmax, kap)
let c1 = cl_coil_c(q, ph + tau * k0, r0, b, pitch, t0, tmax, kap)
let c2 = cl_coil_c(q, ph + tau * (k0 + 1.0), r0, b, pitch, t0, tmax, kap)
let c3 = cl_coil_c(q, ph + tau * (k0 + 2.0), r0, b, pitch, t0, tmax, kap)
var best = c0
if c1.x < best.x {
best = c1
}
if c2.x < best.x {
best = c2
}
if c3.x < best.x {
best = c3
}
return best
}
// How far beyond the nearest turn the next one lies: where the two meet,
// the crease. A turn less than half a turn along the tube from the nearest
// (an end and the run it closes) is the same surface and does not count.
let cl_coil_gap = fn(q: vec2, r0: float, b: float, pitch: float, t0: float, tmax: float, kap: float, best: vec4) -> float {
let tau = 6.2831853
var ph = atan2(q.y, q.x)
if ph < 0.0 {
ph = ph + tau
}
let k0 = floor(((length(q) - r0) / b - ph) / tau)
let c0 = cl_coil_c(q, ph + tau * (k0 - 1.0), r0, b, pitch, t0, tmax, kap)
let c1 = cl_coil_c(q, ph + tau * k0, r0, b, pitch, t0, tmax, kap)
let c2 = cl_coil_c(q, ph + tau * (k0 + 1.0), r0, b, pitch, t0, tmax, kap)
let c3 = cl_coil_c(q, ph + tau * (k0 + 2.0), r0, b, pitch, t0, tmax, kap)
var sec = 1000.0
if abs(c0.w - best.w) > 3.1415927 {
sec = min(sec, c0.x)
}
if abs(c1.w - best.w) > 3.1415927 {
sec = min(sec, c1.x)
}
if abs(c2.w - best.w) > 3.1415927 {
sec = min(sec, c2.x)
}
if abs(c3.w - best.w) > 3.1415927 {
sec = min(sec, c3.x)
}
return sec - best.x
}
mod.widgets.RotaryFlat.clip_x = false
mod.widgets.RotaryFlat.clip_y = false
mod.widgets.RotaryFlat.draw_bg.vertex = fn() {
let g = cl_grow(self.rect_pos, self.rect_size, 5.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 = 5.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 - 24.0)
let dia = min(room.x, room.y)
let rr = dia * 0.5 - 1.0
let c = vec2(Finish.snap(o.x + size.x * 0.5, px) - o.x, Finish.snap(o.y + 24.0 + room.y * 0.5, px) - o.y)
let gap = self.gap * PI / 180.0
let aw = 3.6
let ar = floor((rr - aw * 0.5) / px) * px
let lo = min(self.origin_pos, self.slide_pos)
let hi = max(self.origin_pos, self.slide_pos)
let v0 = mix(0.0, lo, self.arc_origin)
let v1 = mix(self.slide_pos, hi, self.arc_origin)
var out = cl_arc(p, c, ar, aw, px, v0, v1, gap, self.disabled)
// the coil: 2.3 turns of a tube `t0` in radius, its turns a little
// closer than its girth so each presses into the next; the outline's
// circle centred in the scale, its side, the ripple and the tail's tip
// inside it
let avail = ar - aw * 0.5 - 2.5
let turns = 2.3
let tau = 6.2831853
let tmax = tau * turns
let kap = 1.0
let pitch = avail / 2.83
let b = pitch / tau
let t0 = 0.56 * pitch
let r0 = 0.32 * pitch
// the point it turns about: its centre of mass, the centroid of its
// filled area, in its own frame; measured once from that area (every
// length of the coil is a multiple of the pitch, so it scales with it)
let co = vec2(-0.0341, -0.0564) * pitch
// turned so the tip points at the value
let rce = cl_coil_r(tmax, r0, b, pitch, t0, tmax, kap)
let dre = cl_coil_dr(tmax, r0, b, pitch, t0, tmax, kap)
let tg = normalize(vec2(cos(tmax), sin(tmax)) * dre + vec2(-sin(tmax), cos(tmax)) * rce)
let tip0 = vec2(cos(tmax), sin(tmax)) * rce + tg * cl_coil_t(tmax, r0, b, t0, tmax)
let tip = tip0 - co
let ang = gap * 0.5 + (2.0 * PI - gap) * self.slide_pos
let al = atan2(cos(ang), -sin(ang)) - atan2(tip.y, tip.x)
let cs = cos(al)
let sn = sin(al)
let press = self.drag * live
let hover = max(self.hover, self.focus * 0.5) * live
// what it throws on the slab: a soft contact shadow down and to the
// right, from the coil's own outline
let lift = 1.0 + t0 * 0.07 + 0.3 * hover
let ps = p - c - vec2(0.7, 1.45) * lift
let qs = vec2(ps.x * cs + ps.y * sn, ps.y * cs - ps.x * sn) + co
let sds = cl_coil(qs, r0, b, pitch, t0, tmax, kap).x
let pt = p - c - vec2(0.15, 0.35)
let qt = vec2(pt.x * cs + pt.y * sn, pt.y * cs - pt.x * sn) + co
let sdt = cl_coil(qt, r0, b, pitch, t0, tmax, kap).x
let tight = (1.0 - smoothstep(-0.8, 1.4, sdt)) * 0.16
let soft = (1.0 - smoothstep(-1.8 * lift, 3.0 * lift, sds)) * 0.26
let sa = clamp((tight + soft * (1.0 - tight)) * (1.0 - 0.5 * press) * mix(1.0, 0.5, self.disabled), 0.0, 1.0)
out = Finish.over(out, cl_sh(sa))
// the tube
let dp = p - c
let q = vec2(dp.x * cs + dp.y * sn, dp.y * cs - dp.x * sn) + co
let cl = cl_coil(q, r0, b, pitch, t0, tmax, kap)
let a = Finish.cover(cl.x, px)
if a > 0.001 {
let tt = cl_coil_t(cl.w, r0, b, t0, tmax)
// its section: pressed flat on top, a broad soft top and sides
// rounding down into the crease, flatter still at the fat inner end;
// the slope across it, turned back to the screen
let flat = exp(-cl.w / 1.5)
let s = clamp((cl.x + tt) / tt, 0.0, 1.0)
let sl = pow(s, mix(1.7, 2.3, flat))
let zz = sqrt(max(1.0 - sl * sl, 0.0))
let gs = vec2(cl.y * cs - cl.z * sn, cl.y * sn + cl.z * cs)
// the hands' marks on its skin, laid in the coil's own frame so they
// turn with it: the lumps and shallow thumb dents every part of the
// sheet has, tilting the skin a little, strongest on the tops
let bq = cl_bump(q, 0.61)
let bs = vec2(bq.x * cs - bq.y * sn, bq.x * sn + bq.y * cs) * (0.4 + 0.6 * zz)
let nf = normalize(vec3(gs * sl, zz))
let n = normalize(vec3(gs * sl + bs * zz, zz))
let lk = vec3(-0.30, -0.57, 0.77)
// where it meets the next turn: the crease, a soft wide groove in
// the turns' own deeper colour, no dark line
let cg = cl_coil_gap(q, r0, b, pitch, t0, tmax, kap, cl)
let cw = 1.0 - smoothstep(0.0, 0.85 * tt, cg)
// light bounced up from the slab onto its lower outer side
let bounce = clamp(dot(gs, vec2(0.47, 0.88)), 0.0, 1.0) * smoothstep(0.55, 1.0, s) * (1.0 - cw)
let ao = cw * pow(1.0 - zz, 1.5) * 0.42 + (1.0 - smoothstep(0.0, 2.5 * px, cg)) * 0.12
// each turn's own round form, a little more than the soft light
// alone gives it: its lit flank paler, its far flank deeper
let form = clamp((dot(nf, lk) - 0.77) * 0.40, -0.12, 0.10)
var shade = (dot(n, lk) - dot(nf, lk)) * 0.74 - ao + form
// a faint smudge or two where a thumb rubbed it
let sm1 = exp(-dot((q - vec2(0.35, -0.25) * rce) * vec2(1.0, 2.2), (q - vec2(0.35, -0.25) * rce) * vec2(1.0, 2.2)) / (0.10 * rce * rce))
let sm2 = exp(-dot((q - vec2(-0.30, 0.40) * rce) * vec2(2.0, 1.0), (q - vec2(-0.30, 0.40) * rce) * vec2(2.0, 1.0)) / (0.07 * rce * rce))
shade = shade - 0.05 * sm1 + 0.04 * sm2
// the odd air pit and a tool mark, as on every part; two soft
// fingerprints pressed into the tops at other angles, their ridges
// as coarse as the ground's so they never beat against the coil's
// own shading; and a few smoothing strokes along the tube, where a
// finger ran down it. All of it on the tops, gone toward the crease.
let top = smoothstep(0.15, 0.6, zz)
var mk = cl_marks(q, vec2(rce, rce) * 0.7, 0.61, 0.0)
let fsz = clamp(rce * 0.5, 10.0, 24.0)
mk = mk + cl_print_c(q, vec2(cos(2.9), sin(2.9)) * rce * 0.55, fsz, 2.2, 0.37, 2.2) * 0.045
mk = mk + cl_print_c(q, vec2(cos(5.1), sin(5.1)) * rce * 0.8, fsz * 0.9, 4.4, 0.83, 2.3) * 0.045
let across = (cl.x + tt) / tt
let along = cl.w * (r0 + b * cl.w)
let stroke = sin(across * 9.4 + (Finish.noise(vec2(along * 0.06, 3.7)) - 0.5) * 4.0) * smoothstep(0.45, 0.8, Finish.noise(vec2(along * 0.035 + 1.3, 8.1)))
shade = shade + (mk + 0.022 * stroke) * top
// the mottle of a hand-mixed colour
let base = cl_tone(vec3(0.165, 0.400, 0.800), hover, press, self.disabled) * (1.0 + (Finish.fbm(q * 0.05 + vec2(19.3, 10.6)) - 0.5) * 0.12)
var col = cl_detail(cl_light(nf, base), shade)
col = mix(col, mix(base, vec3(1.0, 1.0, 1.0), 0.3), bounce * 0.10)
// a broad satin sheen along the crest
let sp = pow(max(dot(n, vec3(-0.159, -0.303, 0.940)), 0.0), 5.0) * 0.05 * (1.0 - cw)
col = col + mix(vec3(1.0, 1.0, 1.0), base, 0.35) * sp
col = col + g3(Finish.grain(dp / px, 0.010))
col = clamp(col, vec3(0.0, 0.0, 0.0), vec3(1.0, 1.0, 1.0))
out = Finish.over(out, vec4(col * a, a))
}
return Finish.over(out, cl_focus(length(p - c) - ar - aw * 0.5, px, self.focus * live))
}
// ---------------------------------------------------------------------------
// SCREENS. The readouts, the screen, the dial's readout and the needle
// meter share one housing: a frame of charcoal clay rolled round a pane of
// dark glass, the slab's edge shading the pane under its top, a quiet
// diagonal sheen across the glass. `b` is the frame's width.
let cl_screen = fn(p: vec2, lo: vec2, hi: vec2, r: float, b: float, px: float, face: vec3, seed: float) -> vec4 {
let c = (lo + hi) * 0.5
let h = (hi - lo) * 0.5
let rr = min(r, min(h.x, h.y))
var out = cl_drop(p, c, h, rr, 1.0, 1.0)
out = Finish.over(out, cl_lump(p, c, h, rr, b + 1.0, 0.25, 0.04, vec3(0.333, 0.306, 0.314), seed, px, 0.6))
let hf = max(h - vec2(b, b), vec2(0.5, 0.5))
let rf = max(rr - b, 1.5)
let fd = cl_rbox(p, c, hf, rf)
let insh = clamp(Finish.inset(p, c, hf, rf, vec2(0.6, 1.6), 1.6), 0.0, 1.0)
var col = face * (1.0 - 0.55 * insh)
col = mix(col, col * 0.5, Finish.band(fd, 0.0, px, px))
let fa = Finish.cover(fd, px)
return Finish.over(out, vec4(col * fa, fa))
}
// The glass over the pane: a straight diagonal band from the upper left and
// a lighter line just inside the top edge.
let cl_glass = fn(p: vec2, lo: vec2, hi: vec2, r: float, b: float, px: float, sheen: float) -> vec4 {
let c = (lo + hi) * 0.5
let h = (hi - lo) * 0.5
let hf = max(h - vec2(b, b), vec2(0.5, 0.5))
let rf = max(min(r, min(h.x, h.y)) - b, 1.5)
let fd = cl_rbox(p, c, hf, rf)
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.8
let a = clamp(sheen * (band + top), 0.0, 1.0) * Finish.cover(fd + px, px)
return vec4(a, a, a, a)
}
mod.widgets.ScreenView.clip_x = false
mod.widgets.ScreenView.clip_y = false
mod.widgets.ScreenView.padding = mod.turtle.Inset{top: 8.0 right: 10.0 bottom: 8.0 left: 10.0}
mod.widgets.ScreenView.bezel = 3.5
mod.widgets.ScreenView.sheen = 0.06
mod.widgets.ScreenView.draw_bg.vertex = fn() {
let g = cl_grow(self.rect_pos, self.rect_size, 5.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.ScreenView.draw_bg.pixel = fn() {
let m = 5.0
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let o = self.rect_pos
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o
let hi = vec2(Finish.snap(o.x + self.rect_size.x, px), Finish.snap(o.y + self.rect_size.y, px)) - o
var res = cl_screen(p, lo, hi, 7.0, max(self.bezel, 3.0), px, self.color.rgb, fract(self.rect_size.x * 0.031))
if self.scan > 0.0 {
let s = Finish.scan(p.y, px, 3.0 * px, self.scan)
res = vec4(res.rgb * mix(1.0, s, 0.6), res.a)
}
return res * self.color.a
}
mod.widgets.ScreenView.draw_glass.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * self.rect_size
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)) - self.rect_pos
return cl_glass(p, lo, hi, 7.0, max(self.bezel, 3.0), px, self.sheen)
}
// The dial's readout is the same housing, small.
mod.widgets.RotaryFlat.text_input.padding = mod.turtle.Inset{left: 7.0 right: 7.0 top: 3.0 bottom: 3.0}
mod.widgets.RotaryFlat.text_input.min_height = 22.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(1.5, 2.0)
var out = cl_screen(p, lo, hi, 6.0, 2.5, px, vec3(0.173, 0.153, 0.161), fract(self.rect_size.x * 0.043))
return Finish.over(out, cl_glass(p, lo, hi, 6.0, 2.5, px, 0.06))
}
mod.widgets.RotaryFlat.text_input.draw_text.color = #f6ead2
mod.widgets.RotaryFlat.text_input.draw_text.color_hover = #fff6e4
mod.widgets.RotaryFlat.text_input.draw_text.color_focus = #fff6e4
mod.widgets.RotaryFlat.text_input.draw_text.color_empty = #b5a998
mod.widgets.RotaryFlat.text_input.draw_text.text_style = mod.theme.font_label{font_size: 9.0}
// THE NEEDLE METER sits in the same housing: its scale printed on the dark
// glass in the screen's cream, the red zone a coral band along the arc, the
// needle coral, its root under a yellow clay bead.
mod.widgets.NeedleMeter.clip_x = false
mod.widgets.NeedleMeter.clip_y = false
mod.widgets.NeedleMeter.draw_bg.color_zone = uniform(#f06a5c)
mod.widgets.NeedleMeter.draw_bg.color_needle = uniform(#f2836f)
mod.widgets.NeedleMeter.draw_bg.vertex = fn() {
let m = 5.0
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
mod.widgets.NeedleMeter.draw_bg.pixel = fn() {
let m = 5.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 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
let b = 3.5
var res = cl_screen(p, lo, hi, 7.0, b, px, self.color.rgb, 0.83)
let c = (lo + hi) * 0.5
let h = (hi - lo) * 0.5
let hf = max(h - vec2(b, b), vec2(0.5, 0.5))
let fd = cl_rbox(p, c, hf, 3.5)
let inface = Finish.cover(fd + px, px)
var col = res.rgb / max(res.a, 0.001)
let pv = self.pivot()
let rr = self.scale_radius()
let q = p - pv
let r = length(q)
let ang = atan2(q.x, 0.0 - q.y)
let sweep = self.sweep * 0.017453293
let t = ang / sweep + 0.5
let on_scale = step(-0.0001, t) * step(t, 1.0001)
let ink = self.color_scale.rgb
let arc = Finish.cover(abs(r - rr) - px * 0.6, 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.6, 1.1, major), px) * Finish.cover(radial, px) * step(0.0, 0.0 - q.y * cos(ak) + q.x * sin(ak))
col = mix(col, ink, max(arc, tick) * 0.9 * inface)
if self.zone < 0.999 {
let zone_t = Finish.cover((self.zone - t) * sweep * r, px) * Finish.cover((t - 1.0) * sweep * r, px)
let zone_r = Finish.cover(max(rr + px - r, r - rr - 2.5), px)
col = mix(col, self.color_zone.rgb, zone_t * zone_r * inface)
}
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.75, px) * Finish.cover(along - (rr + 3.0), px) * Finish.cover(0.0 - along, px)
col = mix(col, self.color_needle.rgb, needle * inface)
// the bead over the needle's root
let br = max(self.pivot_size, 3.0)
let bead = cl_ball(p, pv, br, 1.0, vec3(0.992, 0.855, 0.420), 0.47, px, 0.3, 0.1)
col = col * (1.0 - bead.a * inface) + bead.rgb * inface
res = vec4(col * res.a, res.a)
res = Finish.over(res, cl_glass(p, lo, hi, 7.0, b, px, 0.06))
return res * self.color.a * self.opacity
}
// ---------------------------------------------------------------------------
// TEXT FIELDS. A slab of white clay inside the field's box, its shadow in
// the room left under it; words sit on the clay. Off, the slab is grey;
// with the focus a sky-blue thread lies along its rim.
let cl_field = fn(p: vec2, size: vec2, o: vec2, px: float, hover: float, focus: float, dis: float, r: float, col: vec3) -> vec4 {
let lo = vec2(Finish.snap(o.x + 0.5, px), Finish.snap(o.y + 0.5, px)) - o
let hi = vec2(Finish.snap(o.x + size.x - 2.0, px), Finish.snap(o.y + size.y - 2.5, px)) - o
let h = (hi - lo) * 0.5
let c = lo + h
let rr = min(r, h.y)
let k = cl_tone(col, hover, 0.0, dis)
var out = cl_drop(p, c, h, rr, mix(0.9, 0.5, dis), 0.75)
out = Finish.over(out, cl_lump(p, c, h, rr, min(h.y * 0.55, 6.0), 0.3, 0.05, k, fract(size.x * 0.0371 + size.y * 0.13), px, 0.9))
let d = cl_rbox(p, c, h, rr)
let f = Finish.cover(abs(d + 1.2) - 0.75, px) * focus * (1.0 - dis)
let lit = 1.0 - 0.3 * smoothstep(-1.0, 1.0, dot(p - c, vec2(0.47, 0.88)) / max(h.y, 1.0))
return Finish.over(out, vec4(vec3(0.310, 0.498, 0.847) * lit * f, f))
}
mod.widgets.TextInputFlat.padding = mod.turtle.Inset{left: 9.0 right: 9.0 top: 4.0 bottom: 4.0}
mod.widgets.TextInputFlat.draw_bg.pixel = fn() {
// The value field inside a slider or a well draws no box of its own.
if self.color.a < 0.01 {
return vec4(0.0, 0.0, 0.0, 0.0)
}
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return cl_field(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, self.hover, self.focus, self.disabled, 7.0, vec3(0.969, 0.957, 0.945))
}
mod.widgets.TextInputFlat.draw_text.color_empty = #43365a
mod.widgets.TextInputFlat.draw_text.color_disabled = #847872
mod.widgets.TextInputFlat.draw_cursor.color = #2a211c
// NUMBER FIELDS. The white slab, and on its right a stepper: a greyer
// white lump split by a pressed groove, a charcoal chevron on each half;
// the half under the pointer lifts, the held half is squashed.
mod.widgets.NumberField.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return cl_field(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, self.hover, self.focus, self.disabled, 7.0, vec3(0.969, 0.957, 0.945))
}
mod.widgets.NumberField.pad_left = 9.0
mod.widgets.NumberSpin.width = 16.0
mod.widgets.NumberSpin.draw_text.color = #00000000
mod.widgets.NumberSpin.draw_text.color_disabled = #00000000
mod.widgets.NumberSpin.draw_bg.pixel = fn() {
let px = 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 + 1.5, px)) - o
let hi = vec2(Finish.snap(o.x + self.rect_size.x - 3.0, px), Finish.snap(o.y + self.rect_size.y - 4.0, px)) - o
let c = (lo + hi) * 0.5
let h = (hi - lo) * 0.5
let mid = Finish.snap(o.y + c.y, px) - o.y
let upper = step(p.y, mid)
let hot = mix(max(0.0, -self.hot), max(0.0, self.hot), upper) * live
let dn = mix(max(0.0, -self.down), max(0.0, self.down), upper) * live
let col = cl_tone(vec3(0.902, 0.890, 0.878), hot, dn, self.disabled)
var out = cl_drop(p, c, h, 4.0, 0.6, 0.6)
out = Finish.over(out, cl_lump(p, c, h, 4.0, 3.0, 0.15, 0.06, col, 0.39, px, 0.5))
// the groove between the halves
let gy = abs(p.y - mid)
let gr = (1.0 - smoothstep(0.3, 0.8, gy)) * step(abs(p.x - c.x), h.x - 2.5) * 0.35
out = Finish.over(out, cl_sh(gr))
let ink = mix(vec3(0.200, 0.180, 0.188), vec3(0.560, 0.540, 0.550), self.disabled)
let cu = vec2(c.x, (lo.y + mid) * 0.5 + 0.4)
let cd = vec2(c.x, (mid + hi.y) * 0.5 - 0.4)
out = Finish.over(out, cl_hook(p, cu + vec2(-2.6, 1.3), cu + vec2(0.0, -1.3), cu + vec2(2.6, 1.3), 0.75, ink, 0.17, px))
return Finish.over(out, cl_hook(p, cd + vec2(-2.6, -1.3), cd + vec2(0.0, 1.3), cd + vec2(2.6, -1.3), 0.75, ink, 0.29, px))
}
// The drag-number: the white slab, its value a deeper wash on the clay.
mod.widgets.ValueInput.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * self.rect_size
var out = cl_field(p, self.rect_size, self.rect_pos, px, max(self.hover, self.drag), self.focus, 0.0, 7.0, vec3(0.969, 0.957, 0.945))
let d = cl_rbox(p, self.rect_size * 0.5 - vec2(0.75, 1.0), self.rect_size * 0.5 - vec2(2.5, 2.75), 5.0)
let wx = 2.0 + (self.rect_size.x - 4.0) * max(self.fill, 0.0)
let wash = step(0.0, self.fill) * step(p.x, wx) * Finish.cover(d, px) * 0.12
return Finish.over(out, vec4(0.188 * wash, 0.122 * wash, 0.200 * wash, wash))
}
mod.widgets.ValueInput.draw_text.color = #2a211c
mod.widgets.ValueInput.draw_text.text_style = mod.theme.font_label{font_size: 8.5 line_spacing: 1.0}
mod.widgets.ValueInput.arrow_color = #3b302a
mod.widgets.ValueInput.text_input.draw_bg.color = #00000000
// ---------------------------------------------------------------------------
// DROP-DOWNS. A mint pill with a charcoal snake bent into a chevron at its
// right end. Open, it is held squashed.
// `ins` 1: the drop-down stands in the field row, whose box is two points
// taller than a pill; its pill takes the field slab's rows.
let cl_dropdown = fn(pos0: vec2, size0: vec2, o0: vec2, px: float, hover: float, held: float, focus: float, dis: float, ins: float) -> vec4 {
let m = 5.0
let p = pos0 * (size0 + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) - vec2(0.0, 0.5 * ins)
let size = size0 - vec2(1.5 * ins, 3.0 * ins)
let o = o0 + vec2(0.0, 0.5 * ins)
let pos = (p + vec2(m, m)) / (size + vec2(2.0 * m, 2.0 * m))
var out = cl_button(pos, size, o, px, hover, held, focus, dis, 0.0, 0.0, vec4(8.0, 8.0, 8.0, 8.0), 0.0, m, vec3(0.224, 0.820, 0.455))
let live = 1.0 - dis
let dn = held * live * 0.5
let cc = vec2(Finish.snap(o.x + size.x - 13.0, px) - o.x, size.y * 0.5 + dn)
let ink = mix(vec3(0.200, 0.180, 0.188), vec3(0.560, 0.540, 0.550), dis)
return Finish.over(out, cl_hook(p, cc + vec2(-3.4, -1.7), cc + vec2(0.0, 1.7), cc + vec2(3.4, -1.7), 0.95, ink, 0.51, px))
}
mod.widgets.DropDownFlat.padding = mod.turtle.Inset{left: 10.0 right: 30.0 top: 4.0 bottom: 4.0}
mod.widgets.DropDown.padding = mod.turtle.Inset{left: 10.0 right: 30.0 top: 4.0 bottom: 4.0}
mod.widgets.DropDownFlat.clip_x = false
mod.widgets.DropDownFlat.clip_y = false
mod.widgets.DropDownFlat.draw_bg.vertex = fn() {
let g = cl_grow(self.rect_pos, self.rect_size, 5.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 cl_dropdown(self.pos, self.rect_size, self.rect_pos, px, self.hover, max(self.down, self.active), self.focus, self.disabled, 1.0)
}
// The select wears a button of its own, built before this sheet: it is given
// the drop-down's face, its grown quad, and no clip, here by name.
mod.widgets.Select.clip_x = false
mod.widgets.Select.clip_y = false
mod.widgets.MultiSelect.clip_x = false
mod.widgets.MultiSelect.clip_y = false
mod.widgets.Select.face.clip_x = false
mod.widgets.Select.face.clip_y = false
mod.widgets.MultiSelect.face.clip_x = false
mod.widgets.MultiSelect.face.clip_y = false
mod.widgets.Select.face.draw_bg.vertex = fn() {
let g = cl_grow(self.rect_pos, self.rect_size, 5.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.MultiSelect.face.draw_bg.vertex = fn() {
let g = cl_grow(self.rect_pos, self.rect_size, 5.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 cl_dropdown(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, 0.0)
}
mod.widgets.MultiSelect.face.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return cl_dropdown(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, 0.0)
}
mod.widgets.Select.face.padding.right = 30.0
mod.widgets.MultiSelect.face.padding.right = 30.0
// The combo box: a mint slab one types on, with the chevron at its end.
mod.widgets.ComboBox.padding = mod.turtle.Inset{left: 10.0 right: 30.0 top: 4.0 bottom: 4.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
let held = self.down * self.hover * live
var out = cl_field(p, size, o, px, self.hover, self.focus, self.disabled, 8.0, vec3(0.224, 0.820, 0.455))
let cc = vec2(Finish.snap(o.x + size.x - 14.5, px) - o.x, (size.y - 2.0) * 0.5 + held * 0.5)
let ink = mix(vec3(0.200, 0.180, 0.188), vec3(0.560, 0.540, 0.550), self.disabled)
return Finish.over(out, cl_hook(p, cc + vec2(-3.4, -1.7), cc + vec2(0.0, 1.7), cc + vec2(3.4, -1.7), 0.95, ink, 0.51, px))
}
// ---------------------------------------------------------------------------
// SEGMENTS AND TABS. One slab across the strip; the chosen segment is a
// pocket of charcoal pressed into it, its word lit cream.
let cl_strip = fn(p: vec2, size: vec2, o: vec2, px: float, col: vec3) -> 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 c = (lo + hi) * 0.5
let h = (hi - lo) * 0.5
let r = cl_br(size.y)
var out = cl_drop(p, c, h, r, 1.0, 1.0)
return Finish.over(out, cl_lump(p, c, h, r, min(h.y * 0.6, 7.0), 0.4, 0.08, col, fract(size.x * 0.0293 + 0.71), px, 1.0))
}
mod.widgets.SegmentedControlFlat.segment_height = 22.0
mod.widgets.SegmentedControlFlat.draw_bg.vertex = fn() {
let g = cl_grow(self.rect_pos, self.rect_size, 5.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)
let m = 5.0
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
return cl_strip(p, self.rect_size, self.rect_pos, px, vec3(0.992, 0.855, 0.420))
}
// The pill is the segment's own box, two points in from the strip; the
// pocket is pressed in that box, a point in at its sides.
mod.widgets.SegmentedControlFlat.draw_pill.vertex = fn() {
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(2.0, 2.0), self.rect_size + vec2(4.0, 4.0))
}
mod.widgets.SegmentedControlFlat.draw_pill.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let o = self.rect_pos
let size = self.rect_size
let p = self.pos * (size + vec2(4.0, 4.0)) - vec2(2.0, 2.0)
let lo = vec2(Finish.snap(o.x + 0.5, px), Finish.snap(o.y - 0.5, px)) - o
let hi = vec2(Finish.snap(o.x + size.x - 0.5, px), Finish.snap(o.y + size.y + 0.5, px)) - o
let c = (lo + hi) * 0.5
let h = (hi - lo) * 0.5
let rim = Finish.ring_out(cl_rbox(p, c, h, 7.0), 0.0, 1.2, px) * 0.30
var out = vec4(rim, rim, rim, rim)
return Finish.over(out, cl_pocket(p, c, h, 7.0, vec3(0.314, 0.290, 0.298), fract(size.x * 0.047), px))
}
mod.widgets.SegmentedControlFlat.draw_text.color = #2a211c
mod.widgets.SegmentedControlFlat.draw_text_selected.color = #fbf1dc
mod.widgets.SegmentedControlFlat.palette.on_neutral_container = #fbf1dc
mod.widgets.SegmentedControl.palette.on_neutral_container = #fbf1dc
// The tab strip: a periwinkle slab; the tab in use a charcoal pocket, a
// hovered tab a pale dent, the quiet ones split by short pressed grooves.
// The tabs share the strip down to 40 points each, so three of them and the
// add mark stand inside a narrow column; their words are the round face
// rolled narrower (its width axis at 75), and the close mark is a small
// cross at the right of its cell, clear of the word.
mod.widgets.Tabs.height = 26.0
mod.widgets.Tabs.tab_min_width = 40.0
mod.widgets.Tabs.draw_bg.vertex = fn() {
let g = cl_grow(self.rect_pos, self.rect_size, 5.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.Tabs.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = 5.0
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
return cl_strip(p, self.rect_size, self.rect_pos, px, vec3(0.392, 0.792, 0.996))
}
mod.widgets.Tabs.draw_tab.pixel = fn() {
return vec4(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
let h = self.rect_size * 0.5 - vec2(2.5, 3.0)
let d = cl_rbox(p, self.rect_size * 0.5, h, 6.0)
let sh = clamp(Finish.inset(p, self.rect_size * 0.5, h, 6.0, vec2(0.5, 1.0), 1.2), 0.0, 1.0) * 0.18
let a = Finish.cover(d, px)
let l = a * 0.14
return Finish.over(vec4(l, l, l, l), cl_sh(sh * a))
}
mod.widgets.Tabs.draw_tab_active.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let o = self.rect_pos
let size = self.rect_size
let p = self.pos * size
let lo = vec2(Finish.snap(o.x + 2.5, px), Finish.snap(o.y + 2.5, px)) - o
let hi = vec2(Finish.snap(o.x + size.x - 2.5, px), Finish.snap(o.y + size.y - 2.5, px)) - o
let c = (lo + hi) * 0.5
let h = (hi - lo) * 0.5
return cl_pocket(p, c, h, 7.0, vec3(0.314, 0.290, 0.298), fract(size.x * 0.047 + 0.3), px)
}
mod.widgets.Tabs.draw_sep.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * self.rect_size
let y0 = self.rect_size.y * 0.28
let y1 = self.rect_size.y * 0.72
let iny = smoothstep(y0, y0 + 1.5, p.y) * (1.0 - smoothstep(y1 - 1.5, y1, p.y))
let dk = step(p.x, px) * iny * 0.30
let lt = step(px, p.x) * step(p.x, 2.0 * px) * iny * 0.40
return Finish.over(cl_sh(dk), vec4(lt, lt, lt, lt))
}
mod.widgets.Tabs.draw_mark.color = #f3e6cf
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
// the close cross keeps to the right of its cell, the plus to its middle
let cxo = mix(self.rect_size.x * 0.5 + 3.6, self.rect_size.x * 0.5, self.plus)
let c = vec2(Finish.snap(o.x + cxo, px), Finish.snap(o.y + self.rect_size.y * 0.5, px)) - o
// two short snakes crossed: an x, or turned upright, a plus
let a = mix(0.7853981634, 0.0, self.plus)
let r = mix(2.7, 4.0, self.plus)
let u = vec2(cos(a), sin(a)) * r
let v = vec2(-sin(a), cos(a)) * r
let col = mix(self.color.rgb, vec3(0.231, 0.208, 0.216), self.plus)
var out = cl_snake_drop(p, c - u, c + u, 0.95, 0.7)
out = Finish.over(out, cl_snake_drop(p, c - v, c + v, 0.95, 0.7))
out = Finish.over(out, cl_snake(p, c - u, c + u, 0.95, col, 0.33, px, 0.3))
out = Finish.over(out, cl_snake(p, c - v, c + v, 0.95, col, 0.37, px, 0.3))
return out * self.color.a
}
mod.widgets.Tabs.draw_text.color = #2a211c
mod.widgets.Tabs.draw_text_active.color = #fbf1dc
// The strip centres a 14 point line box, which leaves the capitals high in
// a 26 point slab: they are dropped to its middle.
mod.widgets.Tabs.draw_text.text_style = mod.theme.font_regular{font_size: 7.25 font_family: mod.theme.font_regular.font_family{ibm_plex_text := FontMember{res: crate_resource("self:resources/Fredoka-Variable.ttf") weight: 520.0 axes: "wdth 75" asc: 0.0 desc: 0.0}}}
mod.widgets.Tabs.draw_text_active.text_style = mod.theme.font_regular{font_size: 7.25 font_family: mod.theme.font_regular.font_family{ibm_plex_text := FontMember{res: crate_resource("self:resources/Fredoka-Variable.ttf") weight: 540.0 axes: "wdth 75" asc: 0.0 desc: 0.0}}}
mod.widgets.Tabs.draw_text.text_style.top_drop = 0.22
mod.widgets.Tabs.draw_text_active.text_style.top_drop = 0.22
// ---------------------------------------------------------------------------
// PROGRESS. A thin groove pressed into the slab its whole length, and the
// value a violet snake laid in it, as thick as the bar. The unknown value is
// a rope twisted from a pink and a violet snake, still: a face may not run a
// clock.
mod.widgets.ProgressBarFlat.height = 10.0
mod.widgets.ProgressBarFlat.draw_bg.thickness = uniform(10.0)
mod.widgets.ProgressBarFlat.clip_x = false
mod.widgets.ProgressBarFlat.clip_y = false
mod.widgets.ProgressBarFlat.draw_bg.vertex = fn() {
let g = cl_grow(self.rect_pos, self.rect_size, 4.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.ProgressBarFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = 4.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 th = min(self.thickness, size.y)
let cy = Finish.snap(o.y + size.y * 0.5, px) - o.y
let x0 = Finish.snap(o.x + size.x * self.start, px) - o.x
let x1 = Finish.snap(o.x + size.x * self.end, px) - o.x
let rad = th * 0.5
let dis = step(4.5, self.intent)
// the groove
let gd = abs(p.y - cy) - 1.1
let gx = step(x0 + 1.0, p.x) * step(p.x, x1 - 1.0)
let ga = Finish.cover(gd, px) * gx * 0.22 * self.track
var out = cl_sh(ga)
let gl = Finish.cover(abs(p.y - cy - 1.1 - px * 0.5) - px * 0.5, px) * gx * 0.45 * self.track
out = Finish.over(out, vec4(gl, gl, gl, gl))
// the snake
var v1 = x0 + (x1 - x0) * clamp(self.value, 0.0, 1.0)
var col = vec3(0.541, 0.384, 0.788)
if self.intent > 0.5 {
col = self.fill_color().rgb
}
col = mix(col, vec3(0.792, 0.773, 0.780), dis * 0.85)
if self.value < 0.0 {
v1 = x1
let tw = fract((p.x - (p.y - cy) * 1.3) / 8.0)
let pink = smoothstep(0.42, 0.5, tw) * (1.0 - smoothstep(0.92, 1.0, tw))
col = mix(vec3(0.541, 0.384, 0.788), vec3(0.902, 0.522, 0.659), pink)
}
if v1 - x0 > 0.5 {
let a = vec2(x0 + rad, cy)
let b = vec2(max(v1 - rad, x0 + rad), cy)
out = Finish.over(out, cl_snake_drop(p, a, b, rad, 1.0))
var body = cl_snake(p, a, b, rad, col, fract(size.x * 0.019 + self.value * 0.37), px, 0.6)
if self.value < 0.0 {
// the twist: a crease where the two snakes meet
let tw2 = fract((p.x - (p.y - cy) * 1.3) / 8.0)
let crease = (1.0 - smoothstep(0.0, 0.07, abs(tw2 - 0.46))) + (1.0 - smoothstep(0.0, 0.07, abs(tw2 - 0.96)))
body = vec4(body.rgb * (1.0 - 0.28 * clamp(crease, 0.0, 1.0)), body.a)
}
out = Finish.over(out, body)
}
return out * self.opacity
}
// ---------------------------------------------------------------------------
// LEVEL METERS. A row of clay pellets, four points long with a point
// between: lit ones green, then tangerine from 70 % and red from 90 %; the
// rest charcoal. The hold is a yellow pellet; the over lamp a small bead.
mod.widgets.LevelMeter.height = 8.0
mod.widgets.LevelMeterColumn.width = 12.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 th = min(6.0, across - 1.5 - 4.0 * vert)
let lamp_room = self.lamp * (th + 4.0)
let bar = max(4.0, along - lamp_room)
let pad = 0.5
let span = max(1.0, bar - pad * 2.0)
let pitch = 5.0
let ci = floor((a - pad) / pitch)
let cnt = floor(span / pitch)
let ca = pad + (ci + 0.5) * pitch - 0.5
let share = clamp((ca - pad) / span, 0.0, 1.0)
let in_row = step(0.0, ci) * step(ci, cnt - 1.0)
let lit = step(ca, pad + span * self.level)
let ramp = mix(mix(vec3(0.388, 0.749, 0.478), vec3(0.957, 0.588, 0.290), step(0.7, share)), vec3(0.906, 0.333, 0.341), step(0.9, share))
var col = mix(mix(vec3(0.314, 0.290, 0.298), vec3(0.180, 0.165, 0.173), vert), ramp, lit)
if self.hold > 0.002 {
let hi_cell = floor((pad + span * self.hold - pad) / pitch)
col = mix(col, vec3(0.992, 0.855, 0.420), step(abs(ci - hi_cell), 0.5) * (1.0 - lit))
}
let pc = mix(vec2(ca, across * 0.5), vec2(across * 0.5, self.rect_size.y - ca), vert)
let ph = mix(vec2(1.9, th * 0.5), vec2(th * 0.5, 1.9), vert)
var out = vec4(0.0, 0.0, 0.0, 0.0)
if vert > 0.5 {
// standing, the pellets are set in a strip of charcoal clay
let sc = vec2(across * 0.5, self.rect_size.y - (pad + span * 0.5))
let sh = vec2(th * 0.5 + 2.0, span * 0.5 + 2.0)
out = cl_drop(p, sc, sh, 3.0, 1.0, 0.8)
out = Finish.over(out, cl_lump(p, sc, sh, 3.0, 2.5, 0.2, 0.03, vec3(0.255, 0.235, 0.243), 0.61, px, 0.5))
}
out = Finish.over(out, cl_drop(p, pc, ph, 1.6, 0.7 * in_row, 0.55))
out = Finish.over(out, cl_lump(p, pc, ph, 1.6, 1.6, 0.1, 0.05, col, fract(ci * 0.137 + 0.21), px, 0.4) * in_row)
if self.lamp > 0.5 {
let la = bar + 2.0 + th * 0.5 + 1.0
let lc = vec2(mix(la, across * 0.5, vert), mix(across * 0.5, self.rect_size.y - la, vert))
out = Finish.over(out, cl_led(p, lc, 2.6, px, vec3(0.910, 0.200, 0.180), step(0.5, self.over), 0.35))
}
return out * self.opacity
}
// ---------------------------------------------------------------------------
// SCROLL BARS. A shallow groove the length of the view and a yellow snake
// riding in it.
mod.widgets.ScrollBar.bar_size = 10.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 - 1.0, across * 0.5 - 2.0)
let sc = vec2(along * 0.5, across * 0.5)
let sd = cl_rbox(q, sc, sh, sh.y)
let ins = clamp(Finish.inset(p, mix(sc, sc.yx, vert), mix(sh, sh.yx, vert), sh.y, vec2(0.4, 0.8), 0.9), 0.0, 1.0)
let ga = Finish.cover(sd, px) * (0.07 + 0.20 * ins)
var out = cl_sh(ga)
let t0 = along * self.norm_scroll
let tl = along * self.norm_handle
let rad = 3.4
let ta = vec2(t0 + rad + 1.0, sc.y)
let tb = vec2(max(t0 + tl - rad - 1.0, ta.x), sc.y)
let sa = mix(ta, ta.yx, vert)
let sb = mix(tb, tb.yx, vert)
let col = cl_tone(vec3(0.992, 0.855, 0.420), self.hover, self.drag, 0.0)
out = Finish.over(out, cl_snake_drop(p, sa, sb, rad, 0.8))
out = Finish.over(out, cl_snake(p, sa, sb, rad, col, 0.41, px, 0.5))
return out * self.opacity
}
// ---------------------------------------------------------------------------
// VIEWS AND PANELS. A panel is a slab of violet clay with a thick rolled
// edge; the sunken panel is a slab of sky blue pressed into the ground, the
// ground's edge shading its upper side; the card a sunny yellow slab.
let cl_plate = fn(p: vec2, size: vec2, o: vec2, px: float, r: float, col: vec3, seed: 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
var out = cl_drop(p, c, h, r, 1.1, 1.15)
return Finish.over(out, cl_lump(p, c, h, r, min(min(h.x, h.y) * 0.4, 8.0), 0.7, 0.04, col, seed, px, 1.0))
}
let cl_sunk = fn(p: vec2, size: vec2, o: vec2, px: float, r: float, col: vec3, seed: 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 = cl_rbox(p, c, h, r)
// the ground pushed up round the pocket: a soft light lip below, a
// shade above
let up = smoothstep(-h.y * 0.2, h.y * 0.9, p.y - c.y)
let lip = Finish.ring_out(d, 0.0, 2.0, px) * (0.55 * up) * (1.0 - smoothstep(0.0, 2.0, d))
var out = vec4(lip, lip, lip, lip)
let sh = Finish.ring_out(d, 0.0, 1.5, px) * 0.20 * (1.0 - up)
out = Finish.over(out, cl_sh(sh))
return Finish.over(out, cl_pocket(p, c, h, r, col, seed, px))
}
mod.widgets.PanelView.clip_x = false
mod.widgets.PanelView.clip_y = false
mod.widgets.PanelView.draw_bg.vertex = fn() {
let g = cl_grow(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)
let seed = fract(self.rect_size.x * 0.0193 + self.rect_size.y * 0.0711)
if self.sunken > 0.5 {
return cl_sunk(p, self.rect_size, self.rect_pos, px, 10.0, vec3(0.310, 0.816, 0.941), fract(seed + 0.37))
}
return cl_plate(p, self.rect_size, self.rect_pos, px, 11.0, vec3(0.667, 0.643, 1.0), seed)
}
// The card is a yellow slab; its palette and shadow numbers from Rust are
// not read. It lifts a little under the pointer.
mod.widgets.Card.radius = 10.0
mod.widgets.Card.draw_bg.vertex = fn() {
let g = cl_grow(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 col = cl_tone(vec3(0.957, 0.780, 0.384), self.hover, self.down, 0.0)
return cl_plate(p, self.rect_size, self.rect_pos, px, 11.0, col, fract(self.rect_size.x * 0.0173 + 0.5))
}
// The plain rounded view keeps the colour its caller gives it, as a slab of
// that clay with a soft rolled edge.
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 r = max(self.border_radius, 1.0)
let d = cl_rbox(p, c, h, r)
if fill.a > 0.98 && min(h.x, h.y) > 6.0 {
var out = cl_lump(p, c, h - vec2(0.5, 1.0), r, min(min(h.x, h.y) * 0.35, 6.0), 0.4, 0.03, fill.rgb, fract(self.rect_size.x * 0.023 + self.rect_size.y * 0.051), px, 0.8)
if self.border_size > 0.0 {
let bc = self.border_color
let ba = Finish.band(d, 0.0, max(self.border_size, px), px) * bc.a
out = Finish.over(out, vec4(bc.rgb * ba, ba))
}
return out
}
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 slab; a hovered row a pale dent; the
// chosen row a pill of lavender clay with a charcoal clay triangle at its
// left, as a cue in a player.
mod.widgets.ListItemOne.height = 23.0
mod.widgets.ListItem.padding = mod.turtle.Inset{left: 24.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 h = vec2((hi.x - lo.x) * 0.5 - 5.0, (hi.y - lo.y) * 0.5 - 2.0)
let c = vec2(lo.x + h.x + 1.5, (lo.y + hi.y) * 0.5)
let r = h.y
var out = vec4(0.0, 0.0, 0.0, 0.0)
if self.selected > 0.5 {
out = cl_drop(p, c, h, r, 0.9, 0.8)
out = Finish.over(out, cl_lump(p, c, h, r, min(h.y * 0.6, 5.0), 0.3, 0.06, cl_tone(vec3(0.667, 0.639, 0.996), self.hover * live, self.down * live, 0.0), fract(self.rect_size.x * 0.031 + 0.17), px, 0.8))
// the triangle
let tc = vec2(lo.x + 13.0, c.y)
let tg = normalize(vec2(max(p.x - tc.x, -1.0), (p.y - tc.y) * 1.6) + vec2(0.0001, 0.0))
let tsh = Finish.cover(max(cl_tri((p - vec2(0.4, 0.7)).yx, tc.yx, 4.2, 2.8, 1.0), 0.0) - 0.9 - 0.8, px) * 0.28
out = Finish.over(out, cl_sh(tsh))
out = Finish.over(out, cl_skin(cl_tri(p.yx, tc.yx, 4.2, 2.8, 1.0), tg, 1.6, p - tc, vec2(0.0, 0.0), vec3(0.231, 0.208, 0.216), 0.63, px, 0.3, vec2(3.0, 3.0)))
} else {
let d = cl_rbox(p, c, h, r)
let sh = clamp(Finish.inset(p, c, h, r, vec2(0.4, 0.9), 1.1), 0.0, 1.0) * 0.18 + 0.05
let a = Finish.cover(d, px) * (self.hover * 0.8 + self.down) * live
out = cl_sh(sh * a)
}
return out
}
mod.widgets.ListItem.draw_text.color = #2a211c
// The open list of a drop-down: a slab of yellow clay thrown a little
// higher than the parts round it; the row under the pointer is a lavender
// pill, the chosen row carries a charcoal check.
mod.widgets.PopupMenuFlat.clip_x = false
mod.widgets.PopupMenuFlat.clip_y = false
mod.widgets.PopupMenuFlat.draw_bg.vertex = fn() {
let g = cl_grow(self.rect_pos, self.rect_size, 7.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.PopupMenuFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = 7.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 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 out = cl_drop(p, c, h, 8.0, 1.2, 1.8)
return Finish.over(out, cl_lump(p, c, h, 8.0, 5.0, 0.5, 0.03, vec3(0.992, 0.855, 0.420), fract(size.y * 0.019 + 0.3), px, 0.8))
}
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(3.0, 1.5)
let live = 1.0 - self.disabled
var out = cl_drop(p, c, h, h.y, 0.7, 0.6) * self.hover * live
out = Finish.over(out, cl_lump(p, c, h, h.y, min(h.y * 0.6, 5.0), 0.25, 0.05, vec3(0.765, 0.706, 0.882), fract(self.rect_size.x * 0.03 + 0.4), px, 0.6) * self.hover * live)
if self.active > 0.5 {
let ink = vec3(0.200, 0.180, 0.188)
out = Finish.over(out, cl_hook(p, vec2(3.2, c.y + 0.4), vec2(5.4, c.y + 2.6), vec2(9.4, c.y - 2.8), 0.85, ink, 0.31, px))
}
return out
}
// The dock's tabs: the bar is the slab with a pressed groove at its foot;
// the tab in use a yellow clay slab rounded at the top and open at its foot,
// where it joins the panel below; the others lift a little under the
// pointer.
mod.widgets.Tab.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 4.0 bottom: 4.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 + 1.0, px), Finish.snap(o.y + 2.0, px)) - o
let hi = vec2(Finish.snap(o.x + self.rect_size.x - 1.0, px), Finish.snap(o.y + self.rect_size.y, px)) - o + vec2(0.0, 8.0)
let h = (hi - lo) * 0.5
let c = lo + h
let face = cl_lump(p, c, h, 7.0, 4.0, 0.3, 0.03, vec3(0.992, 0.855, 0.420), fract(self.rect_size.x * 0.03), px, 0.7)
let lift = Finish.cover(cl_rbox(p, c, h, 7.0), px) * 0.14 * self.hover * (1.0 - self.active)
return Finish.over(vec4(lift, lift, lift, lift), face * self.active)
}
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
let dk = (1.0 - smoothstep(0.3, 0.9, abs(p.y - (yb - 1.5)))) * 0.28
let lt = step(yb - px, p.y) * step(p.y, yb) * 0.5
var out = cl_sh(dk)
return Finish.over(out, vec4(lt, lt, lt, lt))
}
// ---------------------------------------------------------------------------
// CHIPS AND BADGES. A chip is a small pill of bubblegum pink (a filled one
// takes its role's clay), squashed while chosen; a badge is a small pill of
// its palette's clay with semibold figures.
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 = cl_grow(self.rect_pos, self.rect_size, 5.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 = 5.0
let ch = min(24.0, self.rect_size.y)
let dy = (self.rect_size.y - ch) * 0.5
let sz = vec2(self.rect_size.x, ch)
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let pp = (p - vec2(0.0, dy) + vec2(m, m)) / (sz + vec2(2.0 * m, 2.0 * m))
let light_ink = step(0.6, Finish.lum(self.ink.rgb))
let has_fill = step(0.05, self.color.a) * light_ink
let col = mix(vec3(0.910, 0.537, 0.639), self.color.rgb, has_fill)
return cl_button(pp, sz, self.rect_pos + vec2(0.0, dy), px, self.hover, self.down, self.keyed, 0.0, 0.0, 0.0, vec4(9.0, 9.0, 9.0, 9.0), 0.0, m, col)
}
mod.widgets.ChipFlat.draw_text.color = #1f1915
mod.widgets.BadgePalette.neutral = #8cb3e6
mod.widgets.BadgePalette.on_neutral = #1f1915
mod.widgets.BadgeFlat.draw_text.text_style = mod.theme.font_bold{font_size: 7.5}
mod.widgets.Badge.draw_text.text_style = mod.theme.font_bold{font_size: 7.5}
mod.widgets.BadgeFlat.draw_bg.vertex = fn() {
let g = cl_grow(self.rect_pos, self.rect_size, 4.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.BadgeFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = 4.0
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let o = self.rect_pos
let bh = min(18.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)
let h = (hi - lo) * 0.5
let c = lo + h
let r = min(h.y * 0.8, 7.0)
var out = cl_drop(p, c, h, r, 0.9, 0.8)
out = Finish.over(out, cl_lump(p, c, h, r, min(h.y * 0.6, 5.0), 0.2, 0.08, self.color.rgb, fract(self.rect_size.x * 0.07 + 0.13), px, 0.6))
return out * self.color.a
}
// The spinner keeps its stock turning face (it runs on the pass clock, which
// a face may not read), in charcoal with a visible track ring.
mod.widgets.SpinnerFlat.draw_bg.color = #3b3537
mod.widgets.SpinnerFlat.draw_bg.stroke_width = 3.0
mod.widgets.SpinnerFlat.draw_bg.track_alpha = 0.22
// Rules between groups: a groove pressed into the slab with a stick, its
// upper wall in shade and its lower one lit.
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 = (1.0 - smoothstep(0.2, 0.8, abs(p.y - y + 0.3))) * 0.20
let lt = (1.0 - smoothstep(0.2, 0.7, abs(p.y - y - 0.9))) * 0.45
return Finish.over(cl_sh(dk), vec4(lt, lt, 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 = (1.0 - smoothstep(0.2, 0.8, abs(p.x - x + 0.3))) * 0.20
let lt = (1.0 - smoothstep(0.2, 0.7, abs(p.x - x - 0.9))) * 0.45
return Finish.over(cl_sh(dk), vec4(lt, lt, lt, lt))
}
// The rows of a tree stand on the slab, clear; the open row is a lavender
// pill of clay.
mod.widgets.FileTreeNode.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * self.rect_size
let base = mix(self.color_1, self.color_2, self.is_even)
var res = vec4(base.rgb * base.a, base.a)
let c = self.rect_size * 0.5
let h = vec2(self.rect_size.x * 0.5 - 3.0, self.rect_size.y * 0.5 - 1.5)
let r = h.y
if self.active > 0.5 {
res = Finish.over(res, cl_drop(p, c, h, r, 0.8, 0.6))
res = Finish.over(res, cl_lump(p, c, h, r, min(h.y * 0.6, 5.0), 0.3, 0.05, vec3(0.435, 0.616, 0.973), fract(self.rect_size.x * 0.031 + 0.17), px, 0.7))
}
return res
}
mod.widgets.FileTreeNode.draw_text.color_active = #0b1a44
mod.widgets.FileTreeNode.draw_icon.color_active = #0b1a44
// ---------------------------------------------------------------------------
// PRESSED WORDS. A word on clay is pressed into it: the ink with a light
// line one device pixel under every glyph, where the lower wall of each
// letter's groove catches the light; it costs one more sample.
mod.widgets.ButtonFlat.draw_text.pixel = fn() {
let main = self.sample_text_pixel()
if self.texture_index > 1.5 && self.texture_index < 2.5 {
let dxt = length(dFdx(self.t))
let dyt = length(dFdy(self.t))
let tex_size = self.msdf_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let ps = clamp(self.t.xy - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let c = self.get_color()
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) * 0.30 * c.a
return main + vec4(s, s, s, s) * (1.0 - main.a)
}
return main
}
mod.widgets.DropDownFlat.draw_text.pixel = fn() {
let main = self.sample_text_pixel()
if self.texture_index > 1.5 && self.texture_index < 2.5 {
let dxt = length(dFdx(self.t))
let dyt = length(dFdy(self.t))
let tex_size = self.msdf_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let ps = clamp(self.t.xy - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let c = self.get_color()
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) * 0.30 * c.a
return main + vec4(s, s, s, s) * (1.0 - main.a)
}
return main
}
// ---------------------------------------------------------------------------
// STATES. Every held state is written as a plain value that cuts in at once:
// the library writes some as snap() values, which a state held as a default
// (the states page) never applies. Live, the cut is the same as the snap.
mod.widgets.ButtonFlat.animator.hover.on.from = {all: Snap}
mod.widgets.ButtonFlat.animator.hover.on.apply = {draw_bg: {down: 0.0 hover: 1.0} draw_text: {down: 0.0 hover: 1.0}}
mod.widgets.ButtonFlat.animator.hover.down.from = {all: Snap}
mod.widgets.ButtonFlat.animator.hover.down.apply = {draw_bg: {down: 1.0 hover: 1.0} draw_text: {down: 1.0 hover: 1.0}}
mod.widgets.CheckBoxFlat.animator.hover.on.from = {all: Snap}
mod.widgets.CheckBoxFlat.animator.hover.on.apply = {draw_bg: {down: 0.0 hover: 1.0} draw_text: {down: 0.0 hover: 1.0}}
mod.widgets.CheckBoxFlat.animator.hover.down.from = {all: Snap}
mod.widgets.CheckBoxFlat.animator.hover.down.apply = {draw_bg: {down: 1.0 hover: 1.0} draw_text: {down: 1.0 hover: 1.0}}
mod.widgets.ToggleFlat.animator.hover.on.from = {all: Snap}
mod.widgets.ToggleFlat.animator.hover.on.apply = {draw_bg: {down: 0.0 hover: 1.0} draw_text: {down: 0.0 hover: 1.0}}
mod.widgets.ToggleFlat.animator.hover.down.from = {all: Snap}
mod.widgets.ToggleFlat.animator.hover.down.apply = {draw_bg: {down: 1.0 hover: 1.0} draw_text: {down: 1.0 hover: 1.0}}
mod.widgets.RadioButtonFlat.animator.hover.on.from = {all: Snap}
mod.widgets.RadioButtonFlat.animator.hover.on.apply = {draw_bg: {down: 0.0 hover: 1.0} draw_text: {down: 0.0 hover: 1.0}}
mod.widgets.RadioButtonFlat.animator.hover.down.from = {all: Snap}
mod.widgets.RadioButtonFlat.animator.hover.down.apply = {draw_bg: {down: 1.0 hover: 1.0} draw_text: {down: 1.0 hover: 1.0}}
mod.widgets.TextInputFlat.animator.hover.on.from = {all: Snap}
mod.widgets.TextInputFlat.animator.hover.on.apply = {draw_bg: {down: 0.0 hover: 1.0} draw_text: {down: 0.0 hover: 1.0}}
mod.widgets.TextInputFlat.animator.hover.down.from = {all: Snap}
mod.widgets.TextInputFlat.animator.hover.down.apply = {draw_bg: {down: 1.0 hover: 1.0} draw_text: {down: 1.0 hover: 1.0}}
mod.widgets.DropDownFlat.animator.hover.on.from = {all: Snap}
mod.widgets.DropDownFlat.animator.hover.on.apply = {draw_bg: {down: 0.0 hover: 1.0} draw_text: {down: 0.0 hover: 1.0}}
mod.widgets.DropDownFlat.animator.hover.down.from = {all: Snap}
mod.widgets.DropDownFlat.animator.hover.down.apply = {draw_bg: {down: 1.0 hover: 1.0} draw_text: {down: 1.0 hover: 1.0}}
mod.widgets.SliderFlat.animator.hover.on.from = {all: Snap}
mod.widgets.SliderFlat.animator.hover.on.apply = {draw_bg: {hover: 1.0} draw_text: {hover: 1.0}}
mod.widgets.SliderFlat.animator.drag.on.from = {all: Snap}
mod.widgets.SliderFlat.animator.drag.on.apply = {draw_bg: {drag: 1.0} draw_text: {drag: 1.0}}
// ---------------------------------------------------------------------------
// THE BOARD under everything: the window's own ground, a flat of modelling
// clay smeared on by thumb and rolled thick, lumpy and uneven at the
// window's edge. Its floor is a relief of overlapping dabs and smears in
// every direction: a dab a soft pressed hollow with a rounded rim, a smear
// a soft swell that ends in a crisp little lip where the thumb lifted off,
// some with the fine striations of the fingertip's drag. Each lies over or
// under its neighbours, and where one ends over another the step between
// them is a crisp ledge: lit along its top where it faces the light, a thin
// crease under it where it faces away. The colour is one flat clay; only
// the relief varies it. All of it is laid in the window's points, which do
// not scroll.
// The board's clay: a bright bubblegum pink.
let cl_ground_col = fn() -> vec3 {
return vec3(1.0, 0.671, 0.882)
}
// One patch of the relief, laid from the cell `cell` of a scatter `sc`
// points apart: `kind` 0 a dab, 1 a smear. Its slope is worked out with its
// height, so the relief is laid once a pixel and lit from its exact slope.
// Its weight in the stack (its cover, raised by its place in the stack) is
// `w`; this returns w, w times its height in points, and the slope of that
// product. Its shape: a dab a soft swell, a little hollow in the middle,
// rising to a lip; a smear a longer swell rising along the drag to a crisp
// lip where the thumb lifted off, some with the fingertip's striations; and
// in many of them a fingerprint pressed in.
let cl_patch = fn(p: vec2, cell: vec2, sc: float, kind: float, px: float) -> vec4 {
let s1 = Finish.hash(cell + vec2(kind * 17.3 + 1.7, 3.1))
let s2 = Finish.hash(cell + vec2(5.9, kind * 11.1 + 8.3))
let s3 = Finish.hash(cell + vec2(kind * 7.7 + 2.2, 9.4))
let s4 = Finish.hash(cell + vec2(4.4, kind * 5.3 + 6.6))
let ctr = (cell + vec2(0.15, 0.15) + vec2(s1, s2) * 0.7) * sc
let ang = s3 * 6.2831853
let dir = vec2(cos(ang), sin(ang))
let pv = vec2(-dir.y, dir.x)
let hl = mix(mix(9.0, 15.0, s4), mix(17.0, 23.0, s4), kind)
let hw = mix(mix(7.0, 11.0, fract(s4 * 7.13)), mix(10.0, 14.0, fract(s4 * 7.13)), kind)
let dq = p - ctr
let un = dot(dq, dir) / hl
let vn = dot(dq, pv) / hw
// an outline a thumb leaves: never a true oval
let wob = 1.0 + 0.08 * sin(un * 4.1 + s1 * 6.3) * cos(vn * 3.3 + s2 * 6.3) + 0.05 * sin(vn * 5.7 + s3 * 9.1)
let ln = length(vec2(un, vn))
let e = ln * wob
if e > 1.25 {
return vec4(0.0, 0.0, 0.0, 0.0)
}
let gun = dir / hl
let gvn = pv / hw
let ge = (gun * un + gvn * vn) * (wob / max(ln, 0.0001))
// its edge: crisp at the far end, where the thumb lifted off, soft at
// the near end, where it ran in from the clay already there; never
// sharper than a device pixel and a half
let soft = mix(0.30, 1.7 * px / hw, smoothstep(-0.2, 0.6, un))
let f1t = clamp((e - 1.05) / 0.2, 0.0, 1.0)
let f1 = 1.0 - f1t * f1t * (3.0 - 2.0 * f1t)
let f1d = -6.0 * f1t * (1.0 - f1t) / 0.2
let xs = (e - 1.0) / soft
let f2 = 1.0 / (1.0 + exp(min(xs, 30.0)))
let f2d = -f2 * (1.0 - f2) / soft
let pr = Finish.hash(cell + vec2(kind * 3.3 + 0.7, 0.3))
let kw = exp(3.0 * (pr - 1.0)) * mix(1.0, step(0.6, fract(s3 * 3.71)), kind)
let wt = f1 * f2 * kw
let gw = ge * ((f1d * f2 + f1 * f2d) * kw)
let dep = mix(0.9, 1.4, kind)
let base = (fract(s4 * 13.7) - 0.5) * dep * 0.3
// the swell and its lip
let st = clamp((un + 0.1) / 0.7, 0.0, 1.0)
let ss = st * st * (3.0 - 2.0 * st)
let ssd = 6.0 * st * (1.0 - st) / 0.7
// the lip: never narrower than a device pixel and a half, so it reads
// as one clean ledge and not as a row of dots
let lw = max(0.05, 1.5 * px / hw)
let lg = exp(-(e - 0.92) * (e - 0.92) / (2.0 * lw * lw))
let lip = lg * ss
let lipe = lg * (-(e - 0.92) / (lw * lw)) * ss
let lipu = lg * ssd
let ex4 = exp(-e * e * 4.0)
let hd = 0.25 * (1.0 - e * e) - 0.10 * ex4 + 0.15 * un + 0.45 * lip
let hde = -0.5 * e + 0.8 * e * ex4 + 0.45 * lipe
let hdu = 0.15 + 0.45 * lipu
let hs = 0.30 * (1.0 - e * e) + 0.22 * un + 0.50 * lip
let hse = -0.6 * e + 0.5 * lipe
let hsu = 0.22 + 0.5 * lipu
var h = mix(hd, hs, kind)
var gh = ge * mix(hde, hse, kind) + gun * mix(hdu, hsu, kind)
// the fingertip's striations in about half of the smears
let sfl = step(0.5, fract(s2 * 5.17)) * kind
let sk = 2.0 + 0.7 * s4
let swv = un * 2.3 + s2 * 4.0
let phi = vn * hw * sk + s1 * 6.3 + sin(swv) * 1.5
let gphi = pv * sk + gun * (3.45 * cos(swv))
let sfa = max(1.0 - e, 0.0)
h = h + 0.035 * sin(phi) * sfa * sfl
gh = gh + (gphi * (cos(phi) * sfa) - ge * (sin(phi) * step(e, 1.0))) * (0.035 * sfl)
h = h * dep + base
gh = gh * dep
// a fingerprint pressed into many of the dabs and some of the smears:
// fine ridges in a loop (a hairpin opening one way) or a whorl (rings
// twisted into a spiral), bent a little as skin is, over part of the
// patch, fading out at its edges and often cut off on one side where
// the finger lifted; each ridge lit on the side that faces the light
let fk = Finish.hash(cell + vec2(kind * 9.1 + 4.7, 2.9))
if fk > mix(0.25, 0.55, kind) {
let fr = clamp(min(hl, hw) * mix(0.8, 1.1, fract(fk * 5.3)), 5.5, 12.5)
let fo = (vec2(fract(fk * 13.1), fract(fk * 7.7)) - vec2(0.5, 0.5)) * vec2(hl, hw) * 0.6
let fa = fk * 37.7
let fc = vec2(cos(fa), sin(fa))
let lq = vec2(un * hl, vn * hw) - fo
let fq = vec2(lq.x * fc.x + lq.y * fc.y, lq.y * fc.x - lq.x * fc.y)
let ex = dir * fc.x + pv * fc.y
let ey = pv * fc.x - dir * fc.y
let loopk = step(0.35, fract(fk * 3.3))
let yy = fq.y - max(fq.y, 0.0) * loopk
let yd = 1.0 - loopk * step(0.0, fq.y)
let fd = length(vec2(fq.x * 1.1, yy))
let gfd = (ex * (1.21 * fq.x) + ey * (yy * yd)) / max(fd, 0.0001)
let twist = atan2(fq.y, fq.x) / 6.2831853 * (1.0 - loopk)
let gtw = (ey * fq.x - ex * fq.y) / max(dot(fq, fq), 1.0) / 6.2831853 * (1.0 - loopk)
let aw = 0.23 * fq.x + 0.11 * fq.y + fk * 9.0
let bw = 0.19 * fq.y - 0.07 * fq.x + fk * 4.0
let warp = sin(aw) * 0.7 + sin(bw) * 0.6
let gwp = (ex * 0.23 + ey * 0.11) * (0.7 * cos(aw)) + (ey * 0.19 - ex * 0.07) * (0.6 * cos(bw))
let sp = 0.98 + 0.06 * fract(fk * 11.3)
let fph = fd / sp + twist + warp
let gph = gfd / sp + gtw + gwp
let fpr = vec2(cos(fk * 21.0), sin(fk * 21.0))
let part = smoothstep(-0.25, 0.25, dot(fq / fr, fpr) + mix(0.2, 1.3, fract(fk * 17.9)))
// the ridges stop short of the patch's own edge, and give way where
// they would come closer than the device pixels can hold
let rpx = 1.0 / max(length(gfd / sp + gtw) * px, 0.0001)
let env = (1.0 - smoothstep(fr * 0.45, fr, length(vec2(fq.x * 1.25, fq.y - fr * 0.3 * loopk)))) * part * (1.0 - smoothstep(0.72, 0.92, e)) * smoothstep(2.6, 3.0, rpx)
h = h - 0.065 * (0.5 + 0.5 * cos(fph * 6.2831853)) * env
gh = gh + gph * (0.065 * 3.1415927 * sin(fph * 6.2831853) * env)
}
return vec4(wt, wt * h, gw * h + gh * wt)
}
// The slope of a patch's weight alone, for the blend's own slope.
let cl_patch_dw = fn(p: vec2, cell: vec2, sc: float, kind: float, px: float) -> vec2 {
let s1 = Finish.hash(cell + vec2(kind * 17.3 + 1.7, 3.1))
let s2 = Finish.hash(cell + vec2(5.9, kind * 11.1 + 8.3))
let s3 = Finish.hash(cell + vec2(kind * 7.7 + 2.2, 9.4))
let s4 = Finish.hash(cell + vec2(4.4, kind * 5.3 + 6.6))
let ctr = (cell + vec2(0.15, 0.15) + vec2(s1, s2) * 0.7) * sc
let ang = s3 * 6.2831853
let dir = vec2(cos(ang), sin(ang))
let pv = vec2(-dir.y, dir.x)
let hl = mix(mix(9.0, 15.0, s4), mix(17.0, 23.0, s4), kind)
let hw = mix(mix(7.0, 11.0, fract(s4 * 7.13)), mix(10.0, 14.0, fract(s4 * 7.13)), kind)
let dq = p - ctr
let un = dot(dq, dir) / hl
let vn = dot(dq, pv) / hw
let wob = 1.0 + 0.08 * sin(un * 4.1 + s1 * 6.3) * cos(vn * 3.3 + s2 * 6.3) + 0.05 * sin(vn * 5.7 + s3 * 9.1)
let ln = length(vec2(un, vn))
let e = ln * wob
if e > 1.25 {
return vec2(0.0, 0.0)
}
let ge = (dir * (un / hl) + pv * (vn / hw)) * (wob / max(ln, 0.0001))
let soft = mix(0.30, 1.7 * px / hw, smoothstep(-0.2, 0.6, un))
let f1t = clamp((e - 1.05) / 0.2, 0.0, 1.0)
let f1 = 1.0 - f1t * f1t * (3.0 - 2.0 * f1t)
let f1d = -6.0 * f1t * (1.0 - f1t) / 0.2
let xs = (e - 1.0) / soft
let f2 = 1.0 / (1.0 + exp(min(xs, 30.0)))
let f2d = -f2 * (1.0 - f2) / soft
let pr = Finish.hash(cell + vec2(kind * 3.3 + 0.7, 0.3))
let kw = exp(3.0 * (pr - 1.0)) * mix(1.0, step(0.6, fract(s3 * 3.71)), kind)
return ge * ((f1d * f2 + f1 * f2d) * kw)
}
// The relief at `p`: the dabs on a scatter 18 points apart, the odd longer
// smear on one 48 apart, blended by their place in the stack. Returns its
// height (.x) and its slope (.yz).
let cl_relief = fn(p: vec2, px: float) -> vec3 {
let sa = 18.0
let ca = floor(p / sa)
// the flat clay under every patch, a weight of its own so the gaps
// between patches stay calm
var acc = vec4(0.01, 0.0, 0.0, 0.0)
var gw = vec2(0.0, 0.0)
acc = acc + cl_patch(p, ca + vec2(-1.0, -1.0), sa, 0.0, px)
acc = acc + cl_patch(p, ca + vec2(0.0, -1.0), sa, 0.0, px)
acc = acc + cl_patch(p, ca + vec2(1.0, -1.0), sa, 0.0, px)
acc = acc + cl_patch(p, ca + vec2(-1.0, 0.0), sa, 0.0, px)
acc = acc + cl_patch(p, ca, sa, 0.0, px)
acc = acc + cl_patch(p, ca + vec2(1.0, 0.0), sa, 0.0, px)
acc = acc + cl_patch(p, ca + vec2(-1.0, 1.0), sa, 0.0, px)
acc = acc + cl_patch(p, ca + vec2(0.0, 1.0), sa, 0.0, px)
acc = acc + cl_patch(p, ca + vec2(1.0, 1.0), sa, 0.0, px)
gw = gw + cl_patch_dw(p, ca + vec2(-1.0, -1.0), sa, 0.0, px)
gw = gw + cl_patch_dw(p, ca + vec2(0.0, -1.0), sa, 0.0, px)
gw = gw + cl_patch_dw(p, ca + vec2(1.0, -1.0), sa, 0.0, px)
gw = gw + cl_patch_dw(p, ca + vec2(-1.0, 0.0), sa, 0.0, px)
gw = gw + cl_patch_dw(p, ca, sa, 0.0, px)
gw = gw + cl_patch_dw(p, ca + vec2(1.0, 0.0), sa, 0.0, px)
gw = gw + cl_patch_dw(p, ca + vec2(-1.0, 1.0), sa, 0.0, px)
gw = gw + cl_patch_dw(p, ca + vec2(0.0, 1.0), sa, 0.0, px)
gw = gw + cl_patch_dw(p, ca + vec2(1.0, 1.0), sa, 0.0, px)
let sb = 48.0
let cb = floor(p / sb - vec2(0.5, 0.5))
acc = acc + cl_patch(p, cb, sb, 1.0, px)
acc = acc + cl_patch(p, cb + vec2(1.0, 0.0), sb, 1.0, px)
acc = acc + cl_patch(p, cb + vec2(0.0, 1.0), sb, 1.0, px)
acc = acc + cl_patch(p, cb + vec2(1.0, 1.0), sb, 1.0, px)
gw = gw + cl_patch_dw(p, cb, sb, 1.0, px)
gw = gw + cl_patch_dw(p, cb + vec2(1.0, 0.0), sb, 1.0, px)
gw = gw + cl_patch_dw(p, cb + vec2(0.0, 1.0), sb, 1.0, px)
gw = gw + cl_patch_dw(p, cb + vec2(1.0, 1.0), sb, 1.0, px)
let hh = acc.y / acc.x
return vec3(hh, (vec2(acc.z, acc.w) - gw * hh) / acc.x)
}
mod.widgets.Window.show_bg = true
mod.widgets.Window.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * self.rect_size
let size = self.rect_size
let c = size * 0.5
let h = size * 0.5 - vec2(4.0, 4.0)
let r = 16.0
let gcol = cl_ground_col()
// the table under the board
var board = gcol * 0.62 * (1.0 + (Finish.noise(p * vec2(0.6, 0.05)) - 0.5) * 0.10)
// the board's edge: thick, lumpy and uneven, as a slab pressed out by
// hand
let ew = (Finish.noise(p * 0.03 + vec2(1.3, 8.1)) - 0.5) * 3.2 + (Finish.noise(p * 0.09 + vec2(5.7, 2.2)) - 0.5) * 1.1
let d = cl_rbox(p, c, h, r) + ew
let cast = Finish.drop(p, c, h, r, vec2(0.8, 1.6), 2.2) * 0.45
board = board * (1.0 - cast)
// the rolled rim
let w = 10.0
let u = clamp((w + d) / w, 0.0, 1.0)
let z = sqrt(max(1.0 - u * u, 0.02))
let g = cl_grad(p, c, h, max(r, w))
// the relief and its slope, laid two and a quarter times as large as its
// patches are written (its heights grow with it, so its slopes are the
// same; its prints' ridges are written finer, so they lie 2 points apart)
let rl = cl_relief(p / 2.25, px / 2.25)
let h0 = rl.x
var tilt = vec2(-rl.y, -rl.z)
// a steep step is a ledge, not a cliff: the slope is eased toward a
// ceiling so no single pixel goes darker than the crease beside it
let tl = length(tilt)
tilt = tilt / (1.0 + tl / 1.1)
// near the edge the clay was pushed up by thumbs: the rim's lumps
let rim = smoothstep(-22.0, -5.0, d)
let rb = cl_bump(p * 0.45, 0.73) * (1.6 * rim)
// the board's form (its rolled rim) in the soft light; the relief over
// it, gentle: its dabs, lips, striations and prints shade softly
let nf = normalize(vec3(g.x * u + rb.x * z, g.y * u + rb.y * z, z))
let n = normalize(vec3(g.x * u + (tilt.x * 0.5 + rb.x) * z, g.y * u + (tilt.y * 0.5 + rb.y) * z, z))
let lk = vec3(-0.30, -0.57, 0.77)
// a crease in the deepest corners where two dabs meet
// (a bright clay with a channel near full cannot lighten in it, so the
// relief is laid a quarter stronger to read the same)
let dd = clamp((dot(n, lk) - dot(nf, lk)) * 0.65, -0.3, 0.3) - 0.075 * smoothstep(0.25, 0.9, -h0)
var col = cl_detail(cl_light(nf, gcol), dd)
// a soft satin sheen
let sp = pow(max(dot(n, vec3(-0.159, -0.303, 0.940)), 0.0), 6.0) * 0.035
col = col + mix(vec3(1.0, 1.0, 1.0), gcol, 0.5) * sp
col = col + g3(Finish.grain(p / px, 0.005))
let a = Finish.cover(d, px)
return vec4(mix(board, col, a), 1.0)
}
// THE SPLITTER between the panes: a faint groove pressed down the slab with
// a stick; under the pointer a short lavender snake to take hold of.
mod.widgets.Splitter.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * self.rect_size
let v = self.is_vertical
let across = mix(p.y, p.x, v)
let along = mix(p.x, p.y, v)
let thick = mix(self.rect_size.y, self.rect_size.x, v)
let span = mix(self.rect_size.x, self.rect_size.y, v)
let m = thick * 0.5
let fade = smoothstep(0.0, 24.0, along) * (1.0 - smoothstep(span - 24.0, span, along))
let gd = (1.0 - smoothstep(0.2, 0.7, abs(across - m + 0.3))) * 0.13 * fade
let gl = (1.0 - smoothstep(0.2, 0.6, abs(across - m - 0.8))) * 0.32 * fade
var res = Finish.over(cl_sh(gd), vec4(gl, gl, gl, gl))
let act = max(max(self.hover, self.drag), self.focus)
if act > 0.001 {
let a = mix(vec2(span * 0.5 - 16.0, m), vec2(m, span * 0.5 - 16.0), v)
let b = mix(vec2(span * 0.5 + 16.0, m), vec2(m, span * 0.5 + 16.0), v)
res = Finish.over(res, cl_snake(p, a, b, 2.2, vec3(0.745, 0.671, 0.886), 0.19, px, 0.4) * act)
}
return res
}
// ---------------------------------------------------------------- switches
// THE SWITCHES are the sheet's own toggle: the rocker and the slide switch
// take the toggle's face, its size and shape, its quad and its label gap.
mod.widgets.ToggleRocker.label_walk.margin = mod.turtle.Inset{left: 48.0 right: 3.0 top: 0.0 bottom: 0.0}
mod.widgets.ToggleSlide.label_walk.margin = mod.turtle.Inset{left: 48.0 right: 3.0 top: 0.0 bottom: 0.0}
mod.widgets.ToggleRocker.draw_bg.size = 14.0
mod.widgets.ToggleSlide.draw_bg.size = 14.0
mod.widgets.ToggleRocker.min_height = 24.0
mod.widgets.ToggleSlide.min_height = 24.0
mod.widgets.ToggleRocker.draw_bg.pill_aspect = 2.7143
mod.widgets.ToggleSlide.draw_bg.pill_aspect = 2.7143
mod.widgets.ToggleRocker.draw_bg.knob_inset = 7.0
mod.widgets.ToggleSlide.draw_bg.knob_inset = 7.0
mod.widgets.ToggleRocker.draw_bg.vertex = mod.widgets.ToggleFlat.draw_bg.vertex
mod.widgets.ToggleSlide.draw_bg.vertex = mod.widgets.ToggleFlat.draw_bg.vertex
mod.widgets.ToggleRocker.draw_bg.pixel = mod.widgets.ToggleFlat.draw_bg.pixel
mod.widgets.ToggleSlide.draw_bg.pixel = mod.widgets.ToggleFlat.draw_bg.pixel
// ---------------------------------------------------------------- lamps
// LAMPS: an LED, a glossy bead of coloured glass pressed into the clay. 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 the socket the bead was pressed
// into: 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, gone in about
// two radii; on a pale ground its hue is also laid round it as a tint that
// keeps the ground's luminance. A bar is the same light drawn out into a
// short light pipe.
let cl_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)
}
let cl_led_lamp_full = fn(ink: vec3) -> vec3 {
return ink / max(max(ink.x, max(ink.y, ink.z)), 0.01)
}
let cl_led_lamp_hue = fn(ink: vec3, vivid: float) -> vec3 {
let f = cl_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`, `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. Premultiplied over `dst`.
let cl_led_lamp = fn(dst: vec4, p: vec2, c: vec2, hb: vec2, lit: float, ink: vec3, vivid: float, px: float, ground: vec3, room: float) -> vec4 {
let rt = max(min(hb.x, hb.y), 0.4)
let d = Material.sd_box(p, c, hb, rt)
let bw = clamp(0.32 * rt, 0.8, 2.4)
let ds = d - bw
let L = clamp(lit, 0.0, 1.0)
let b = cl_led_lamp_full(ink)
let hue = cl_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)
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)
// the clay pushed up round the socket: a soft rolled lip
let lipd = Material.sd_box(p, c, hb + vec2(bw + 1.3, bw + 1.3), rt + bw + 1.3)
let lipa = Finish.cover(lipd, px) * (1.0 - Finish.cover(ds, px))
let lu = clamp(-lipd / 1.3, 0.0, 1.0)
let lfac = dot(nrm, vec2(-0.47, -0.88)) * (1.0 - lu)
let lipc = ground * (1.0 + 0.16 * lfac)
res = Finish.over(res, vec4(lipc * lipa, lipa))
if L > 0.001 && pale > 0.001 {
let h = clamp(0.42 * pw * pow(2.0, -x / max(0.4 * reach, 0.5)) * win, 0.0, 0.45) * pale
let est = res.rgb + ground * (1.0 - res.a)
let w = clamp(dot(hue, lum) / max(dot(est, lum), 0.001), 0.0, 1.0)
res = Finish.over(res, vec4(hue * h * w, h * w))
}
// the socket: its upper wall in shadow, a catch light on its lower rim
let a_s = Finish.cover(ds, px)
let under = ground
let gy = clamp(nrm.y * t, -1.0, 1.0)
var sc = under * mix(0.50, 0.90, smoothstep(-0.9, 0.9, gy))
sc = mix(sc, min(under * 1.06 + vec3(0.25, 0.25, 0.25), white), Finish.band(ds, 0.0, max(px, 0.45), px) * smoothstep(0.2, 0.95, nrm.y) * 0.85)
res = Finish.over(res, vec4(min(sc, white) * a_s, a_s))
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 = cl_led_lamp_add(res, mix(hue, white, 0.15 * pw) * halo * pw * hwin * win, ground)
}
// THE BEAD: a glossy dome of coloured glass.
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, its colour barely there, a catch light along
// its lower rim.
let offc = mix(vec3(0.085, 0.08, 0.09), b * 0.4, 0.14)
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
var col = mix(off, min(on, white), smoothstep(0.0, 0.35, L))
let e = length(vec2(q.x / 0.62, (q.y + 0.46) / 0.30))
let spec = (1.0 - smoothstep(0.55, 1.0, e)) * mix(1.0, 0.3, clamp((q.y + 0.76) / 0.6, 0.0, 1.0))
col = mix(col, white, spec * mix(0.8, 0.55, L) * smoothstep(0.9, 2.2, rt))
col = 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 bead above, pressed into the cream
// slab. The halo's room is held in the lamp's own box. The bar is a short
// pipe at the left of its cell, so its light stays clear of the switches
// beside the column.
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 tb = min(t, 6.0)
let lb = tb * 1.9
let c = mix(self.rect_size * 0.5, vec2(3.5 + lb * 0.5, self.rect_size.y * 0.5), self.bar)
let hb = mix(vec2(t * 0.5, t * 0.5), vec2(lb * 0.5, tb * 0.5), self.bar)
let glow = mix(max(room - 0.5, 2.0), 2.2, self.bar)
let res = cl_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, cl_ground_col(), glow)
return res * self.opacity
}
true