makepad/widgets/sheets/themes/kids-toy/widgets.splash

3659 lines
193 KiB
Text

use mod.prelude.widgets_internal.*
// Kids toy. Every part is injection-moulded plastic on a royal-blue board:
// a key is a thick rounded moulding whose side wall shows under its top
// face, its shoulder deepening the colour where the plastic is thickest, a
// soft window reflection over its upper half and a crisp catch light along
// its top edge, and it throws a short soft shadow down onto the board.
// Every face is drawn in true points (one device pixel is `px`); shadows,
// walls and rings that fall outside a layout box are drawn in a quad grown
// in `vertex`, and no padding is spent on relief.
// ---------------------------------------------------------------- helpers
// One device pixel in points.
let kt_px = fn(dpi: float) -> float {
return 1.0 / max(dpi, 0.5)
}
// An edge rounded to a whole device pixel in window space, handed back in
// box space.
let kt_snap = fn(v: float, o: float, px: float) -> float {
return Finish.snap(o + v, px) - o
}
// Distance from p to the segment a..b, in points.
let kt_seg = fn(p: vec2, a: vec2, b: vec2) -> float {
let pa = p - a
let ba = b - a
let t = clamp(dot(pa, ba) / max(dot(ba, ba), 0.0001), 0.0, 1.0)
return length(pa - ba * t)
}
// The toy box: one colour per kind of part. 0 red, 1 green, 2 yellow,
// 3 purple, 4 orange, 5 light blue, 6 magenta, 7 teal, 8 cyan, 9 white.
let kt_col = fn(k: float) -> vec3 {
if k < 0.5 { return vec3(0.878, 0.204, 0.165) }
if k < 1.5 { return vec3(0.278, 0.706, 0.263) }
if k < 2.5 { return vec3(0.976, 0.812, 0.106) }
if k < 3.5 { return vec3(0.580, 0.310, 0.780) }
if k < 4.5 { return vec3(0.957, 0.537, 0.110) }
if k < 5.5 { return vec3(0.200, 0.557, 0.937) }
if k < 6.5 { return vec3(0.820, 0.243, 0.773) }
if k < 7.5 { return vec3(0.090, 0.639, 0.627) }
if k < 8.5 { return vec3(0.278, 0.776, 0.941) }
return vec3(0.957, 0.949, 0.925)
}
// The colour deep in the plastic, where it is thickest: darker and more
// saturated, as light that has travelled through a coloured moulding is.
let kt_deep = fn(c: vec3) -> vec3 {
return c * c * 0.9 + c * 0.08
}
// A part that cannot be used: its colour washed toward a grey board blue.
let kt_dull = fn(c: vec3, disabled: float) -> vec3 {
let l = Finish.lum(c)
let g = mix(c, vec3(l, l, l), 0.55 * disabled)
return mix(g, vec3(0.42, 0.47, 0.64), 0.38 * disabled)
}
// The navy shadow every part throws on the board (unpremultiplied ink).
let kt_shade = fn() -> vec3 {
return vec3(0.0, 0.03, 0.11)
}
// Signed grain, -1..1, one value per device pixel of window space `w`.
// Each pixel is first sent to a far cell picked by the hash of its own, so
// the hash's near-repeat every 97 pixels never lines up into a lattice.
let kt_grain = fn(w: vec2, px: float) -> float {
let i = floor(w / px)
let j = i + vec2(Finish.hash(i) * 419.0, Finish.hash(i + vec2(5.31, 9.17)) * 263.0)
return Finish.hash(j) - Finish.hash(j + vec2(17.13, 3.71))
}
// Which way the outline of a rounded box faces at `p`: a straight side's
// normal on the sides, an arc's on the corners.
let kt_box_n = fn(p: vec2, c: vec2, h: vec2, r: float) -> vec2 {
let s = vec2(step(0.0, p.x - c.x) * 2.0 - 1.0, step(0.0, p.y - c.y) * 2.0 - 1.0)
let rr = clamp(r, 0.0, min(h.x, h.y))
let q = abs(p - c) - (h - vec2(rr, rr))
let qc = max(q, vec2(0.0, 0.0))
var n = vec2(0.0, s.y)
if q.x > q.y {
n = vec2(s.x, 0.0)
}
if q.x > 0.0 && q.y > 0.0 {
n = qc / max(length(qc), 0.0001) * s
}
return n
}
// The board's moulded pebble grain: a height 0..1 at a point of window space
// in points. Three octaves of smooth noise about 1.3, 0.6 and 5 points
// across, the middle one turned, so no cell repeats and nothing lines up.
let kt_pebble = fn(q: vec2) -> float {
let a = Finish.noise(q * 0.78)
let b = Finish.noise(vec2(q.x * 1.55 + q.y * 0.42, q.y * 1.55 - q.x * 0.42) + vec2(17.3, 5.9))
let c = Finish.noise(q * 0.19 + vec2(3.1, 11.7))
return a * 0.5 + b * 0.3 + c * 0.2
}
// The board at window point `w` of a window `size` points big: royal blue,
// a broad soft light over the upper middle as a lamp over a play table
// gives it, the far corners and the foot a step darker, and the pebble
// grain catching that light from the upper left.
let kt_board = fn(w: vec2, size: vec2, px: float) -> vec3 {
let u = w / max(size, vec2(1.0, 1.0))
let dx = (u.x - 0.46) / 0.8
let dy = (u.y - 0.30) / 0.9
let lite = exp(-(dx * dx + dy * dy) * 1.5)
var col = mix(vec3(0.106, 0.318, 0.643), vec3(0.165, 0.420, 0.765), lite)
col = col * (1.0 - 0.08 * smoothstep(0.65, 1.0, u.y))
let g = kt_pebble(w) - kt_pebble(w + vec2(-0.3, -0.42))
col = col * (1.0 + 0.3 * g)
return col + vec3(1.0, 1.0, 1.0) * kt_grain(w, px) * 0.003
}
// The board's wear: scratches in the plastic. Each cell `g` of a lattice
// `s` points wide holds at most one scratch (with chance `prob`): a thin
// line through a random point of its cell at a random angle, `lmin` to
// `lmax` long (never over two cells, so the cells round a point find every
// scratch that reaches it), barely bowed, tapering to nothing at both ends
// and catching the light unevenly along its length. Most are faint
// hairlines, a few brighter; with chance `gouge` one is cut deeper, a
// dark core with a pale lip either side. Returned: how much it lifts the
// plastic toward white in .x, how much it darkens it in .y.
let kt_scratch = fn(w: vec2, g: vec2, s: float, lmin: float, lmax: float, prob: float, gouge: float, seed: float, px: float) -> vec2 {
let k = g + vec2(seed * 0.37, seed * 0.91)
if Finish.hash(k * 1.31 + vec2(3.7, 9.1)) > prob {
return vec2(0.0, 0.0)
}
let h1 = Finish.hash(k + vec2(17.13, 3.31))
let h2 = Finish.hash(k + vec2(5.71, 41.93))
let h3 = Finish.hash(k + vec2(29.37, 13.11))
let h4 = Finish.hash(k + vec2(7.97, 61.73))
let h5 = Finish.hash(k + vec2(83.19, 27.47))
let h6 = Finish.hash(k + vec2(47.53, 71.29))
let h7 = Finish.hash(k + vec2(61.07, 89.41))
let c = (g + vec2(h1, h2)) * s
let ang = h3 * PI
let dir = vec2(cos(ang), sin(ang))
let nrm = vec2(0.0 - dir.y, dir.x)
let len = mix(lmin, lmax, h4 * h4)
let q = w - (c - dir * len * 0.5)
let along = dot(q, dir)
let t = clamp(along / len, 0.0, 1.0)
let bow = (h5 - 0.5) * len * 0.05
let d = length(vec2(along - t * len, dot(q, nrm) - bow * 4.0 * t * (1.0 - t)))
let taper = smoothstep(0.0, 0.22, t) * (1.0 - smoothstep(0.7, 1.0, t))
let strength = 0.4 + 0.3 * h6 * h6 * h6
let hw = px * (0.22 + 0.4 * h6) * taper
// Along its length a scratch catches the light unevenly: it breaks up.
let broken = 0.45 + 0.55 * Finish.noise(vec2(along * 0.16, h1 * 37.0))
let line = Finish.cover(d - hw, px) * taper * strength * broken
let deep = step(1.0 - gouge, h7)
let core = Finish.cover(d - px * 0.45 * taper, px) * taper
let lip = clamp(Finish.cover(d - px * 1.6 * taper, px) * taper - core, 0.0, 1.0)
return mix(vec2(line, 0.0), vec2(lip * 0.5, core * 0.55), deep)
}
// Every scratch reaching window point `w`, at three scales: a few long ones
// crossing a large stretch of the board, more of middle length, and short
// nicks that gather into scuffed patches where a low noise says the board
// was handled most. The cells round `w` are searched; the strongest wins.
let kt_scratches = fn(w: vec2, px: float) -> vec2 {
var res = vec2(0.0, 0.0)
var i = 0.0
loop {
if i > 8.5 { break }
let o = vec2(i - 3.0 * floor(i / 3.0) - 1.0, floor(i / 3.0) - 1.0)
let g1 = floor(w / 230.0) + o
res = max(res, kt_scratch(w, g1, 230.0, 120.0, 440.0, 0.62, 0.14, 1.0, px))
let g2 = floor(w / 62.0) + o
res = max(res, kt_scratch(w, g2, 62.0, 14.0, 115.0, 0.5, 0.03, 2.0, px))
let g3 = floor(w / 22.0) + o
let patch = smoothstep(0.55, 0.85, Finish.noise((g3 + vec2(0.5, 0.5)) * 22.0 * 0.0055 + vec2(11.3, 4.7)))
res = max(res, kt_scratch(w, g3, 22.0, 3.0, 30.0, 0.1 + 0.5 * patch, 0.0, 3.0, px) * vec2(0.85, 0.0))
i = i + 1.0
}
return res
}
// ---------------------------------------------------------------- materials
// A moulded plastic key standing on the board, premultiplied. `lo`..`hi` is
// its footprint; the top face stands on a side wall that shows along the
// bottom, about a seventh of the key's height (pressed, the face sinks over
// three quarters of it and the shadow under it all but goes). `lift` 1
// throws the full shadow, 0 seats the key on the board with the contact
// shadow only.
let kt_block = fn(p: vec2, lo: vec2, hi: vec2, r: float, px: float, base: vec3, hover: float, down: float, disabled: float, focus: float, lift: float) -> vec4 {
let c = (lo + hi) * 0.5
let h = (hi - lo) * 0.5
let hh = hi.y - lo.y
let sz = min(min(hh, hi.x - lo.x), 40.0)
let live = 1.0 - disabled
let dn = clamp(down, 0.0, 1.0) * live
let wall_full = clamp(0.14 * sz, 1.5, 4.5)
let wall = wall_full * (1.0 - 0.75 * dn) * (1.0 - 0.45 * disabled)
let d = Material.sd_box(p, c, h, r)
// The shadow: a soft one falling down and a little right, and a tight
// contact shadow along the foot.
let reach = max(0.085 * sz, 1.0)
let s1 = Finish.drop(p, c, h - vec2(0.05 * sz, 0.0), r, vec2(0.025 * sz, reach), reach * 0.85) * 0.45 * lift * (1.0 - 0.8 * dn)
let s2 = Finish.drop(p, c, h, r, vec2(0.0, px + 0.02 * sz), px + 0.025 * sz) * 0.55
let sh = (1.0 - (1.0 - s1) * (1.0 - s2)) * (1.0 - 0.6 * disabled)
var res = vec4(kt_shade() * sh, sh)
var b = kt_dull(base, disabled)
b = b + (vec3(1.0, 1.0, 1.0) - b) * 0.08 * hover * live
b = b * (1.0 - 0.07 * dn)
let deep = kt_deep(b)
// The top face, lifted by the wall.
let flo = vec2(lo.x, lo.y + wall_full - wall)
let fhi = vec2(hi.x, hi.y - wall)
let fc = (flo + fhi) * 0.5
let fh = max((fhi - flo) * 0.5, vec2(0.5, 0.5))
let df = Material.sd_box(p, fc, fh, r)
let n = kt_box_n(p, fc, fh, r)
// How far over the rounded shoulder: 0 on the flat, 1 at the edge.
let bw = clamp(0.2 * sz, 1.6, 5.0)
let s = clamp(1.0 + df / bw, 0.0, 1.0)
let s2c = s * s
let facing = dot(n, vec2(-0.38, -0.92))
let t = clamp((p.y - flo.y) / max(fhi.y - flo.y, 1.0), 0.0, 1.0)
var face = b * (1.05 - 0.09 * t)
face = mix(face, deep, 0.38 * s2c)
face = face * (1.0 + 0.30 * facing * s2c)
let uv = clamp((p - flo) / max(fhi - flo, vec2(1.0, 1.0)), vec2(0.0, 0.0), vec2(1.0, 1.0))
let gloss = (1.0 - 0.8 * disabled) * (1.0 - 0.5 * dn)
// The window seen in the gloss: a soft bright band over the upper half,
// its lower edge curving up toward the ends.
face = mix(face, vec3(1.0, 1.0, 1.0), Finish.sheen(uv, 0.04, 0.52, 0.35) * (1.0 - s * 0.7) * 0.32 * gloss)
// Light that has come through the plastic gathers above the lower
// shoulder, a breath brighter and richer.
face = face + b * 0.12 * smoothstep(0.55, 0.95, t) * (1.0 - s) * (1.0 - disabled)
let catchl = smoothstep(0.3, 0.95, facing) * smoothstep(0.2, 0.7, s) * (1.0 - smoothstep(0.85, 1.0, s))
face = mix(face, vec3(1.0, 1.0, 1.0), catchl * 0.6 * gloss)
// The wall: the colour seen through the thickness, darkening to the foot.
let tw = clamp((p.y - fhi.y + r * 0.5) / max(hi.y - fhi.y + r * 0.5, 1.0), 0.0, 1.0)
let wallc = mix(b * 0.72, deep * 0.5, tw)
var col = mix(wallc, face, Finish.cover(df, px))
col = mix(col, deep * 0.55, Finish.band(df, 0.0, px, px) * smoothstep(0.2, 0.9, n.y) * 0.6)
// The moulding seam round the whole key, darkest under it.
col = mix(col, deep * 0.32, Finish.band(d, 0.0, px, px) * mix(0.3, 0.8, step(c.y, p.y)))
let a = Finish.cover(d, px)
res = Finish.over(res, vec4(col * a, a))
// Focus: a toy-yellow ring standing just off the key.
let ring = Finish.ring_out(d, px, px * 2.5, px) + Finish.glow(d - px * 2.5, 1.0) * 0.4 * (1.0 - a)
return Finish.over(res, vec4(1.0, 0.86, 0.2, 1.0) * clamp(ring, 0.0, 1.0) * focus * live)
}
// A glossy plastic ball laid over `dst`, premultiplied: its soft shadow on
// the board, a lit sphere whose colour deepens toward its rim, light coming
// back through the plastic low down, a broad soft reflection and a crisp
// white highlight up and to the left.
let kt_ball = fn(dst: vec4, p: vec2, c: vec2, rad: float, px: float, base: vec3, hover: float, held: float, disabled: float, lift: float) -> vec4 {
let live = 1.0 - disabled
var b = kt_dull(base, disabled)
b = b + (vec3(1.0, 1.0, 1.0) - b) * 0.09 * hover * live
b = b * (1.0 - 0.06 * held * live)
let so = c + vec2(0.16 * rad, 0.38 * rad) * lift
let sd = length(p - so) - rad * 0.84
let shs = max(0.36 * rad, px)
let sh = (1.0 - smoothstep(-shs, shs * 1.5, sd)) * 0.42 * lift
let cd = length(p - (c + vec2(0.0, 0.1 * rad))) - rad
let ct = (1.0 - smoothstep(-0.12 * rad, 0.22 * rad + px, cd)) * 0.35
let shadow = (1.0 - (1.0 - sh) * (1.0 - ct)) * (1.0 - 0.5 * disabled)
var res = Finish.over(dst, vec4(kt_shade() * shadow, shadow))
let q = (p - c) / max(rad, 0.5)
let r2 = dot(q, q)
let z = sqrt(max(1.0 - r2, 0.0))
let n = vec3(q.x, q.y, z)
let l = normalize(vec3(-0.45, -0.68, 0.58))
let dif = max(dot(n, l), 0.0)
let deep = kt_deep(b)
var col = mix(deep * 0.8, b * 1.1, dif)
let rimt = smoothstep(0.5, 1.0, sqrt(r2))
col = mix(col, deep * 0.9, rimt * 0.3)
col = col + b * 0.3 * smoothstep(0.25, 0.95, q.y) * rimt
let hv = normalize(l + vec3(0.0, 0.0, 1.0))
let nh = max(dot(n, hv), 0.0)
let gloss = 1.0 - 0.8 * disabled
col = col + vec3(1.0, 1.0, 1.0) * (pow(nh, 9.0) * 0.16 + pow(nh, 70.0) * 0.95) * gloss
col = min(col, vec3(1.0, 1.0, 1.0))
let d = length(p - c) - rad
col = mix(col, deep * 0.45, Finish.band(d, 0.0, px, px) * 0.45)
let a = Finish.cover(d, px)
return Finish.over(res, vec4(col * a, a))
}
// A channel moulded into the board, premultiplied: its floor `fl`, the
// board's shadow falling in along the upper wall, and a catch light on the
// board just under its lower lip.
let kt_groove = fn(p: vec2, lo: vec2, hi: vec2, r: float, px: float, fl: vec3) -> vec4 {
let c = (lo + hi) * 0.5
let h = (hi - lo) * 0.5
let d = Material.sd_box(p, c, h, r)
let hh = hi.y - lo.y
let t = clamp((p.y - lo.y) / max(hh, 1.0), 0.0, 1.0)
var col = fl * (0.84 + 0.26 * t)
let ins = Finish.inset(p, c, h, r, vec2(0.0, clamp(0.35 * hh, 0.6, 2.5)), clamp(0.3 * hh, px, 2.0))
col = col * (1.0 - 0.55 * ins)
col = mix(col, fl * 0.5, Finish.band(d, 0.0, px, px) * 0.6)
let a = Finish.cover(d, px)
let lip = Finish.ring_out(d, 0.0, px * 1.5, px) * smoothstep(c.y - h.y * 0.2, c.y + h.y * 0.7, p.y) * 0.5
return Finish.over(vec4(vec3(0.62, 0.77, 1.0) * lip, lip), vec4(col * a, a))
}
// A rod of coloured plastic lying in a channel, laid over `dst`: round
// across, lit from above, with a highlight line along its upper third.
let kt_rod = fn(dst: vec4, p: vec2, lo: vec2, hi: vec2, px: float, base: vec3, disabled: float) -> vec4 {
let c = (lo + hi) * 0.5
let h = max((hi - lo) * 0.5, vec2(0.25, 0.25))
let d = Material.sd_box(p, c, h, min(h.x, h.y))
let b = kt_dull(base, disabled)
let deep = kt_deep(b)
let v = clamp((p.y - c.y) / max(h.y, 0.25), -1.0, 1.0)
let z = sqrt(max(1.0 - v * v, 0.0))
var col = mix(deep * 0.8, b * 1.06, clamp(0.25 + 0.6 * z - 0.3 * v, 0.0, 1.0))
col = col + vec3(1.0, 1.0, 1.0) * exp(-((v + 0.45) * (v + 0.45)) / 0.06) * 0.38 * (1.0 - 0.8 * disabled)
col = mix(col, deep * 0.5, Finish.band(d, 0.0, px, px) * 0.5)
let a = Finish.cover(d, px)
return Finish.over(dst, vec4(min(col, vec3(1.0, 1.0, 1.0)) * a, a))
}
// A thick moulded frame of coloured plastic round a window, premultiplied:
// the frame `bw` wide and round across like a tube, lit from above; the
// window's floor `fill` (at `fill_a`) with the frame's shadow falling in
// under its top edge, and a dark seam where the window starts.
let kt_frame = fn(p: vec2, lo: vec2, hi: vec2, r: float, px: float, bw: float, rim: vec3, fill: vec3, fill_a: float, hover: float, focus: float, disabled: float) -> vec4 {
let c = (lo + hi) * 0.5
let h = (hi - lo) * 0.5
let d = Material.sd_box(p, c, h, r)
let ih = max(h - vec2(bw, bw), vec2(0.5, 0.5))
let ir = max(r - bw, 0.5)
let di = Material.sd_box(p, c, ih, ir)
let live = 1.0 - disabled
var rc = kt_dull(rim, disabled)
rc = rc + (vec3(1.0, 1.0, 1.0) - rc) * (0.08 * hover + 0.14 * focus) * live
let deep = kt_deep(rc)
let insh = Finish.inset(p, c, ih, ir, vec2(0.0, 1.4), 1.1)
let win = fill * (1.0 - 0.5 * insh)
let n = kt_box_n(p, c, h, r)
let across = clamp(di / max(bw, 0.5), 0.0, 1.0) * 2.0 - 1.0
let facing = dot(n * across, vec2(-0.38, -0.92))
var fr = mix(deep * 0.85, rc * 1.08, clamp(0.55 + 0.5 * facing, 0.0, 1.0))
let crown = exp(-((across + 0.15) * (across + 0.15)) / 0.1) * smoothstep(0.1, 0.9, -n.y)
fr = mix(fr, vec3(1.0, 1.0, 1.0), crown * 0.42 * (1.0 - 0.7 * disabled))
let inframe = Finish.cover(-di, px)
var col = mix(win, fr, inframe)
col = mix(col, deep * 0.35, Finish.band(di, 0.0, px, px) * 0.8)
col = mix(col, deep * 0.35, Finish.band(d, 0.0, px, px) * 0.6)
let a = Finish.cover(d, px) * mix(fill_a, 1.0, inframe)
return vec4(col * a, a)
}
// A part pressed into the board: a navy well with the board's shadow falling
// in from its top edge and walls, and light catching its lower wall.
let kt_sunk = fn(p: vec2, lo: vec2, hi: vec2, r: float, px: float, hover: float) -> vec4 {
let c = (lo + hi) * 0.5
let h = (hi - lo) * 0.5
let d = Material.sd_box(p, c, h, r)
let t = clamp((p.y - lo.y) / max(hi.y - lo.y, 1.0), 0.0, 1.0)
var col = mix(vec3(0.075, 0.180, 0.440), vec3(0.110, 0.255, 0.560), t) + vec3(0.03, 0.03, 0.04) * hover
let ins = Finish.inset(p, c, h, r, vec2(0.0, 2.0), 1.6)
col = col * (1.0 - 0.6 * ins)
col = mix(col, vec3(0.36, 0.56, 0.92), Finish.band(d, 0.0, px * 1.5, px) * smoothstep(c.y + h.y * 0.3, hi.y, p.y) * 0.6)
col = mix(col, vec3(0.03, 0.08, 0.22), Finish.band(d, 0.0, px, px) * (1.0 - smoothstep(c.y, hi.y, p.y)) * 0.7)
let a = Finish.cover(d, px)
return vec4(col * a, a)
}
// Clear plastic, premultiplied: what is under it shows lightened and tinted
// by `tint` at `density`; a broad reflection over its upper half, a bright
// refraction rim just inside the edge, light gathering at its foot, and a
// faint coloured shadow under it.
let kt_jelly = fn(p: vec2, lo: vec2, hi: vec2, r: float, px: float, tint: vec3, density: float, disabled: float) -> vec4 {
let c = (lo + hi) * 0.5
let h = (hi - lo) * 0.5
let d = Material.sd_box(p, c, h, r)
let t = clamp((p.y - lo.y) / max(hi.y - lo.y, 1.0), 0.0, 1.0)
let tn = kt_dull(tint, disabled)
let sh = Finish.drop(p, c, h, r, vec2(0.0, 1.2), 1.4) * 0.35 * (1.0 - Finish.cover(d, px))
var res = vec4(mix(kt_shade(), kt_deep(tn) * 0.5, density) * sh, sh)
let clear = vec3(0.74, 0.82, 0.95)
var col = mix(clear, tn, density)
col = col * (0.92 + 0.12 * t)
col = col + (vec3(1.0, 1.0, 1.0) - col) * smoothstep(0.55, 1.0, t) * 0.18
let uv = clamp((p - lo) / max(hi - lo, vec2(1.0, 1.0)), vec2(0.0, 0.0), vec2(1.0, 1.0))
col = mix(col, vec3(1.0, 1.0, 1.0), Finish.sheen(uv, 0.06, 0.5, 0.12) * 0.5 * (1.0 - 0.7 * disabled))
col = mix(col, vec3(1.0, 1.0, 1.0), Finish.band(d, px, px * 2.5, px) * 0.35)
col = mix(col, kt_deep(mix(vec3(0.2, 0.35, 0.65), tn, density)) * 0.6, Finish.band(d, 0.0, px, px) * 0.7)
let a = Finish.cover(d, px) * mix(0.72, 0.95, density)
return Finish.over(res, vec4(col * a, a))
}
// The focus ring: a toy-yellow line standing a device pixel off the edge
// of a shape at distance `d`, with a short breath of light outside it.
let kt_focus = fn(d: float, px: float) -> float {
let ring = Finish.ring_out(d, px, px * 2.5, px)
return clamp(ring + Finish.glow(d - px * 2.5, 1.0) * 0.4 * (1.0 - Finish.cover(d - px * 2.5, px)), 0.0, 1.0)
}
// The three-quarter arc a waiting button turns in place of its label.
let kt_spinner = fn(dst: vec4, p: vec2, c: vec2, rad: float, t: float, amt: float, stroke: float) -> vec4 {
let sdf = Sdf2d.viewport(p)
let a0 = t * 2.0 * PI
sdf.arc_round_caps(c.x, c.y, rad, a0, a0 + 1.5 * PI, stroke)
sdf.fill(vec4(1.0, 1.0, 1.0, amt))
return Finish.over(dst, sdf.result)
}
// ---------------------------------------------------------------- buttons
// Every button is a thick plastic key, each kind in its own colour: the
// plain button red, the primary green, the secondary yellow, the tertiary
// purple, the danger magenta, the flat one orange, the icon key teal; the
// outline is a cyan ring with the board showing through. The key's wall
// takes the bottom of its box, so the label is set two points up from the
// box's middle, onto the face.
mod.widgets.ButtonFlat.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 2.0 bottom: 4.0}
mod.widgets.ButtonFlat.min_height = 24.0
mod.widgets.ButtonFlat.icon_walk.width = 14.0
mod.widgets.ButtonIcon.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 2.0 bottom: 4.0}
mod.widgets.ButtonFlatIcon.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 2.0 bottom: 4.0}
mod.widgets.ButtonFlatterIcon.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 2.0 bottom: 4.0}
mod.widgets.ButtonPrimary.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 2.0 bottom: 4.0}
mod.widgets.ButtonSecondary.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 2.0 bottom: 4.0}
mod.widgets.ButtonTertiary.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 2.0 bottom: 4.0}
mod.widgets.ButtonDanger.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 2.0 bottom: 4.0}
mod.widgets.ButtonOutline.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 3.0 bottom: 3.0}
mod.widgets.ButtonXs.min_height = 0.0
mod.widgets.ButtonSm.min_height = 0.0
mod.widgets.ButtonSm.padding = mod.turtle.Inset{left: 8.0 right: 8.0 top: 0.0 bottom: 2.0}
mod.widgets.ButtonLg.padding = mod.turtle.Inset{left: 14.0 right: 14.0 top: 0.0 bottom: 3.0}
mod.widgets.ButtonXs.draw_text.text_style.font_size = 6.5
mod.widgets.ButtonFlat.draw_bg.border_radius = uniform(2.5)
mod.widgets.ButtonFlat.draw_bg.border_size = uniform(0.67)
mod.widgets.ButtonFlat.clip_x = false
mod.widgets.ButtonFlat.clip_y = false
mod.widgets.ButtonFlat.draw_bg.vertex = fn() {
let m = 8.0
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
// The key's colour for a button `kind`: 0 flat (orange), 1 plain (red),
// 2 primary (green), 3 secondary (yellow), 4 tertiary (purple), 5 danger
// (magenta), 8 icon (teal).
let kt_key_col = fn(kind: float) -> vec3 {
if kind < 0.5 { return kt_col(4.0) }
if kind < 1.5 { return kt_col(0.0) }
if kind < 2.5 { return kt_col(1.0) }
if kind < 3.5 { return kt_col(2.0) }
if kind < 4.5 { return kt_col(3.0) }
if kind < 5.5 { return kt_col(6.0) }
return kt_col(7.0)
}
// The key and its states, shared by every button template; 6 is the
// outline ring and 7 the flatter button, a pale key only under the hand.
let kt_button = fn(pos: vec2, size: vec2, o: vec2, dpi: float, hover: float, down: float, disabled: float, focus: float, loading: float, anim: float, kind: float, dash: float) -> vec4 {
let m = 8.0
let px = kt_px(dpi)
let p = pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let lo = vec2(kt_snap(0.0, o.x, px), kt_snap(0.0, o.y, px))
let hi = vec2(kt_snap(size.x, o.x, px), kt_snap(size.y, o.y, px))
let hh = hi.y - lo.y
let r = clamp(0.24 * hh, 3.0, 7.0)
var res = vec4(0.0, 0.0, 0.0, 0.0)
if kind > 5.5 && kind < 7.5 {
if kind > 6.5 {
if hover + down + focus > 0.001 {
res = kt_block(p, lo, hi, r, px, kt_col(9.0) * 0.92, hover, down, disabled, focus, 0.6) * clamp(max(max(hover, down), focus), 0.0, 1.0)
}
} else {
// The outline: a cyan ring moulding standing on the board, the
// board showing through it a shade darker; under the hand the
// window fills a little, pressed the ring sinks.
let c = (lo + hi) * 0.5
let h = (hi - lo) * 0.5
let dn = down * (1.0 - disabled)
let sh = Finish.drop(p, c, h, r, vec2(0.4, 1.6 * (1.0 - 0.8 * dn) + px), 1.3) * 0.45 * (1.0 - 0.5 * disabled)
res = vec4(kt_shade() * sh, sh)
var ring = kt_frame(p, lo, hi, r, px, 2.5, kt_col(8.0), vec3(0.10, 0.36, 0.62), 0.16 + 0.16 * hover + 0.25 * dn, hover, 0.0, disabled)
if dash > 0.0 {
let dd = Material.sd_box(p, c, h - vec2(1.25, 1.25), max(r - 1.25, 0.5))
let on = step(0.5, fract((p.x + p.y) / (dash * 2.0)))
ring = mix(ring * Finish.cover(-dd + 1.25, px), ring, on)
}
res = Finish.over(res, ring)
let d = Material.sd_box(p, c, h, r)
let fr = Finish.ring_out(d, px, px * 2.5, px) + Finish.glow(d - px * 2.5, 1.0) * 0.4 * (1.0 - Finish.cover(d, px))
res = Finish.over(res, vec4(1.0, 0.86, 0.2, 1.0) * clamp(fr, 0.0, 1.0) * focus * (1.0 - disabled))
}
} else {
res = kt_block(p, lo, hi, r, px, kt_key_col(kind), hover, down, disabled, focus, 1.0)
}
if loading > 0.001 {
let rad = max(min(hi.x - lo.x, hh) * 0.5 - 6.0, 2.0)
res = kt_spinner(res, p, (lo + hi) * 0.5 - vec2(0.0, 1.5), rad, anim, loading, 1.5)
}
return res
}
mod.widgets.ButtonFlat.draw_bg.pixel = fn() {
return self.host_over(kt_button(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.down, self.disabled, self.focus, self.loading, self.anim_time, 0.0, 0.0), 8.0)
}
mod.widgets.Button.draw_bg.pixel = fn() {
return self.host_over(kt_button(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.down, self.disabled, self.focus, self.loading, self.anim_time, 1.0, 0.0), 8.0)
}
mod.widgets.ButtonSm.draw_bg.pixel = fn() {
return self.host_over(kt_button(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.down, self.disabled, self.focus, self.loading, self.anim_time, 1.0, 0.0), 8.0)
}
mod.widgets.ButtonLg.draw_bg.pixel = fn() {
return self.host_over(kt_button(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.down, self.disabled, self.focus, self.loading, self.anim_time, 1.0, 0.0), 8.0)
}
mod.widgets.ButtonXs.draw_bg.pixel = fn() {
return self.host_over(kt_button(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.down, self.disabled, self.focus, self.loading, self.anim_time, 1.0, 0.0), 8.0)
}
mod.widgets.ButtonXl.draw_bg.pixel = fn() {
return self.host_over(kt_button(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.down, self.disabled, self.focus, self.loading, self.anim_time, 1.0, 0.0), 8.0)
}
mod.widgets.ButtonIcon.draw_bg.pixel = fn() {
return self.host_over(kt_button(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.down, self.disabled, self.focus, self.loading, self.anim_time, 8.0, 0.0), 8.0)
}
mod.widgets.ButtonPrimary.draw_bg.pixel = fn() {
return self.host_over(kt_button(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.down, self.disabled, self.focus, self.loading, self.anim_time, 2.0, 0.0), 8.0)
}
mod.widgets.ButtonSecondary.draw_bg.pixel = fn() {
return self.host_over(kt_button(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.down, self.disabled, self.focus, self.loading, self.anim_time, 3.0, 0.0), 8.0)
}
mod.widgets.ButtonTertiary.draw_bg.pixel = fn() {
return self.host_over(kt_button(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.down, self.disabled, self.focus, self.loading, self.anim_time, 4.0, 0.0), 8.0)
}
mod.widgets.ButtonDanger.draw_bg.pixel = fn() {
return self.host_over(kt_button(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.down, self.disabled, self.focus, self.loading, self.anim_time, 5.0, 0.0), 8.0)
}
mod.widgets.ButtonOutline.draw_bg.pixel = fn() {
return self.host_over(kt_button(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.down, self.disabled, self.focus, self.loading, self.anim_time, 6.0, self.border_dash), 8.0)
}
mod.widgets.ButtonFlatter.draw_bg.pixel = fn() {
return self.host_over(kt_button(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.down, self.disabled, self.focus, self.loading, self.anim_time, 7.0, 0.0), 8.0)
}
// Label ink per key: white a breath toward the key's colour, dark olive on
// the yellow key, the outline's cyan; disabled, a muted tone of the key.
mod.widgets.ButtonFlat.draw_text.color = #fff7ee
mod.widgets.ButtonFlat.draw_text.color_hover = #ffffff
mod.widgets.ButtonFlat.draw_text.color_down = #fff1e2
mod.widgets.ButtonFlat.draw_text.color_focus = #fff7ee
mod.widgets.ButtonFlat.draw_text.color_disabled = #c9b8a8
mod.widgets.ButtonFlat.draw_icon.color = #fff7ee
mod.widgets.Button.draw_text.color = #fff3f0
mod.widgets.Button.draw_text.color_hover = #ffffff
mod.widgets.Button.draw_text.color_down = #ffe9e4
mod.widgets.Button.draw_text.color_focus = #fff3f0
mod.widgets.Button.draw_text.color_disabled = #c8a7b4
mod.widgets.ButtonSm.draw_text.color = #fff3f0
mod.widgets.ButtonSm.draw_text.color_hover = #ffffff
mod.widgets.ButtonSm.draw_text.color_down = #ffe9e4
mod.widgets.ButtonSm.draw_text.color_focus = #fff3f0
mod.widgets.ButtonSm.draw_text.color_disabled = #c8a7b4
mod.widgets.ButtonLg.draw_text.color = #fff3f0
mod.widgets.ButtonLg.draw_text.color_hover = #ffffff
mod.widgets.ButtonLg.draw_text.color_down = #ffe9e4
mod.widgets.ButtonLg.draw_text.color_focus = #fff3f0
mod.widgets.ButtonLg.draw_text.color_disabled = #c8a7b4
mod.widgets.ButtonPrimary.draw_text.color = #f3fff0
mod.widgets.ButtonPrimary.draw_text.color_hover = #ffffff
mod.widgets.ButtonPrimary.draw_text.color_down = #e6f8e2
mod.widgets.ButtonPrimary.draw_text.color_focus = #f3fff0
mod.widgets.ButtonPrimary.draw_text.color_disabled = #a9c2b4
mod.widgets.ButtonPrimary.draw_icon.color = #f3fff0
mod.widgets.ButtonSecondary.draw_text.color = #3d2f00
mod.widgets.ButtonSecondary.draw_text.color_hover = #2e2300
mod.widgets.ButtonSecondary.draw_text.color_down = #3d2f00
mod.widgets.ButtonSecondary.draw_text.color_focus = #3d2f00
mod.widgets.ButtonSecondary.draw_text.color_disabled = #5c5a52
mod.widgets.ButtonSecondary.draw_icon.color = #3d2f00
mod.widgets.ButtonTertiary.draw_text.color = #fbf3ff
mod.widgets.ButtonTertiary.draw_text.color_hover = #ffffff
mod.widgets.ButtonTertiary.draw_text.color_down = #f1e4fa
mod.widgets.ButtonTertiary.draw_text.color_focus = #fbf3ff
mod.widgets.ButtonTertiary.draw_text.color_disabled = #b7aac8
mod.widgets.ButtonTertiary.draw_icon.color = #fbf3ff
mod.widgets.ButtonDanger.draw_text.color = #fff2fd
mod.widgets.ButtonDanger.draw_text.color_hover = #ffffff
mod.widgets.ButtonDanger.draw_text.color_down = #fbe2f8
mod.widgets.ButtonDanger.draw_text.color_focus = #fff2fd
mod.widgets.ButtonDanger.draw_text.color_disabled = #c4a8c8
mod.widgets.ButtonDanger.draw_icon.color = #fff2fd
mod.widgets.ButtonOutline.draw_text.color = #8fe0ff
mod.widgets.ButtonOutline.draw_text.color_hover = #b5ebff
mod.widgets.ButtonOutline.draw_text.color_down = #8fe0ff
mod.widgets.ButtonOutline.draw_text.color_focus = #b5ebff
mod.widgets.ButtonOutline.draw_text.color_disabled = #8fa6cf
mod.widgets.ButtonOutline.draw_icon.color = #8fe0ff
mod.widgets.ButtonFlat.draw_icon_end.color = #ffffff
// The labels on the keys are set in the medium weight.
mod.widgets.ButtonFlat.draw_text.text_style = mod.theme.font_label{font_size: 8.5}
mod.widgets.Button.draw_text.text_style = mod.theme.font_label{font_size: 8.5}
mod.widgets.ButtonPrimary.draw_text.text_style = mod.theme.font_label{font_size: 8.5}
mod.widgets.ButtonSecondary.draw_text.text_style = mod.theme.font_label{font_size: 8.5}
mod.widgets.ButtonTertiary.draw_text.text_style = mod.theme.font_label{font_size: 8.5}
mod.widgets.ButtonDanger.draw_text.text_style = mod.theme.font_label{font_size: 8.5}
mod.widgets.ButtonOutline.draw_text.text_style = mod.theme.font_label{font_size: 8.5}
mod.widgets.ButtonFlatter.draw_text.text_style = mod.theme.font_label{font_size: 8.5}
mod.widgets.ButtonSm.draw_text.text_style = mod.theme.font_label{font_size: 7.75}
mod.widgets.ButtonLg.draw_text.text_style = mod.theme.font_label{font_size: 9.5}
// ---------------------------------------------------------------- selection
// The quad of a small control grows by this much so its shadow and focus
// ring can fall outside the box.
mod.widgets.CheckBoxFlat.draw_bg.vertex = fn() {
let m = 6.0
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
mod.widgets.CheckBoxFlat.clip_x = false
mod.widgets.CheckBoxFlat.clip_y = false
mod.widgets.CheckBoxFlat.padding = mod.turtle.Inset{left: 8.0 right: 8.0 top: 4.0 bottom: 4.0}
mod.widgets.CheckBoxFlat.draw_bg.size = uniform(15.0)
mod.widgets.CheckBoxFlat.draw_bg.border_radius = uniform(2.0)
mod.widgets.CheckBoxFlat.label_walk.margin.left = 15.0
// The check box: a square socket moulded into the board. Checked, a chunky
// white tick stands in it; mixed, a red plug fills it with a white dash.
mod.widgets.CheckBoxFlat.draw_bg.pixel = fn() {
let m = 6.0
let px = kt_px(self.draw_pass.dpi_factor)
let o = self.rect_pos
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let sz = self.size
let x0 = (self.rect_size.x - sz) * self.mark_at_end
let y0 = self.rect_size.y * 0.5 - sz * 0.5
let lo = vec2(kt_snap(x0, o.x, px), kt_snap(y0, o.y, px))
let hi = vec2(kt_snap(x0 + sz, o.x, px), kt_snap(y0 + sz, o.y, px))
let c = (lo + hi) * 0.5
let r = 3.5
let live = 1.0 - self.disabled
var fl = mix(mix(vec3(0.075, 0.200, 0.490), vec3(0.11, 0.26, 0.57), self.hover * live), vec3(0.36, 0.42, 0.56), self.disabled)
fl = fl * (1.0 - 0.3 * self.down * live)
var res = kt_groove(p, lo, hi, r, px, fl)
// Under the hand the socket's rim lights.
let d = Material.sd_box(p, c, (hi - lo) * 0.5, r)
res = Finish.over(res, vec4(0.75, 0.86, 1.0, 1.0) * Finish.ring_out(d, 0.0, px * 1.5, px) * 0.5 * self.hover * live)
// Mixed: a red plug pressed into the socket, a white dash on it.
let plug = kt_block(p, lo + vec2(1.5, 1.5), hi - vec2(1.5, 1.0), 2.2, px, kt_col(0.0), 0.0, max(self.down, 0.0), self.disabled, 0.0, 0.0)
res = mix(res, Finish.over(res, plug), self.mixed)
let cy = c.y - 0.6
let dd = kt_seg(p, vec2(c.x - 3.2, cy), vec2(c.x + 3.2, cy)) - 1.05
res = Finish.over(res, vec4(1.0, 1.0, 1.0, 1.0) * Finish.cover(dd, px) * self.mixed * (1.0 - 0.4 * self.disabled))
// Checked: the tick, white plastic with its own small shadow.
let on = self.active * (1.0 - self.mixed)
let cm = c + vec2(0.0, 0.2)
let dm = min(kt_seg(p, vec2(cm.x - 4.0, cm.y), vec2(cm.x - 1.2, cm.y + 2.9)), kt_seg(p, vec2(cm.x - 1.2, cm.y + 2.9), vec2(cm.x + 4.2, cm.y - 3.3))) - 1.1
let ds = min(kt_seg(p - vec2(0.3, 0.9), vec2(cm.x - 4.0, cm.y), vec2(cm.x - 1.2, cm.y + 2.9)), kt_seg(p - vec2(0.3, 0.9), vec2(cm.x - 1.2, cm.y + 2.9), vec2(cm.x + 4.2, cm.y - 3.3))) - 1.1
res = Finish.over(res, vec4(0.0, 0.02, 0.08, 1.0) * Finish.cover(ds, px * 2.0) * 0.5 * on)
let tk = mix(vec3(1.0, 1.0, 1.0), vec3(0.72, 0.78, 0.88), self.disabled)
res = Finish.over(res, vec4(tk, 1.0) * Finish.cover(dm, px) * on)
// The error: the rim turns red.
res = Finish.over(res, vec4(0.95, 0.25, 0.2, 1.0) * Finish.ring_out(d, 0.0, px * 2.0, px) * self.error)
let fr = kt_focus(d - px * 0.5, px)
return Finish.over(res, vec4(1.0, 0.86, 0.2, 1.0) * clamp(fr, 0.0, 1.0) * self.focus * live)
}
// The toggle: a pill of clear plastic with a clear plastic block for a
// thumb. On, the pill is red jelly lit from inside and the block stands at
// its right end; off, the board shows through the clear plastic.
mod.widgets.ToggleFlat.draw_bg.size = uniform(18.0)
mod.widgets.ToggleFlat.draw_bg.pill_aspect = uniform(1.89)
mod.widgets.ToggleFlat.label_walk.margin.left = 34.0
let kt_toggle = fn(p: vec2, lo: vec2, hi: vec2, o: vec2, px: float, kt: float, on: float, hover: float, down: float, disabled: float, focus: float, tint: vec3) -> vec4 {
let th = hi.y - lo.y
let tw = hi.x - lo.x
let r = th * 0.5
var res = kt_jelly(p, lo, hi, r, px, tint, 0.85 * on, disabled)
// The thumb: a clear block the track's height less two points, rounded,
// riding from end to end.
let ks = th - 3.0
let kx = kt_snap(lo.x + 1.5 + (tw - 3.0 - ks) * kt, o.x, px)
let klo = vec2(kx, lo.y + 1.5)
let khi = vec2(kx + ks, lo.y + 1.5 + ks)
let kc = (klo + khi) * 0.5
let kh = (khi - klo) * 0.5
let kd = Material.sd_box(p, kc, kh, ks * 0.32)
let ksh = Finish.drop(p, kc, kh, ks * 0.32, vec2(0.5, 1.4 * (1.0 - 0.6 * down)), 1.2) * 0.5 * (1.0 - Finish.cover(kd, px))
res = Finish.over(res, vec4(kt_shade() * ksh, ksh))
let thumb = kt_jelly(p, klo, khi, ks * 0.32, px, vec3(0.92, 0.95, 1.0), 0.55 + 0.1 * hover, disabled)
res = Finish.over(res, thumb)
// A glint on the thumb's upper left corner.
let gq = p - (klo + vec2(ks * 0.3, ks * 0.26))
let glint = exp(-dot(gq, gq) / max(0.06 * ks * ks, 0.2)) * 0.7 * (1.0 - 0.7 * disabled)
res = Finish.over(res, vec4(1.0, 1.0, 1.0, 1.0) * glint * Finish.cover(kd, px))
let d = Material.sd_box(p, (lo + hi) * 0.5, (hi - lo) * 0.5, r)
let fr = Finish.ring_out(d, px, px * 2.5, px) + Finish.glow(d - px * 2.5, 1.0) * 0.35 * (1.0 - Finish.cover(d, px))
return Finish.over(res, vec4(1.0, 0.86, 0.2, 1.0) * clamp(fr, 0.0, 1.0) * focus * (1.0 - disabled))
}
mod.widgets.ToggleFlat.draw_bg.pixel = fn() {
let m = 5.0
let px = kt_px(self.draw_pass.dpi_factor)
let o = self.rect_pos
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let tw = self.size * self.pill_aspect
let th = self.size
let x0 = (self.rect_size.x - tw) * self.mark_at_end
let y0 = self.rect_size.y * 0.5 - th * 0.5
let lo = vec2(kt_snap(x0, o.x, px), kt_snap(y0, o.y, px))
let hi = vec2(kt_snap(x0 + tw, o.x, px), kt_snap(y0 + th, o.y, px))
let kt = mix(self.active, self.drag_pos, self.drag)
return kt_toggle(p, lo, hi, o, px, kt, self.active, self.hover, max(self.pressed, self.down), self.disabled, self.focus, kt_col(0.0))
}
// The radio: a ball of plastic sitting in a round socket. Not chosen it is
// white; chosen it is the toy red, pressed a little deeper into the socket.
mod.widgets.RadioButtonFlat.draw_bg.vertex = fn() {
let m = 6.0
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
mod.widgets.RadioButtonFlat.clip_x = false
mod.widgets.RadioButtonFlat.clip_y = false
mod.widgets.RadioButtonFlat.draw_bg.size = uniform(15.0)
mod.widgets.RadioButtonFlat.padding = mod.turtle.Inset{left: 8.0 right: 8.0 top: 4.0 bottom: 4.0}
mod.widgets.RadioButtonFlat.label_walk.margin.left = 15.0
mod.widgets.RadioButtonFlat.draw_bg.pixel = fn() {
let m = 6.0
let px = kt_px(self.draw_pass.dpi_factor)
let o = self.rect_pos
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let sz = self.size
let y0 = self.rect_size.y * 0.5 - sz * 0.5
let lo = vec2(kt_snap(0.0, o.x, px), kt_snap(y0, o.y, px))
let hi = vec2(kt_snap(sz, o.x, px), kt_snap(y0 + sz, o.y, px))
let c = (lo + hi) * 0.5
let on = self.active
let live = 1.0 - self.disabled
var res = kt_groove(p, lo, hi, sz * 0.5, px, mix(vec3(0.075, 0.2, 0.49), vec3(0.36, 0.42, 0.56), self.disabled))
let rad = sz * 0.5 - 1.6
let col = mix(kt_col(9.0), kt_col(0.0), on)
let pushed = max(self.down * live, on)
res = kt_ball(res, p, c - vec2(0.0, 0.6 * (1.0 - pushed)), rad - 0.4 * self.down * live, px, col, self.hover, pushed, self.disabled, 0.55 - 0.4 * pushed)
let d = length(p - c) - sz * 0.5
let fr = kt_focus(d - px * 0.5, px)
return Finish.over(res, vec4(1.0, 0.86, 0.2, 1.0) * clamp(fr, 0.0, 1.0) * self.focus * live)
}
// The radio tab: an orange key; chosen, it is pressed into the board, a
// navy well with its word lit.
mod.widgets.RadioButtonTabFlat.draw_bg.vertex = fn() {
let m = 8.0
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
mod.widgets.RadioButtonTabFlat.clip_x = false
mod.widgets.RadioButtonTabFlat.clip_y = false
mod.widgets.RadioButtonTabFlat.padding = mod.turtle.Inset{left: 0.0 right: 8.0 top: 3.0 bottom: 5.0}
mod.widgets.RadioButtonTabFlat.min_height = 24.0
mod.widgets.RadioButtonTabFlat.draw_bg.pixel = fn() {
let m = 8.0
let px = kt_px(self.draw_pass.dpi_factor)
let o = self.rect_pos
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let lo = vec2(kt_snap(0.0, o.x, px), kt_snap(0.0, o.y, px))
let hi = vec2(kt_snap(self.rect_size.x, o.x, px), kt_snap(self.rect_size.y, o.y, px))
let r = clamp(0.24 * (hi.y - lo.y), 3.0, 6.0)
let key = kt_block(p, lo, hi, r, px, kt_col(4.0), self.hover, self.down, self.disabled, self.focus, 1.0)
let well = kt_sunk(p, lo, hi, r, px, self.hover)
return mix(key, well, self.active)
}
mod.widgets.RadioButtonTabFlat.draw_text.color = #fff7ee
mod.widgets.RadioButtonTabFlat.draw_text.color_hover = #ffffff
mod.widgets.RadioButtonTabFlat.draw_text.color_active = #9fd6ff
mod.widgets.RadioButtonTabFlat.draw_text.color_disabled = #c9b8a8
// ---------------------------------------------------------------- sliders
// A slider is a channel moulded into the board with a coloured plastic rod
// lying in it up to the value, and a glossy ball riding it; past the value
// the channel's floor is the slider's colour at its deepest. Each kind has
// its colour: the slider green, the minimal one light blue, the round one
// orange, the band purple. Its value sits in a small navy window framed in
// the same colour.
let kt_slider = fn(dst: vec4, p: vec2, lo: vec2, hi: vec2, f0: float, f1: float, hx: vec2, px: float, rad: float, base: vec3, hover: float, held: float, disabled: float) -> vec4 {
let th = hi.y - lo.y
let fl = mix(kt_deep(kt_dull(base, disabled)) * 0.75, vec3(0.07, 0.19, 0.46), 0.5)
var res = Finish.over(dst, kt_groove(p, lo, hi, th * 0.5, px, fl))
let ins = 1.0
if f1 - f0 > 0.01 {
res = kt_rod(res, p, vec2(max(f0, lo.x + ins), lo.y + ins), vec2(min(f1, hi.x - ins), hi.y - ins), px, base, disabled)
}
if rad > 0.0 {
res = kt_ball(res, p, hx, rad, px, base, hover, held, disabled, 1.0)
}
return res
}
// The value window of a slider kind: a navy window in a thick frame of the
// slider's colour; focused, the frame lightens.
let kt_valbox = fn(pos: vec2, size: vec2, o: vec2, dpi: float, rim: vec3, hover: float, focus: float, disabled: float) -> vec4 {
let px = kt_px(dpi)
let p = pos * size
if size.x < 1.0 || size.y < 1.0 {
return vec4(0.0, 0.0, 0.0, 0.0)
}
let lo = vec2(kt_snap(0.0, o.x, px), kt_snap(0.0, o.y, px))
let hi = vec2(kt_snap(size.x - px * 0.49, o.x, px), kt_snap(size.y - px * 0.49, o.y, px))
return kt_frame(p, lo, hi, 5.0, px, 2.5, rim, vec3(0.07, 0.18, 0.44), 1.0, hover, focus, disabled)
}
mod.widgets.SliderFlat.clip_x = false
mod.widgets.SliderFlat.clip_y = false
mod.widgets.SliderFlat.draw_bg.vertex = fn() {
let m = 12.0
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
mod.widgets.SliderFlat.draw_bg.pixel = fn() {
let m = 12.0
let px = kt_px(self.draw_pass.dpi_factor)
let o = self.rect_pos
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let rad = 9.5
let cy = kt_snap(20.0 + (self.rect_size.y - 20.0) * 0.5, o.y, px)
let lo = vec2(kt_snap(0.0, o.x, px), cy - 3.5)
let hi = vec2(kt_snap(self.rect_size.x, o.x, px), cy + 3.5)
let span = self.rect_size.x - rad * 2.0
let hx = rad + self.slide_pos * span
let ox = rad + self.origin_pos * span
let f0 = mix(lo.x, min(ox, hx), self.arc_origin)
let f1 = mix(hx, max(ox, hx), self.arc_origin)
var res = kt_slider(vec4(0.0, 0.0, 0.0, 0.0), p, lo, hi, f0, f1, vec2(hx, cy - 0.5), px, rad, kt_col(1.0), self.hover, self.drag, self.disabled)
let d = length(p - vec2(hx, cy - 0.5)) - rad
let fr = kt_focus(d, px)
return Finish.over(res, vec4(1.0, 0.86, 0.2, 1.0) * clamp(fr, 0.0, 1.0) * self.focus * (1.0 - self.disabled))
}
// The value field rides in the label row: padded into a small window.
mod.widgets.SliderMinimal.text_input.min_height = 0.0
mod.widgets.SliderMinimal.text_input.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 2.0 bottom: 2.0}
mod.widgets.SliderFlat.text_input.min_height = 0.0
mod.widgets.SliderFlat.text_input.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 2.0 bottom: 2.0}
mod.widgets.Slider.text_input.min_height = 0.0
mod.widgets.Slider.text_input.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 2.0 bottom: 2.0}
mod.widgets.SliderRoundFlat.text_input.min_height = 0.0
mod.widgets.SliderRoundFlat.text_input.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 2.0 bottom: 2.0}
mod.widgets.SliderRound.text_input.min_height = 0.0
mod.widgets.SliderRound.text_input.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 2.0 bottom: 2.0}
mod.widgets.RotaryFlat.text_input.min_height = 0.0
mod.widgets.RotaryFlat.text_input.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 2.0 bottom: 2.0}
mod.widgets.Rotary.text_input.min_height = 0.0
mod.widgets.Rotary.text_input.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 2.0 bottom: 2.0}
mod.widgets.RotaryGradientY.text_input.min_height = 0.0
mod.widgets.RotaryGradientY.text_input.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 2.0 bottom: 2.0}
mod.widgets.SliderFlat.text_input.draw_bg.pixel = fn() {
return kt_valbox(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, kt_col(1.0), self.hover, self.focus, self.disabled)
}
mod.widgets.Slider.text_input.draw_bg.pixel = fn() {
return kt_valbox(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, kt_col(1.0), self.hover, self.focus, self.disabled)
}
mod.widgets.SliderMinimal.text_input.draw_bg.pixel = fn() {
return kt_valbox(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, kt_col(5.0), self.hover, self.focus, self.disabled)
}
mod.widgets.SliderRoundFlat.text_input.draw_bg.pixel = fn() {
return kt_valbox(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, kt_col(4.0), self.hover, self.focus, self.disabled)
}
mod.widgets.SliderRound.text_input.draw_bg.pixel = fn() {
return kt_valbox(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, kt_col(4.0), self.hover, self.focus, self.disabled)
}
mod.widgets.RotaryFlat.text_input.draw_bg.pixel = fn() {
return kt_valbox(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, kt_col(1.0), self.hover, self.focus, self.disabled)
}
mod.widgets.Rotary.text_input.draw_bg.pixel = fn() {
return kt_valbox(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, kt_col(1.0), self.hover, self.focus, self.disabled)
}
mod.widgets.RotaryGradientY.text_input.draw_bg.pixel = fn() {
return kt_valbox(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, kt_col(1.0), self.hover, self.focus, self.disabled)
}
// The minimal slider: a slimmer channel, the light-blue rod and a smaller
// ball.
mod.widgets.SliderMinimal.clip_x = false
mod.widgets.SliderMinimal.clip_y = false
mod.widgets.SliderMinimal.draw_bg.vertex = fn() {
let m = 12.0
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
mod.widgets.SliderMinimal.draw_bg.pixel = fn() {
let m = 12.0
let px = kt_px(self.draw_pass.dpi_factor)
let o = self.rect_pos
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let rad = 8.0
let cy = kt_snap(self.offset_y + (self.rect_size.y - self.offset_y) * 0.5, o.y, px)
let lo = vec2(kt_snap(0.0, o.x, px), cy - 2.5)
let hi = vec2(kt_snap(self.rect_size.x, o.x, px), cy + 2.5)
let span = self.rect_size.x - rad * 2.0
let hx = rad + self.slide_pos * span
let ox = rad + self.origin_pos * span
let f0 = mix(lo.x, min(ox, hx), self.arc_origin)
let f1 = mix(hx, max(ox, hx), self.arc_origin)
var res = kt_slider(vec4(0.0, 0.0, 0.0, 0.0), p, lo, hi, f0, f1, vec2(hx, cy - 0.5), px, rad, kt_col(5.0), self.hover, self.drag, self.disabled)
let d = length(p - vec2(hx, cy - 0.5)) - rad
let fr = kt_focus(d, px)
return Finish.over(res, vec4(1.0, 0.86, 0.2, 1.0) * clamp(fr, 0.0, 1.0) * self.focus * (1.0 - self.disabled))
}
// The round slider: a label column, then the channel with the orange rod
// and ball; the channel ends short of the right edge, where its value
// window stands on the board.
mod.widgets.SliderRoundFlat.clip_x = false
mod.widgets.SliderRoundFlat.clip_y = false
mod.widgets.SliderRoundFlat.draw_bg.vertex = fn() {
let m = 12.0
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
mod.widgets.SliderRoundFlat.draw_bg.pixel = fn() {
let m = 12.0
let px = kt_px(self.draw_pass.dpi_factor)
let o = self.rect_pos
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let rad = 9.0
let cy = kt_snap(self.rect_size.y * 0.5, o.y, px)
let x0 = kt_snap(self.label_size, o.x, px)
let lo = vec2(x0, cy - 3.5)
let hi = vec2(kt_snap(self.rect_size.x - 46.0, o.x, px), cy + 3.5)
let span = hi.x - lo.x - rad * 2.0
let hx = lo.x + rad + self.slide_pos * span
var res = kt_slider(vec4(0.0, 0.0, 0.0, 0.0), p, lo, hi, lo.x, hx, vec2(hx, cy - 0.5), px, rad, kt_col(4.0), self.hover, self.drag, self.disabled)
let d = length(p - vec2(hx, cy - 0.5)) - rad
let fr = kt_focus(d, px)
return Finish.over(res, vec4(1.0, 0.86, 0.2, 1.0) * clamp(fr, 0.0, 1.0) * self.focus * (1.0 - self.disabled))
}
// THE BAND is the slider with two balls, in purple: the rod lies between
// the two balls. The widget's grab geometry is the balls' own.
mod.widgets.RangeSliderFlat.clip_x = false
mod.widgets.RangeSliderFlat.clip_y = false
mod.widgets.RangeSliderFlat.track_inset = 0.0
mod.widgets.RangeSliderFlat.handle_size = 18.0
mod.widgets.RangeSliderFlat.draw_bg.vertex = fn() {
let m = 12.0
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
mod.widgets.RangeSliderFlat.draw_bg.pixel = fn() {
let m = 12.0
let px = kt_px(self.draw_pass.dpi_factor)
let o = self.rect_pos
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let rad = 9.0
let cy = kt_snap(self.label_px + (self.rect_size.y - self.label_px) * 0.5, o.y, px)
let lo = vec2(kt_snap(0.0, o.x, px), cy - 3.5)
let hi = vec2(kt_snap(self.rect_size.x, o.x, px), cy + 3.5)
let span = self.rect_size.x - rad * 2.0
let h0 = rad + self.start_pos * span
let h1 = rad + self.end_pos * span
let col = kt_col(3.0)
var res = kt_slider(vec4(0.0, 0.0, 0.0, 0.0), p, lo, hi, h0, h1, vec2(h0, cy), px, 0.0, col, 0.0, 0.0, self.disabled)
res = kt_ball(res, p, vec2(h0, cy - 0.5), rad, px, col, self.hot_start, self.drag * self.hot_start, self.disabled, 1.0)
res = kt_ball(res, p, vec2(h1, cy - 0.5), rad, px, col, self.hot_end, self.drag * self.hot_end, self.disabled, 1.0)
let d = min(length(p - vec2(h0, cy - 0.5)), length(p - vec2(h1, cy - 0.5))) - rad
let fr = kt_focus(d, px)
return Finish.over(res, vec4(1.0, 0.86, 0.2, 1.0) * clamp(fr, 0.0, 1.0) * self.focus * (1.0 - self.disabled))
}
// THE FADER: a slot moulded into the board with dotted scale marks either
// side, a yellow rod lying in it up to the value, and a chunky cap of green
// plastic with a purple top band and a white index line across its middle,
// throwing its shadow down the board.
mod.widgets.SliderFader.cap_size = 22.0
// A standing fader is 34 points wide, so a bank of four keeps to its column.
mod.widgets.SliderFaderY.width = 34.0
mod.widgets.SliderFader.track_inset = 8.0
mod.widgets.SliderFader.draw_bg.track_size = uniform(5.0)
mod.widgets.SliderFader.draw_bg.cap_cross = uniform(0.68)
mod.widgets.SliderFader.clip_x = false
mod.widgets.SliderFader.clip_y = false
mod.widgets.SliderFader.draw_bg.vertex = fn() {
let m = 12.0
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
mod.widgets.SliderFader.draw_bg.pixel = fn() {
let m = 12.0
let px = kt_px(self.draw_pass.dpi_factor)
let sz = self.rect_size
let o = self.rect_pos
let p = self.pos * (sz + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let v = self.is_vertical
let along = mix(sz.x, sz.y, v)
let across = mix(sz.y, sz.x, v)
let inset = self.inset_px
let travel = max(along - inset * 2.0 - self.cap_px, 1.0)
let foot = inset + self.cap_px * 0.5
let dis = self.disabled
// Track space: `a` along the track from the value's zero (up the screen
// when the fader stands), `b` across it.
let a = mix(p.x, sz.y - p.y, v)
let b = mix(p.y, p.x, v)
let mid = kt_snap(across * 0.5, mix(o.y, o.x, v), px)
let tw = min(self.track_size, across)
// The slot and the scale, laid out in track space and drawn in screen
// space through the swap.
let slo = mix(vec2(inset, mid - tw * 0.5), vec2(mid - tw * 0.5, inset), v)
let shi = mix(vec2(along - inset, mid + tw * 0.5), vec2(mid + tw * 0.5, along - inset), v)
var res = kt_groove(p, slo, shi, tw * 0.5, px, vec3(0.06, 0.16, 0.40))
// The scale: ten short white dashes either side of the slot.
let pitch = travel / 10.0
let k = (a - foot) / max(pitch, 1.0)
let ka = abs(fract(k + 0.5) - 0.5) * pitch
let inr = step(-0.5, k) * step(k, 10.5)
let side = abs(abs(b - mid) - (tw * 0.5 + 4.5))
let dash = Finish.cover(max(ka - 0.55, side - 1.6), px) * inr
res = Finish.over(res, vec4(0.86, 0.92, 1.0, 1.0) * dash * mix(0.55, 0.3, dis))
// The value: a yellow rod in the slot.
var lo = foot + self.fill_lo * travel
var hi = foot + self.fill_hi * travel
if self.fill_lo < 0.0005 {
lo = inset + 1.0
}
if self.fill_hi > 0.9995 {
hi = along - inset - 1.0
}
if hi - lo > 0.1 {
let rlo = mix(vec2(lo, mid - tw * 0.5 + 1.0), vec2(mid - tw * 0.5 + 1.0, sz.y - hi), v)
let rhi = mix(vec2(hi, mid + tw * 0.5 - 1.0), vec2(mid + tw * 0.5 - 1.0, sz.y - lo), v)
res = kt_rod(res, p, rlo, rhi, px, kt_col(2.0), dis)
}
// The cap.
let at = foot + self.slide_pos * travel
let cw = min(max(across * self.cap_cross, 8.0), across)
let cc = mix(vec2(at, mid), vec2(mid, sz.y - at), v)
let ch = mix(vec2(self.cap_px * 0.5, cw * 0.5), vec2(cw * 0.5, self.cap_px * 0.5), v)
let clo = vec2(Finish.snap(o.x + cc.x - ch.x, px), Finish.snap(o.y + cc.y - ch.y, px)) - o
let chi = clo + ch * 2.0
let r = 4.0
let held = self.drag * (1.0 - dis)
var cap = kt_block(p, clo, chi, r, px, kt_col(1.0), self.hover, held * 0.5, dis, self.focus, 1.0)
// The purple band over its top quarter, moulded in, and the index line.
let wall = clamp(0.14 * min(chi.y - clo.y, chi.x - clo.x), 1.5, 4.5)
let fhi = chi.y - wall * (1.0 - 0.375 * held)
let fmid = (clo.y + fhi) * 0.5
let band_d = max(Material.sd_box(p, (clo + chi) * 0.5, (chi - clo) * 0.5 - vec2(1.6, 0.0), r), p.y - (clo.y + (fhi - clo.y) * 0.3))
let band = Finish.cover(band_d, px) * step(clo.y + 0.8, p.y)
let pc = kt_dull(kt_col(3.0), dis)
cap = vec4(mix(cap.rgb, pc * (1.04 - 0.1 * (p.y - clo.y) / max(fhi - clo.y, 1.0)) * cap.a, band * 0.95), cap.a)
let line = Finish.cover(abs(p.y - fmid) - px * 0.75, px) * step(clo.x + 2.5, p.x) * step(p.x, chi.x - 2.5)
cap = vec4(mix(cap.rgb, vec3(1.0, 1.0, 1.0) * cap.a, line * mix(0.9, 0.5, dis)), cap.a)
return Finish.over(res, cap)
}
// ---------------------------------------------------------------- knobs
// The angle of `q` from straight down, clockwise, 0..2 pi.
let kt_ang = fn(q: vec2) -> float {
var ang = atan2(0.0 - q.x, q.y)
if ang < 0.0 {
ang = ang + 2.0 * PI
}
return ang
}
// A small knob: a channel ring round it with a yellow rod lit along the
// value, and the knob itself, a glossy red dome on a fluted skirt whose
// flutes turn with the value, a yellow pointer moulded across its top.
// Angles run from straight down, clockwise, the gap at the bottom.
let kt_knob = fn(p: vec2, c: vec2, rr: float, px: float, slide: float, origin: float, arc_origin: float, gap: float, hover: float, held: float, focus: float, disabled: float) -> vec4 {
let q = p - c
let rl = length(q)
let ang = kt_ang(q)
let one = PI / 180.0
let sweep = 2.0 * PI - gap * one
let start = gap * one * 0.5
let ve = start + sweep * slide
let oe = start + sweep * origin
let a0 = mix(start, min(oe, ve), arc_origin)
let a1 = mix(ve, max(oe, ve), arc_origin)
// The channel ring, open at the bottom like the arc.
let rw = clamp(0.16 * rr, 2.6, 5.0)
let rm = rr - rw * 0.5
let ac = clamp(ang, start, start + sweep)
let cp = c + vec2(0.0 - sin(ac), cos(ac)) * rm
let dch = length(p - cp) - rw * 0.5
let fl = vec3(0.06, 0.17, 0.42)
var res = vec4(0.0, 0.0, 0.0, 0.0)
let lip = Finish.ring_out(dch, 0.0, px * 1.5, px) * smoothstep(-0.2, 0.8, (p.y - c.y) / max(rr, 1.0)) * 0.4
res = vec4(vec3(0.62, 0.77, 1.0) * lip, lip)
var gcol = fl * (0.8 + 0.3 * clamp((p.y - cp.y) / max(rw, 1.0) + 0.5, 0.0, 1.0))
gcol = mix(gcol, fl * 0.45, Finish.band(dch, 0.0, px, px) * 0.6)
let ga = Finish.cover(dch, px)
res = Finish.over(res, vec4(gcol * ga, ga))
// The lit arc: a yellow rod along the channel from a0 to a1.
let aa = clamp(ang, a0, a1)
let ap = c + vec2(0.0 - sin(aa), cos(aa)) * rm
let dv = length(p - ap) - (rw * 0.5 - 0.8)
let live = step(0.002, a1 - a0)
let yb = kt_dull(kt_col(2.0), disabled)
let across = clamp((rl - rm) / max(rw * 0.5, 0.5), -1.0, 1.0)
let yrod = mix(kt_deep(yb) * 0.85, yb * 1.06, clamp(0.65 - 0.45 * across * (q.y / max(rl, 0.001)), 0.0, 1.0))
let va = Finish.cover(dv, px) * live
res = Finish.over(res, vec4(yrod * va, va))
// The knob.
let rk = rr - rw - clamp(0.07 * rr, 1.2, 3.0)
// Fourteen rounded lobes round the skirt, sharp notches between them.
let flute = sqrt(abs(cos(7.0 * (ang - ve))))
let rs = rk * (0.93 + 0.07 * flute)
var b = kt_dull(kt_col(0.0), disabled)
b = b + (vec3(1.0, 1.0, 1.0) - b) * 0.08 * hover * (1.0 - disabled)
let deep = kt_deep(b)
let so = c + vec2(0.12 * rk, 0.3 * rk)
let sd = length(p - so) - rk * 0.92
let sh = (1.0 - smoothstep(-0.3 * rk, 0.45 * rk, sd)) * 0.45 * (1.0 - 0.5 * disabled)
res = Finish.over(res, vec4(kt_shade() * sh, sh))
let qn = q / max(rk, 0.5)
let r2 = dot(qn, qn)
// The dome: a low sphere cap over the middle, the skirt falling at the
// fluted rim.
let zt = sqrt(max(1.0 - r2 * 0.6, 0.0))
let n = normalize(vec3(qn.x * 0.8, qn.y * 0.8, zt))
let l = normalize(vec3(-0.45, -0.68, 0.58))
let dif = max(dot(n, l), 0.0)
var col = mix(deep * 0.8, b * 1.08, dif)
let skirt = smoothstep(0.7, 0.8, sqrt(r2))
col = col * (1.0 + 0.3 * (flute - 0.6) * skirt)
col = mix(col, deep * 0.85, skirt * 0.3)
col = col + b * 0.25 * smoothstep(0.3, 0.95, qn.y) * skirt
// The ledge where the domed top stands off the fluted skirt: lit on
// its upper left, in shade on its lower right.
let ledge = exp(-((sqrt(r2) - 0.73) * (sqrt(r2) - 0.73)) / 0.0012)
let lf = dot(q / max(rl, 0.001), vec2(-0.45, -0.89))
col = col * (1.0 - 0.3 * ledge * clamp(0.0 - lf, 0.0, 1.0)) + (vec3(1.0, 1.0, 1.0) - col) * ledge * clamp(lf, 0.0, 1.0) * 0.35
let hv = normalize(l + vec3(0.0, 0.0, 1.0))
let nh = max(dot(n, hv), 0.0)
col = col + vec3(1.0, 1.0, 1.0) * (pow(nh, 8.0) * 0.14 + pow(nh, 60.0) * 0.8) * (1.0 - 0.8 * disabled)
let dk = rl - rs
col = mix(col, deep * 0.4, Finish.band(dk, 0.0, px, px) * 0.6)
let ka = Finish.cover(dk, px)
res = Finish.over(res, vec4(min(col, vec3(1.0, 1.0, 1.0)) * ka, ka))
// The pointer: a yellow bar moulded across the top, from near the
// middle to near the rim, with its own small shadow.
let dir = vec2(0.0 - sin(ve), cos(ve))
let pa = c + dir * rk * 0.3
let pb = c + dir * rk * 0.62
let pw = clamp(0.1 * rk, 1.2, 2.6)
let dps = kt_seg(p - vec2(0.3, 0.8), pa, pb) - pw
res = Finish.over(res, vec4(kt_shade() * 0.5, 0.5) * Finish.cover(dps, px * 2.0) * ka)
let dp = kt_seg(p, pa, pb) - pw
let pq = clamp(dot(p - pa, vec2(0.0 - dir.y, dir.x)) / max(pw, 0.5), -1.0, 1.0)
let pcol = mix(yb * 1.08, kt_deep(yb), clamp(0.5 + 0.5 * dot(vec2(0.0 - dir.y, dir.x) * pq, vec2(0.38, 0.92)), 0.0, 1.0))
res = Finish.over(res, vec4(pcol, 1.0) * Finish.cover(dp, px))
let fr = kt_focus(rl - rr, px)
return Finish.over(res, vec4(1.0, 0.86, 0.2, 1.0) * clamp(fr, 0.0, 1.0) * focus * (1.0 - disabled))
}
mod.widgets.RotaryKnob.clip_x = false
mod.widgets.RotaryKnob.clip_y = false
mod.widgets.RotaryKnob.draw_bg.vertex = fn() {
let m = 6.0
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
mod.widgets.RotaryKnob.draw_bg.pixel = fn() {
let m = 6.0
let px = kt_px(self.draw_pass.dpi_factor)
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let dia = min(self.rect_size.x, self.rect_size.y)
let c = vec2(Finish.snap(self.rect_pos.x + self.rect_size.x * 0.5, px) - self.rect_pos.x, Finish.snap(self.rect_pos.y + self.rect_size.y - dia * 0.5, px) - self.rect_pos.y)
return kt_knob(p, c, dia * 0.5 - 1.0, px, self.slide_pos, self.origin_pos, self.arc_origin, self.gap, self.hover, self.drag, self.focus, self.disabled)
}
// The rainbow of the dial: hue `t` 0..1 from red through orange, yellow,
// green and blue to purple, in the toy box's own colours.
let kt_rainbow = fn(t: float) -> vec3 {
let k = clamp(t, 0.0, 1.0) * 5.0
var col = mix(kt_col(0.0), kt_col(4.0), clamp(k, 0.0, 1.0))
col = mix(col, kt_col(2.0), clamp(k - 1.0, 0.0, 1.0))
col = mix(col, kt_col(1.0), clamp(k - 2.0, 0.0, 1.0))
col = mix(col, kt_col(5.0), clamp(k - 3.0, 0.0, 1.0))
return mix(col, kt_col(3.0), clamp(k - 4.0, 0.0, 1.0))
}
// THE DIAL is a toy wind-up knob. A round plate of turquoise plastic stands
// on the board; a moulded collar rises round a socket in it, a dark gap
// where the knob goes in, and in the socket a glossy yellow knob, a dome
// with soft corners, with one broad raised bar across its face: the grip
// taken between the fingers to wind it.
// The bar turns with the value, so the knob shows by its angle how far it
// is wound; a small red bead on the leading end of the bar tells its two
// ends apart. Round the plate a ring of short plastic dashes in the
// rainbow's colours is lit up to the value and dark past it.
// The wing's footprint in the knob's own frame (`u` along the wing, `v`
// across it, in points; `rk` the knob's radius): one broad straight bar
// across the whole face, rim to rim, a little wider at its ends. Negative inside.
let kt_wing_d = fn(u: float, v: float, rk: float) -> float {
let t = clamp(abs(u) / rk, 0.0, 1.0)
let w = rk * (0.18 + 0.04 * t * t)
let ex = abs(u) - rk * 1.1
let db = abs(v) - w
return length(vec2(max(ex, 0.0), max(db, 0.0))) + min(max(ex, db), 0.0)
}
// The knob's height at `q` (points from its centre), the wing pointing
// along `dir`: a low dome, nearly flat on top and rounding off hard at its
// rim, with the wing standing on it, round across like a moulded ridge and
// sinking into the dome's shoulder at its ends.
let kt_wind_h = fn(q: vec2, dir: vec2, rk: float) -> float {
let r = min(length(q) / max(rk, 0.5), 1.0)
let dome = rk * 0.24 * (1.0 - r * r * r * r)
let u = dot(q, dir)
let v = dot(q, vec2(0.0 - dir.y, dir.x))
let x = clamp((0.0 - kt_wing_d(u, v, rk)) / (rk * 0.14), 0.0, 1.0)
let wing = rk * 0.23 * x * x * (3.0 - 2.0 * x) * (1.0 - smoothstep(0.62, 0.97, r))
return dome + wing
}
let kt_dial = fn(p: vec2, c: vec2, rr: float, px: float, slide: float, origin: float, arc_origin: float, gap: float, hover: float, held: float, focus: float, disabled: float) -> vec4 {
let q = p - c
let rl = length(q)
let ang = kt_ang(q)
let one = PI / 180.0
let sweep = 2.0 * PI - gap * one
let start = gap * one * 0.5
let ve = start + sweep * slide
let oe = start + sweep * origin
let a0 = mix(start, min(oe, ve), arc_origin)
let a1 = mix(ve, max(oe, ve), arc_origin)
let live = 1.0 - disabled
let l = normalize(vec3(-0.45, -0.68, 0.58))
let hv = normalize(l + vec3(0.0, 0.0, 1.0))
let rdir = q / max(rl, 0.001)
var res = vec4(0.0, 0.0, 0.0, 0.0)
// The dashes: 19 over the sweep, each a short rounded rod pointing out.
let n = 19.0
let tl = clamp(0.13 * rr, 3.0, 9.0)
let tr = rr - tl * 0.5
let kf = clamp(floor((ang - start) / sweep * n), 0.0, n - 1.0)
let ta = start + (kf + 0.5) / n * sweep
let tdir = vec2(0.0 - sin(ta), cos(ta))
let tw = clamp(0.035 * rr, 1.1, 2.6)
let dt = kt_seg(p, c + tdir * (tr - tl * 0.5 + tw), c + tdir * (tr + tl * 0.5 - tw)) - tw
let lit = step(a0 - 0.001, ta) * step(ta, a1 + 0.001)
var tcol = kt_dull(kt_rainbow((kf + 0.5) / n), disabled)
tcol = mix(mix(tcol, vec3(0.08, 0.2, 0.48), 0.72), tcol, lit)
let tsh = Finish.cover(kt_seg(p - vec2(0.3, 0.8), c + tdir * (tr - tl * 0.5 + tw), c + tdir * (tr + tl * 0.5 - tw)) - tw, px * 2.0) * 0.4
res = Finish.over(res, vec4(kt_shade() * tsh, tsh))
let tlit = 1.0 + 0.25 * clamp(dot(tdir, vec2(0.0, -1.0)), -1.0, 1.0)
res = Finish.over(res, vec4(tcol * tlit, 1.0) * Finish.cover(dt, px))
// The plate: a turquoise disc on the board, its shoulder rounded off and
// lit from the upper left, its shadow falling down and a little right.
let rb = rr - tl - clamp(0.05 * rr, 1.5, 4.0)
let sd = length(q - vec2(0.08 * rb, 0.16 * rb)) - rb * 0.95
let sh = (1.0 - smoothstep(-0.2 * rb, 0.25 * rb, sd)) * 0.45
res = Finish.over(res, vec4(kt_shade() * sh, sh))
let tq = kt_dull(vec3(0.12, 0.72, 0.78), disabled)
let tdeep = kt_deep(tq)
let facing = dot(rdir, vec2(-0.38, -0.92))
let pbw = clamp(0.14 * rb, 2.0, 7.0)
let ps = clamp(1.0 + (rl - rb) / pbw, 0.0, 1.0)
var plate = tq * (1.04 - 0.08 * clamp((q.y + rb) / (2.0 * rb), 0.0, 1.0))
plate = mix(plate, tdeep, 0.45 * ps * ps)
plate = plate * (1.0 + 0.38 * facing * ps * ps)
plate = mix(plate, vec3(1.0, 1.0, 1.0), smoothstep(0.3, 0.95, facing) * smoothstep(0.2, 0.7, ps) * (1.0 - smoothstep(0.85, 1.0, ps)) * 0.5 * live)
plate = plate + vec3(1.0, 1.0, 1.0) * kt_grain(q, px) * 0.008
plate = mix(plate, tdeep * 0.35, Finish.band(rl - rb, 0.0, px, px) * 0.6)
// The collar: a soft raised ring of the same plastic round the socket.
let cw = 0.16 * rb
let gw = max(0.035 * rb, 1.2)
let rk = rb * 0.72 - gw
let rcm = rk + gw + cw * 0.5
let ca = clamp((rl - rcm) / max(cw * 0.5, 0.5), -1.0, 1.0)
let cn = normalize(vec3(rdir * ca * 0.85, sqrt(max(1.0 - ca * ca, 0.0)) + 0.25))
var collar = mix(tdeep * 0.9, tq * 1.12, max(dot(cn, l), 0.0))
collar = collar + vec3(1.0, 1.0, 1.0) * pow(max(dot(cn, hv), 0.0), 30.0) * 0.45 * live
plate = mix(plate, collar, Finish.cover(abs(rl - rcm) - cw * 0.5, px))
// The gap where the knob goes in: dark.
let ing = Finish.cover(rl - (rk + gw), px)
plate = mix(plate, tdeep * 0.22, ing)
let pa = Finish.cover(rl - rb, px)
res = Finish.over(res, vec4(min(plate, vec3(1.0, 1.0, 1.0)) * pa, pa))
// The knob's shadow in the socket and on the collar's inner side.
let ksd = length(q - vec2(0.05 * rk, 0.1 * rk)) - rk
let ksh = (1.0 - smoothstep(-0.05 * rk, 0.2 * rk, ksd)) * 0.5 * (1.0 - Finish.cover(rl - rk, px))
res = Finish.over(res, vec4(kt_shade() * ksh, ksh) * pa)
// The knob, the bar pointing at the value.
let dir = vec2(0.0 - sin(ve), cos(ve))
let perp = vec2(0.0 - dir.y, dir.x)
var b = kt_dull(kt_col(2.0), disabled)
b = b + (vec3(1.0, 1.0, 1.0) - b) * 0.08 * hover * live
b = b * (1.0 - 0.06 * held * live)
let deep = kt_deep(b)
let e = max(rk * 0.02, px * 0.5)
let hx = kt_wind_h(q + vec2(e, 0.0), dir, rk) - kt_wind_h(q - vec2(e, 0.0), dir, rk)
let hy = kt_wind_h(q + vec2(0.0, e), dir, rk) - kt_wind_h(q - vec2(0.0, e), dir, rk)
let nk = normalize(vec3(0.0 - hx / (2.0 * e), 0.0 - hy / (2.0 * e), 1.0))
let dif = max(dot(nk, l), 0.0)
var col = mix(deep * 0.78, b * 1.12, smoothstep(0.0, 0.92, dif))
col = mix(col, deep * 0.9, (1.0 - nk.z) * 0.35)
col = col + b * 0.22 * smoothstep(0.3, 0.95, q.y / max(rk, 1.0)) * (1.0 - nk.z)
// The bar's own shade on the dome toward the lower right.
let qs = q - vec2(0.07 * rk, 0.11 * rk)
let ws = smoothstep(0.0, rk * 0.1, 0.0 - kt_wing_d(dot(qs, dir), dot(qs, perp), rk))
let wm = smoothstep(0.0, rk * 0.1, 0.0 - kt_wing_d(dot(q, dir), dot(q, perp), rk))
col = col * (1.0 - 0.28 * ws * (1.0 - wm))
let nh = max(dot(nk, hv), 0.0)
col = col + vec3(1.0, 1.0, 1.0) * (pow(nh, 9.0) * 0.16 + pow(nh, 55.0) * 0.85) * (1.0 - 0.8 * disabled)
let dk = rl - rk
col = mix(col, deep * 0.4, Finish.band(dk, 0.0, px, px) * 0.6)
let ka = Finish.cover(dk, px)
res = Finish.over(res, vec4(min(col, vec3(1.0, 1.0, 1.0)) * ka, ka))
// The red bead on the leading end of the bar.
let bc = c + dir * (rk * 0.58) - vec2(0.0, rk * 0.06)
res = kt_ball(res, p, bc, max(rk * 0.075, 1.3), px, kt_col(0.0), 0.0, held, disabled, 0.25)
let fr = kt_focus(rl - rr, px)
return Finish.over(res, vec4(1.0, 0.86, 0.2, 1.0) * clamp(fr, 0.0, 1.0) * focus * live)
}
mod.widgets.RotaryFlat.clip_x = false
mod.widgets.RotaryFlat.clip_y = false
mod.widgets.RotaryFlat.draw_bg.vertex = fn() {
let m = 12.0
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
mod.widgets.RotaryFlat.draw_bg.pixel = fn() {
let m = 12.0
let px = kt_px(self.draw_pass.dpi_factor)
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let dia = min(self.rect_size.x, self.rect_size.y - 22.0)
let c = vec2(Finish.snap(self.rect_pos.x + self.rect_size.x * 0.5, px) - self.rect_pos.x, Finish.snap(self.rect_pos.y + 20.0 + dia * 0.5, px) - self.rect_pos.y)
return kt_dial(p, c, dia * 0.5 - 1.0, px, self.slide_pos, self.origin_pos, self.arc_origin, self.gap, self.hover, self.drag, self.focus, self.disabled)
}
// ---------------------------------------------------------------- fields
// A text field: a navy window in a thick red plastic frame, all inside its
// own box (a field clips what it holds), the last row left for the frame's
// short shadow. Under the hand the frame lightens; focused it lightens more
// and a pale line lights along the window's inner edge.
let kt_field = fn(p: vec2, size: vec2, o: vec2, px: float, rim: vec3, fill: vec3, hover: float, focus: float, disabled: float) -> vec4 {
let lo = vec2(kt_snap(0.0, o.x, px), kt_snap(0.0, o.y, px))
let hi = vec2(kt_snap(size.x - px * 0.49, o.x, px), kt_snap(size.y - px * 0.49, o.y, px) - 1.0)
let c = (lo + hi) * 0.5
let h = (hi - lo) * 0.5
let r = 7.0
let bw = 3.0
let sh = Finish.drop(p, c, h, r, vec2(0.0, 0.8), 0.6) * 0.5
var res = vec4(kt_shade() * sh, sh)
let f = mix(fill + vec3(0.02, 0.025, 0.04) * hover, vec3(0.42, 0.45, 0.52), disabled)
res = Finish.over(res, kt_frame(p, lo, hi, r, px, bw, rim, f, 1.0, hover, focus, disabled))
let di = Material.sd_box(p, c, h - vec2(bw, bw), r - bw)
let fo = focus * (1.0 - disabled)
return Finish.over(res, vec4(0.85, 0.94, 1.0, 1.0) * Finish.band(di, px, px * 2.0, px) * 0.6 * fo)
}
// Text fields; an input with no fill of its own (the one a slider, a combo
// box or a well holds) draws nothing.
mod.widgets.TextInputFlat.draw_bg.pixel = fn() {
if self.color.a < 0.01 {
return vec4(0.0, 0.0, 0.0, 0.0)
}
let px = kt_px(self.draw_pass.dpi_factor)
return kt_field(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, kt_col(0.0), vec3(0.08, 0.20, 0.47), self.hover, self.focus, self.disabled)
}
// A field well (a form's field, a search box with icons) wears the same
// frame and carries its input's state into it.
mod.widgets.FieldWell.draw_bg.pixel = fn() {
let px = kt_px(self.draw_pass.dpi_factor)
return kt_field(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, kt_col(0.0), vec3(0.08, 0.20, 0.47), self.hover, self.focus, self.disabled)
}
mod.widgets.FormField.input.draw_bg.pixel = fn() {
let px = kt_px(self.draw_pass.dpi_factor)
return kt_field(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, kt_col(0.0), vec3(0.08, 0.20, 0.47), self.hover, self.focus, self.disabled)
}
mod.widgets.TextInputFlat.padding = mod.turtle.Inset{left: 9.0 right: 9.0 top: 3.0 bottom: 4.0}
mod.widgets.TextInputFlat.min_height = 24.0
mod.widgets.TextInputFlat.draw_cursor.color = #ffffff
mod.widgets.TextInputFlat.draw_text.color = #f6f8ff
mod.widgets.TextInputFlat.draw_text.color_empty = #a9c0ea
mod.widgets.TextInputFlat.draw_text.color_disabled = #e6e8ee
// The number field: a purple frame, its stepper a pair of small purple keys
// standing inside the window's right end.
mod.widgets.NumberField.draw_bg.pixel = fn() {
let px = kt_px(self.draw_pass.dpi_factor)
return kt_field(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, kt_col(3.0), vec3(0.08, 0.20, 0.47), self.hover, self.focus, self.disabled)
}
mod.widgets.NumberField.spin_width = 15.0
mod.widgets.NumberSpin.width = 15.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 = kt_px(self.draw_pass.dpi_factor)
let o = self.rect_pos
let p = self.pos * self.rect_size
let x0 = kt_snap(0.0, o.x, px)
let xr = kt_snap(self.rect_size.x - px * 0.49, o.x, px) - 3.0
let yt = kt_snap(0.0, o.y, px) + 3.0
let yb = kt_snap(self.rect_size.y - px * 0.49, o.y, px) - 4.0
let mid = (yt + yb) * 0.5
let live = 1.0 - self.disabled
let col = kt_col(3.0)
var res = kt_block(p, vec2(x0, yt), vec2(xr, mid - 0.5), 2.5, px, col, max(0.0, self.hot), max(0.0, self.down), self.disabled, 0.0, 0.4)
res = Finish.over(res, kt_block(p, vec2(x0, mid + 0.5), vec2(xr, yb), 2.5, px, col, max(0.0, 0.0 - self.hot), max(0.0, 0.0 - self.down), self.disabled, 0.0, 0.4))
let ink = mix(vec3(1.0, 1.0, 1.0), vec3(0.8, 0.82, 0.88), self.disabled)
let cx = (x0 + xr) * 0.5
let cu = (yt + mid) * 0.5 - 0.5
let cd = (mid + yb) * 0.5 - 0.5
let du = min(kt_seg(p, vec2(cx - 2.2, cu + 1.1), vec2(cx, cu - 1.1)), kt_seg(p, vec2(cx, cu - 1.1), vec2(cx + 2.2, cu + 1.1)))
let dd = min(kt_seg(p, vec2(cx - 2.2, cd - 1.1), vec2(cx, cd + 1.1)), kt_seg(p, vec2(cx, cd + 1.1), vec2(cx + 2.2, cd - 1.1)))
let k = min(du, dd) - 0.6
return Finish.over(res, vec4(ink, 1.0) * Finish.cover(k, px) * (0.6 + 0.4 * live))
}
// The drop-down: a green plastic key with a chevron at its right, behind a
// moulded divider.
mod.widgets.DropDownFlat.padding = mod.turtle.Inset{left: 10.0 right: 26.0 top: 2.0 bottom: 4.0}
mod.widgets.DropDownFlat.clip_x = false
mod.widgets.DropDownFlat.clip_y = false
mod.widgets.DropDownFlat.draw_bg.vertex = fn() {
let m = 8.0
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
// The divider and the chevron, laid on a key or a field; `ink` is the
// chevron's, `face` the colour it is moulded in.
let kt_chevron = fn(dst: vec4, p: vec2, lo: vec2, hi: vec2, px: float, face: vec3, ink: vec3, disabled: float) -> vec4 {
let dx = hi.x - 19.0
let inside = step(lo.y + 4.0, p.y) * step(p.y, hi.y - 4.0)
let dark = step(dx - px, p.x) * step(p.x, dx) * inside
let light = step(dx, p.x) * step(p.x, dx + px) * inside
var res = Finish.over(dst, vec4(kt_deep(face) * 0.6, 1.0) * dark * 0.8)
res = Finish.over(res, vec4(mix(face, vec3(1.0, 1.0, 1.0), 0.45), 1.0) * light * 0.7)
let cc = vec2(dx + 9.5, (lo.y + hi.y) * 0.5)
let ds = min(kt_seg(p - vec2(0.0, 0.8), vec2(cc.x - 3.5, cc.y - 1.8), vec2(cc.x, cc.y + 1.8)), kt_seg(p - vec2(0.0, 0.8), vec2(cc.x, cc.y + 1.8), vec2(cc.x + 3.5, cc.y - 1.8))) - 1.0
res = Finish.over(res, vec4(kt_deep(face) * 0.4, 1.0) * Finish.cover(ds, px * 1.5) * 0.5)
let dc = min(kt_seg(p, vec2(cc.x - 3.5, cc.y - 1.8), vec2(cc.x, cc.y + 1.8)), kt_seg(p, vec2(cc.x, cc.y + 1.8), vec2(cc.x + 3.5, cc.y - 1.8))) - 1.0
return Finish.over(res, vec4(mix(ink, vec3(0.75, 0.78, 0.85), disabled), 1.0) * Finish.cover(dc, px))
}
mod.widgets.DropDownFlat.draw_bg.pixel = fn() {
let m = 8.0
let px = kt_px(self.draw_pass.dpi_factor)
let o = self.rect_pos
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let lo = vec2(kt_snap(0.0, o.x, px), kt_snap(0.0, o.y, px))
let hi = vec2(kt_snap(self.rect_size.x, o.x, px), kt_snap(self.rect_size.y, o.y, px))
let r = clamp(0.24 * (hi.y - lo.y), 3.0, 7.0)
let col = kt_col(1.0)
var res = kt_block(p, lo, hi, r, px, col, self.hover, max(self.down, self.active), self.disabled, self.focus, 1.0)
let wall = clamp(0.14 * min(hi.y - lo.y, 40.0), 1.5, 4.5)
return kt_chevron(res, p, lo, vec2(hi.x, hi.y - wall), px, col, vec3(1.0, 1.0, 1.0), self.disabled)
}
mod.widgets.DropDownFlat.draw_text.color = #f3fff0
mod.widgets.DropDownFlat.draw_text.color_hover = #ffffff
mod.widgets.DropDownFlat.draw_text.color_focus = #f3fff0
mod.widgets.DropDownFlat.draw_text.color_disabled = #a9c2b4
mod.widgets.DropDownFlat.draw_text.text_style = mod.theme.font_label{font_size: 8.5}
// The combo box: a yellow plastic bar, its words dark, its list opened from
// the chevron at its right.
mod.widgets.ComboBoxFlat.padding = mod.turtle.Inset{left: 10.0 right: 26.0 top: 3.0 bottom: 4.0}
mod.widgets.ComboBoxFlat.input.min_height = 0.0
mod.widgets.ComboBoxFlat.draw_bg.pixel = fn() {
let px = kt_px(self.draw_pass.dpi_factor)
let p = self.pos * self.rect_size
let o = self.rect_pos
let lo = vec2(kt_snap(0.0, o.x, px), kt_snap(0.0, o.y, px))
let hi = vec2(kt_snap(self.rect_size.x - px * 0.49, o.x, px), kt_snap(self.rect_size.y - px * 0.49, o.y, px) - 1.0)
let col = kt_col(2.0)
let c = (lo + hi) * 0.5
let h = (hi - lo) * 0.5
let sh = Finish.drop(p, c, h, 6.0, vec2(0.0, 0.8), 0.6) * 0.5
var res = vec4(kt_shade() * sh, sh)
res = Finish.over(res, kt_frame(p, lo, hi, 6.0, px, 2.5, col * 0.97, mix(col, vec3(1.0, 1.0, 1.0), 0.06 + 0.06 * self.hover), 1.0, self.hover, self.focus, self.disabled))
return kt_chevron(res, p, lo, hi, px, col, vec3(0.24, 0.18, 0.0), self.disabled)
}
mod.widgets.ComboBoxFlat.input.draw_text.color = #3d2f00
mod.widgets.ComboBoxFlat.input.draw_text.color_hover = #2e2300
mod.widgets.ComboBoxFlat.input.draw_text.color_focus = #2e2300
mod.widgets.ComboBoxFlat.input.draw_text.color_empty = #7a6a2a
mod.widgets.ComboBoxFlat.input.draw_text.color_disabled = #5c5a52
mod.widgets.ComboBoxFlat.input.draw_cursor.color = #3d2f00
// Selected words on the yellow bar: a pale wash the dark ink reads through.
mod.widgets.ComboBoxFlat.input.draw_selection.color = uniform(#fffbe04d)
mod.widgets.ComboBoxFlat.input.draw_selection.color_hover = uniform(#fffbe04d)
mod.widgets.ComboBoxFlat.input.draw_selection.color_focus = uniform(#fffbe05c)
mod.widgets.ComboBoxFlat.input.draw_selection.color_down = uniform(#fffbe05c)
// Open lists: a tray of navy plastic in a thick blue frame, rows that lift
// a little under the hand, and the chosen row a red key.
let kt_menu = fn(p: vec2, size: vec2, px: float) -> vec4 {
let lo = vec2(0.0, 0.0)
let hi = size
return kt_frame(p, lo, hi, 6.0, px, 2.5, kt_col(5.0), vec3(0.075, 0.19, 0.46), 1.0, 0.0, 0.0, 0.0)
}
let kt_row = fn(p: vec2, size: vec2, px: float, hover: float, active: float, disabled: float) -> vec4 {
let lo = vec2(2.0, 1.0)
let hi = size - vec2(2.0, 1.0)
var res = vec4(1.0, 1.0, 1.0, 1.0) * 0.06 * hover * (1.0 - disabled) * Finish.cover(Material.sd_box(p, (lo + hi) * 0.5, (hi - lo) * 0.5, 4.0), px)
let key = kt_block(p, lo, hi, 4.0, px, kt_col(0.0), hover, 0.0, 0.0, 0.0, 0.0)
return mix(res, key, active)
}
mod.widgets.PopupMenuFlat.draw_bg.pixel = fn() {
let px = kt_px(self.draw_pass.dpi_factor)
return kt_menu(self.pos * self.rect_size, self.rect_size, px)
}
mod.widgets.PopupMenuItem.draw_bg.pixel = fn() {
let px = kt_px(self.draw_pass.dpi_factor)
return kt_row(self.pos * self.rect_size, self.rect_size, px, self.hover, self.active, self.disabled)
}
mod.widgets.PopupMenuItem.draw_text.color_active = #ffffff
mod.widgets.ComboBoxFlat.draw_popup_bg.pixel = fn() {
let px = kt_px(self.draw_pass.dpi_factor)
return kt_menu(self.pos * self.rect_size, self.rect_size, px)
}
mod.widgets.ComboBoxFlat.draw_item.pixel = fn() {
let px = kt_px(self.draw_pass.dpi_factor)
return kt_row(self.pos * self.rect_size, self.rect_size, px, self.hover, self.active, 0.0)
}
mod.widgets.ComboBoxFlat.draw_item_text.color_active = #ffffff
// ---------------------------------------------------------------- tabs
// The segmented control: the whole strip one long red key; the chosen
// answer is pressed down into the board, a navy well with its word lit
// light blue, and the key's two ends stand over it.
mod.widgets.SegmentedControlFlat.draw_bg.vertex = fn() {
let m = 8.0
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
mod.widgets.SegmentedControlFlat.draw_bg.pixel = fn() {
let m = 8.0
let px = kt_px(self.draw_pass.dpi_factor)
let o = self.rect_pos
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let lo = vec2(kt_snap(0.0, o.x, px), kt_snap(0.0, o.y, px))
let hi = vec2(kt_snap(self.rect_size.x, o.x, px), kt_snap(self.rect_size.y, o.y, px))
let r = clamp(0.24 * (hi.y - lo.y), 3.0, 7.0)
return kt_block(p, lo, hi, r, px, kt_col(0.0), 0.0, 0.0, 0.0, 0.0, 1.0)
}
// The answers stand 2 points in from the strip's edge; the well reaches
// from the strip's top edge to its face's foot, a point in from either side.
mod.widgets.SegmentedControlFlat.draw_pill.vertex = fn() {
let m = 9.0
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
mod.widgets.SegmentedControlFlat.draw_pill.pixel = fn() {
let m = 9.0
let v = 1.0 - step(0.0, self.radius)
let hz = 1.0 - v
let px = kt_px(self.draw_pass.dpi_factor)
let o = self.rect_pos
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let sh = self.rect_size.y + 4.0 * hz
let wall = clamp(0.14 * min(sh, 40.0), 1.5, 4.5)
let lo = vec2(kt_snap(1.0 * hz - 0.5 * v, o.x, px), kt_snap(-1.0 * hz, o.y, px))
let hi = vec2(kt_snap(self.rect_size.x - 1.0 * hz + 0.5 * v, o.x, px), kt_snap(self.rect_size.y + (2.0 - wall - 0.5) * hz, o.y, px))
let r = clamp(0.24 * (hi.y - lo.y), 3.0, 6.0)
var res = kt_sunk(p, lo, hi, r, px, 0.0)
// A soft pool of the word's light on the floor under it.
let c = (lo + hi) * 0.5
let qq = vec2((p.x - c.x) / max((hi.x - lo.x) * 0.36, 1.0), (p.y - c.y) / max((hi.y - lo.y) * 0.45, 1.0))
let pool = exp(-dot(qq, qq)) * 0.16 * Finish.cover(Material.sd_box(p, c, (hi - lo) * 0.5, r), px)
return Finish.over(res, vec4(0.45, 0.7, 1.0, 1.0) * pool)
}
mod.widgets.SegmentedControlFlat.draw_text.text_style = mod.theme.font_label{font_size: 8.5}
mod.widgets.SegmentedControlFlat.draw_text_selected.text_style = mod.theme.font_label{font_size: 8.5}
mod.widgets.SegmentedControlFlat.draw_text.color = #fff3f0
mod.widgets.SegmentedControlFlat.draw_text_selected.color = #a8dcff
mod.widgets.SegmentedControlVertical.draw_pill.radius = -1.0
// Tabs: each tab is an orange key of its own with a small gap to the next;
// the tab in use is pressed into the board like the chosen segment, its word
// lit light blue.
mod.widgets.Tabs.height = 26.0
mod.widgets.Tabs.clip_x = false
mod.widgets.Tabs.clip_y = false
let kt_tab = fn(pos: vec2, size: vec2, o: vec2, dpi: float, hover: float, active: float) -> vec4 {
let m = 8.0
let px = kt_px(dpi)
let p = pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let lo = vec2(kt_snap(1.5, o.x, px), kt_snap(0.0, o.y, px))
let hi = vec2(kt_snap(size.x - 1.5, o.x, px), kt_snap(size.y, o.y, px))
let r = clamp(0.24 * (hi.y - lo.y), 3.0, 6.0)
let key = kt_block(p, lo, hi, r, px, kt_col(4.0), hover, 0.0, 0.0, 0.0, 1.0)
let well = kt_sunk(p, lo, hi, r, px, hover)
return mix(key, well, active)
}
mod.widgets.Tabs.draw_tab.vertex = fn() {
let m = 8.0
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
mod.widgets.Tabs.draw_tab_hover.vertex = fn() {
let m = 8.0
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
mod.widgets.Tabs.draw_tab_active.vertex = fn() {
let m = 8.0
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
mod.widgets.Tabs.draw_bg.pixel = fn() {
return vec4(0.0, 0.0, 0.0, 0.0)
}
mod.widgets.Tabs.draw_tab.pixel = fn() {
return kt_tab(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, 0.0, 0.0)
}
mod.widgets.Tabs.draw_tab_hover.pixel = fn() {
return kt_tab(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, 1.0, 0.0)
}
mod.widgets.Tabs.draw_tab_active.pixel = fn() {
return kt_tab(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, 0.0, 1.0)
}
mod.widgets.Tabs.draw_sep.pixel = fn() {
return vec4(0.0, 0.0, 0.0, 0.0)
}
mod.widgets.Tabs.draw_mark.color = #fff7ee
mod.widgets.Tabs.draw_text.color = #fff7ee
mod.widgets.Tabs.draw_text_active.color = #a8dcff
mod.widgets.Tabs.draw_text.text_style = mod.theme.font_label{font_size: 8.5}
mod.widgets.Tabs.draw_text_active.text_style = mod.theme.font_label{font_size: 8.5}
// ---------------------------------------------------------------- feedback
// The progress bar: a channel in the board with a coloured rod lying in it
// up to the value; still and unknown, a rod standing in the middle third.
mod.widgets.ProgressBarFlat.height = 8.0
mod.widgets.ProgressBarFlat.draw_bg.pixel = fn() {
let px = kt_px(self.draw_pass.dpi_factor)
let o = self.rect_pos
let p = self.pos * self.rect_size
let th = min(max(self.thickness, 6.0), self.rect_size.y)
let y0 = kt_snap((self.rect_size.y - th) * 0.5, o.y, px)
let x0 = kt_snap(self.rect_size.x * self.start, o.x, px)
let x1 = kt_snap(self.rect_size.x * self.end, o.x, px)
let lo = vec2(x0, y0)
let hi = vec2(x1, y0 + kt_snap(th, 0.0, px) - px)
var res = vec4(0.0, 0.0, 0.0, 0.0)
if self.track > 0.5 {
res = kt_groove(p, lo, hi, (hi.y - lo.y) * 0.5, px, vec3(0.06, 0.17, 0.42))
}
let ins = 1.0
let span = hi.x - lo.x - ins * 2.0
var f0 = lo.x + ins
var f1 = f0 + span * clamp((self.value - self.start) / max(self.end - self.start, 0.001), 0.0, 1.0)
if self.value < 0.0 {
f0 = lo.x + ins + span * 0.3
f1 = lo.x + ins + span * 0.7
}
let fill = self.fill_color()
if f1 - f0 > 0.01 {
let rod = kt_rod(vec4(0.0, 0.0, 0.0, 0.0), p, vec2(f0, lo.y + ins), vec2(f1, hi.y - ins), px, fill.rgb, 0.0)
res = Finish.over(res, rod * fill.a)
}
return res * self.opacity
}
mod.widgets.ProgressBarFlat.draw_bg.color_primary = #47b443
// The level meter: chunky rounded cells of plastic in a navy channel, green,
// then yellow toward the top, red at the end; unlit cells are dark glass of
// their own colour. The clip lamp is a small LED.
mod.widgets.LevelMeter.height = 8.0
mod.widgets.LevelMeterColumn.width = 11.0
mod.widgets.LevelMeter.draw_bg.pixel = fn() {
let px = kt_px(self.draw_pass.dpi_factor)
let o = self.rect_pos
let sz = self.rect_size
let p = self.pos * sz
let v = self.vertical
let along = mix(sz.x, sz.y, v)
let across = mix(sz.y, sz.x, v)
let a = mix(p.x, sz.y - p.y, v)
let b = mix(p.y, p.x, v)
let lamp_room = self.lamp * (across + 3.0)
let bar = max(4.0, along - lamp_room)
let wlo = mix(vec2(0.0, 0.0), vec2(0.0, sz.y - bar), v)
let whi = mix(vec2(bar, sz.y), vec2(sz.x, sz.y), v)
let lo = vec2(kt_snap(wlo.x, o.x, px), kt_snap(wlo.y, o.y, px))
let hi = vec2(kt_snap(whi.x, o.x, px), kt_snap(whi.y, o.y, px) - px)
var res = kt_groove(p, lo, hi, min(across * 0.5, 4.0), px, vec3(0.05, 0.14, 0.36))
let pad = 1.5
let span = max(1.0, bar - pad * 2.0)
// The cells: about a cell and a half of the meter's thickness long.
let pitch = span / max(floor(span / max(across * 1.5, 4.0) + 0.5), 1.0)
let k = floor((a - pad) / pitch)
let ca = pad + (k + 0.5) * pitch
let share = clamp((ca - pad) / span, 0.0, 1.0)
var lc = kt_col(1.0)
lc = mix(lc, kt_col(2.0), step(self.hot_at, share))
lc = mix(lc, kt_col(4.0), step(mix(self.hot_at, self.hot_to, 0.6), share))
lc = mix(lc, kt_col(0.0), step(self.hot_to, share))
let on = step(share - 0.5 / max(span / pitch, 1.0), self.level) * step(0.001, self.level)
let held = step(abs(ca - (pad + span * self.hold)), pitch * 0.5) * step(0.002, self.hold)
let lit = max(on, held)
let cw = max(pitch - 1.4, 1.0)
let cb = across - pad * 2.0
let cq = vec2(a - ca, b - across * 0.5)
let cd = Material.sd_box(cq, vec2(0.0, 0.0), vec2(cw * 0.5, cb * 0.5), min(1.6, cb * 0.5))
let inb = step(pad, a) * step(a, bar - pad)
let deep = kt_deep(lc)
let t = clamp(cq.y / max(cb * 0.5, 0.5), -1.0, 1.0)
var cc = mix(lc * 1.08, deep * 0.85, clamp(0.35 + 0.4 * t, 0.0, 1.0))
cc = cc + vec3(1.0, 1.0, 1.0) * exp(-((t + 0.5) * (t + 0.5)) / 0.08) * 0.3
let unl = mix(vec3(0.06, 0.15, 0.38), deep, 0.28)
cc = mix(unl, cc, lit)
res = Finish.over(res, vec4(cc, 1.0) * Finish.cover(cd, px) * inb * self.opacity)
if self.lamp > 0.5 {
let lcen = mix(vec2(bar + 3.0 + across * 0.5, sz.y * 0.5), vec2(sz.x * 0.5, across * 0.5), v)
let rl = clamp(across * 0.5 - 0.5, 1.5, 3.5)
res = kt_led_lamp(res, p, lcen, vec2(rl, rl), step(0.5, self.over), kt_col(0.0), 1.0, px, vec3(0.153, 0.388, 0.725), rl * 1.2, kt_col(9.0), 0.0)
}
return res * self.opacity
}
// The spinner: a channel ring in the board with a yellow rod lying three
// quarters of the way round. A sheet may not read the pass clock, so it
// stands still.
mod.widgets.SpinnerFlat.size = 20.0
mod.widgets.SpinnerFlat.draw_bg.pixel = fn() {
let px = kt_px(self.draw_pass.dpi_factor)
let p = self.pos * self.rect_size
let o = self.rect_pos
let c = vec2(Finish.snap(o.x + self.rect_size.x * 0.5, px), Finish.snap(o.y + self.rect_size.y * 0.5, px)) - o
let size = min(self.rect_size.x, self.rect_size.y)
let rw = 3.6
let rm = size * 0.5 - rw * 0.5 - 0.5
let q = p - c
let rl = length(q)
let dr = abs(rl - rm) - rw * 0.5
let fl = vec3(0.06, 0.17, 0.42)
let lip = Finish.ring_out(dr, 0.0, px * 1.5, px) * smoothstep(-0.2, 0.8, q.y / max(rm, 1.0)) * 0.4
var res = vec4(vec3(0.62, 0.77, 1.0) * lip, lip)
var g = fl * (0.8 + 0.3 * clamp((rl - rm) / rw + 0.5, 0.0, 1.0))
g = mix(g, fl * 0.45, Finish.band(dr, 0.0, px, px) * 0.6)
res = Finish.over(res, vec4(g, 1.0) * Finish.cover(dr, px))
let ang = kt_ang(q)
let ac = clamp(ang, 0.8, 0.8 + 1.5 * PI)
let ap = c + vec2(0.0 - sin(ac), cos(ac)) * rm
let da = length(p - ap) - (rw * 0.5 - 0.7)
let yb = kt_col(2.0)
let across = clamp((rl - rm) / max(rw * 0.5, 0.5), -1.0, 1.0)
let yc = mix(kt_deep(yb) * 0.85, yb * 1.06, clamp(0.65 - 0.45 * across * (q.y / max(rl, 0.001)), 0.0, 1.0))
res = Finish.over(res, vec4(yc, 1.0) * Finish.cover(da, px))
return res * self.alpha
}
// A count is a small navy window in a thick yellow frame, its figures white.
mod.widgets.BadgePalette.neutral = #0d2154
mod.widgets.BadgePalette.on_neutral = #ffffff
mod.widgets.BadgeFlat.shape = mod.widgets.BadgeShape.Rounded
mod.widgets.BadgeFlat.radius = 4.0
mod.widgets.BadgeFlat.draw_bg.pixel = fn() {
let px = kt_px(self.draw_pass.dpi_factor)
let o = self.rect_pos
let p = self.pos * self.rect_size
let lo = vec2(kt_snap(0.0, o.x, px), kt_snap(0.0, o.y, px))
let hi = vec2(kt_snap(self.rect_size.x - px * 0.49, o.x, px), kt_snap(self.rect_size.y - px * 0.49, o.y, px))
let r = min(5.0, min(hi.x - lo.x, hi.y - lo.y) * 0.5)
return kt_frame(p, lo, hi, r, px, 2.0, kt_col(2.0), self.color.rgb, 1.0, 0.0, 0.0, 0.0) * max(self.color.a, 0.0)
}
// A chip is a small yellow key, its label dark; a keyed chip wears the
// focus ring.
mod.widgets.ChipFlat.draw_text.text_style = mod.theme.font_label{font_size: 8.5}
mod.widgets.ChipFlat.draw_text.color = #3d2f00
mod.widgets.ChipFlat.draw_bg.vertex = fn() {
let m = 8.0
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
mod.widgets.ChipFlat.draw_bg.pixel = fn() {
let m = 8.0
let px = kt_px(self.draw_pass.dpi_factor)
let o = self.rect_pos
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let lo = vec2(kt_snap(0.0, o.x, px), kt_snap(0.0, o.y, px))
let hi = vec2(kt_snap(self.rect_size.x, o.x, px), kt_snap(self.rect_size.y, o.y, px))
let r = clamp(0.24 * (hi.y - lo.y), 3.0, 7.0)
return kt_block(p, lo, hi, r, px, kt_col(2.0), self.hover, self.down, 0.0, self.keyed, 1.0)
}
// ---------------------------------------------------------------- surfaces
// The scroll bar: a channel in the board, the thumb a yellow rod in it.
mod.widgets.ScrollBar.bar_size = 9.0
mod.widgets.ScrollBar.bar_side_margin = 1.0
mod.widgets.ScrollBar.draw_bg.pixel = fn() {
let px = kt_px(self.draw_pass.dpi_factor)
let o = self.rect_pos
let sz = self.rect_size
let p = self.pos * sz
let v = self.is_vertical
let lo = vec2(kt_snap(0.0, o.x, px), kt_snap(0.0, o.y, px))
let hi = vec2(kt_snap(sz.x, o.x, px), kt_snap(sz.y, o.y, px))
var res = kt_groove(p, lo, hi, min(hi.x - lo.x, hi.y - lo.y) * 0.5, px, vec3(0.06, 0.17, 0.42))
let t0 = mix(sz.x * self.norm_scroll, sz.y * self.norm_scroll, v)
let t1 = t0 + mix(sz.x * self.norm_handle, sz.y * self.norm_handle, v)
let tlo = mix(vec2(t0 + 1.0, 1.5), vec2(1.5, t0 + 1.0), v)
let thi = mix(vec2(t1 - 1.0, sz.y - 1.5), vec2(sz.x - 1.5, t1 - 1.0), v)
let slo = vec2(kt_snap(tlo.x, o.x, px), kt_snap(tlo.y, o.y, px))
let shi = vec2(kt_snap(thi.x, o.x, px), kt_snap(thi.y, o.y, px))
let col = kt_col(2.0) + (vec3(1.0, 1.0, 1.0) - kt_col(2.0)) * 0.1 * max(self.hover, self.drag)
// The rod lies across the bar either way; lit from above, so a standing
// rod is shaded across x.
let c = (slo + shi) * 0.5
let h = max((shi - slo) * 0.5, vec2(0.5, 0.5))
let d = Material.sd_box(p, c, h, min(h.x, h.y))
let u = clamp(mix((p.y - c.y) / h.y, (p.x - c.x) / h.x, v), -1.0, 1.0)
let z = sqrt(max(1.0 - u * u, 0.0))
var rc = mix(kt_deep(col) * 0.85, col * 1.06, clamp(0.3 + 0.6 * z - 0.3 * u, 0.0, 1.0))
rc = rc + vec3(1.0, 1.0, 1.0) * exp(-((u + 0.45) * (u + 0.45)) / 0.06) * 0.35
rc = mix(rc, kt_deep(col) * 0.5, Finish.band(d, 0.0, px, px) * 0.5)
res = Finish.over(res, vec4(min(rc, vec3(1.0, 1.0, 1.0)), 1.0) * Finish.cover(d, px))
return res * self.opacity
}
// A raised panel: a big block of red plastic with a moulded lip just inside
// its edge, standing on the board with its soft shadow held within half the
// gap to its neighbours. A sunken panel is a green block whose middle is
// cut away into a pocket. A card is a purple block with a lighter rim.
let kt_slab = fn(p: vec2, size: vec2, o: vec2, px: float, r: float, base: vec3, rim: float, hover: float) -> vec4 {
let lo = vec2(kt_snap(0.0, o.x, px), kt_snap(0.0, o.y, px))
let hi = vec2(kt_snap(size.x, o.x, px), kt_snap(size.y, o.y, px))
let c = (lo + hi) * 0.5
let h = (hi - lo) * 0.5
let d = Material.sd_box(p, c, h, r)
let s1 = Finish.drop(p, c, h - vec2(2.0, 0.0), r, vec2(1.2, 3.6), 3.0) * 0.45
let s2 = Finish.drop(p, c, h, r, vec2(0.0, px + 0.6), px + 0.8) * 0.5
let sh = 1.0 - (1.0 - s1) * (1.0 - s2)
var res = vec4(kt_shade() * sh, sh)
let b = base + (vec3(1.0, 1.0, 1.0) - base) * 0.05 * hover
let deep = kt_deep(b)
// The wall along the foot, 5 points.
let wall = 5.0
let fhi = vec2(hi.x, hi.y - wall)
let fc = (lo + fhi) * 0.5
let fh = (fhi - lo) * 0.5
let df = Material.sd_box(p, fc, fh, r)
let n = kt_box_n(p, fc, fh, r)
let bw = clamp(0.32 * min(fh.x, fh.y), 4.0, 16.0)
let s = clamp(1.0 + df / bw, 0.0, 1.0)
let s2c = s * s
let facing = dot(n, vec2(-0.38, -0.92))
let t = clamp((p.y - lo.y) / max(fhi.y - lo.y, 1.0), 0.0, 1.0)
var face = b * (1.05 - 0.09 * t)
face = mix(face, deep, 0.5 * s2c)
face = face * (1.0 + 0.4 * facing * s2c)
let catchl = smoothstep(0.3, 0.95, facing) * smoothstep(0.2, 0.7, s) * (1.0 - smoothstep(0.85, 1.0, s))
face = mix(face, vec3(1.0, 1.0, 1.0), catchl * 0.5)
// A soft reflection over the upper left of the face.
let uv = clamp((p - lo) / max(fhi - lo, vec2(1.0, 1.0)), vec2(0.0, 0.0), vec2(1.0, 1.0))
let refl = exp(-((uv.x - 0.25) * (uv.x - 0.25) / 0.09 + (uv.y - 0.12) * (uv.y - 0.12) / 0.03)) * (1.0 - s)
face = mix(face, vec3(1.0, 1.0, 1.0), refl * 0.16)
face = face + vec3(1.0, 1.0, 1.0) * kt_grain(p, px) * 0.008
// The moulded lip: a low rounded ridge `rim` points in from the edge.
if rim > 0.0 {
let dl = Material.sd_box(p, fc, fh - vec2(rim, rim), max(r - rim, 1.0))
let x = dl / 1.4
let ridge = exp(-x * x)
let nl = kt_box_n(p, fc, fh - vec2(rim, rim), max(r - rim, 1.0))
let lf = dot(nl * clamp(x, -1.0, 1.0), vec2(-0.38, -0.92))
face = face * (1.0 + 0.22 * lf * ridge) + (vec3(1.0, 1.0, 1.0) - face) * ridge * 0.08
}
let tw = clamp((p.y - fhi.y + r * 0.5) / max(wall + r * 0.5, 1.0), 0.0, 1.0)
let wallc = mix(b * 0.72, deep * 0.5, tw)
var col = mix(wallc, face, Finish.cover(df, px))
col = mix(col, deep * 0.32, Finish.band(d, 0.0, px, px) * mix(0.3, 0.8, step(c.y, p.y)))
let a = Finish.cover(d, px)
return Finish.over(res, vec4(col * a, a))
}
// The pocket cut into a sunken panel: a deeper floor of the same plastic
// with the frame's shadow falling in from the top and the sides.
let kt_pocket = fn(dst: vec4, p: vec2, size: vec2, o: vec2, px: float, r: float, base: vec3, frame: float) -> vec4 {
let lo = vec2(kt_snap(frame, o.x, px), kt_snap(frame, o.y, px))
let hi = vec2(kt_snap(size.x - frame, o.x, px), kt_snap(size.y - 8.5, o.y, px))
let c = (lo + hi) * 0.5
let h = (hi - lo) * 0.5
let rr = max(r - frame * 0.5, 2.0)
let d = Material.sd_box(p, c, h, rr)
let deep = kt_deep(base)
let t = clamp((p.y - lo.y) / max(hi.y - lo.y, 1.0), 0.0, 1.0)
var col = mix(base * 0.66, base * 0.78, t)
let ins = Finish.inset(p, c, h, rr, vec2(0.0, 3.0), 2.6)
col = mix(col, deep * 0.45, ins * 0.75)
col = mix(col, mix(base, vec3(1.0, 1.0, 1.0), 0.3), Finish.band(d, 0.0, px * 1.5, px) * smoothstep(c.y + h.y * 0.4, hi.y, p.y) * 0.6)
col = col + vec3(1.0, 1.0, 1.0) * kt_grain(p, px) * 0.008
let a = Finish.cover(d, px)
return Finish.over(dst, vec4(col * a, a))
}
mod.widgets.PanelView.clip_x = false
mod.widgets.PanelView.clip_y = false
mod.widgets.PanelView.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 12.0 bottom: 12.0}
mod.widgets.PanelView.draw_bg.vertex = fn() {
let m = 14.0
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
mod.widgets.PanelView.draw_bg.pixel = fn() {
let m = 14.0
let px = kt_px(self.draw_pass.dpi_factor)
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
if self.sunken > 0.5 {
let g = kt_col(1.0)
let slab = kt_slab(p, self.rect_size, self.rect_pos, px, 16.0, g, 0.0, 0.0)
return kt_pocket(slab, p, self.rect_size, self.rect_pos, px, 16.0, g, 5.5)
}
return kt_slab(p, self.rect_size, self.rect_pos, px, 16.0, vec3(0.82, 0.16, 0.13), 0.0, 0.0)
}
// The rounded view keeps the colour its caller gives it: opaque, it is a
// slab of plastic in that colour, rounded, its face catching the light at
// its top edge and its foot a shade deeper; translucent, it stays a plain
// tint.
mod.widgets.RoundedView.draw_bg.pixel = fn() {
let px = kt_px(self.draw_pass.dpi_factor)
let p = self.pos * self.rect_size
let lo = vec2(self.border_inset.x, self.border_inset.y)
let hi = self.rect_size - vec2(self.border_inset.z, self.border_inset.w)
let c = (lo + hi) * 0.5
let h = max((hi - lo) * 0.5, vec2(0.5, 0.5))
let r = min(2.0 * max(1.0, self.border_radius), min(h.x, h.y))
let d = Material.sd_box(p, c, h, r)
var fill = self.color
if self.color_2.x > -0.5 {
fill = mix(self.color, self.color_2, mix(self.pos.y, self.pos.x, self.gradient_fill_horizontal))
}
let solid = step(0.98, fill.a)
let b = fill.rgb
let deep = kt_deep(b)
let n = kt_box_n(p, c, h, r)
let bw = clamp(0.3 * min(h.x, h.y), 1.0, 14.0)
let s = clamp(1.0 + d / max(bw, 0.5), 0.0, 1.0)
let facing = dot(n, vec2(-0.38, -0.92))
let t = clamp((p.y - lo.y) / max(hi.y - lo.y, 1.0), 0.0, 1.0)
var col = b * (1.04 - 0.08 * t)
col = mix(col, deep, 0.45 * s * s)
col = col * (1.0 + 0.36 * facing * s * s)
let catchl = smoothstep(0.3, 0.95, facing) * smoothstep(0.2, 0.7, s) * (1.0 - smoothstep(0.85, 1.0, s))
col = mix(col, vec3(1.0, 1.0, 1.0), catchl * 0.4)
col = mix(b, col, solid)
var ink = self.border_color
if self.border_size > 0.01 {
col = mix(col, ink.rgb, Finish.band(d, 0.0, max(self.border_size, px), px) * ink.a)
} else {
col = mix(col, deep * 0.4, Finish.band(d, 0.0, px, px) * 0.6 * solid)
}
let a = Finish.cover(d, px)
return vec4(col * a * fill.a, a * fill.a) + vec4(ink.rgb * ink.a, ink.a) * Finish.band(d, 0.0, max(self.border_size, px), px) * a * (1.0 - fill.a) * step(0.01, self.border_size)
}
// The card: a purple slab with a lighter moulded rim; a pressable card
// lifts under the hand.
mod.widgets.Card.clip_x = false
mod.widgets.Card.clip_y = false
mod.widgets.Card.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 12.0 bottom: 12.0}
mod.widgets.Card.draw_bg.vertex = fn() {
let m = 14.0
return self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
mod.widgets.Card.draw_bg.pixel = fn() {
let m = 14.0
let px = kt_px(self.draw_pass.dpi_factor)
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let o = self.rect_pos
let pu = kt_col(3.0)
var res = kt_slab(p, self.rect_size, o, px, 10.0, pu, 0.0, self.hover)
// The rim: a lighter frame moulded on the face, 3.5 points wide.
let lo = vec2(kt_snap(1.5, o.x, px), kt_snap(1.5, o.y, px))
let hi = vec2(kt_snap(self.rect_size.x - 1.5, o.x, px), kt_snap(self.rect_size.y - 6.5, o.y, px))
let fr = kt_frame(p, lo, hi, 8.5, px, 3.5, mix(pu, vec3(1.0, 1.0, 1.0), 0.2), pu, 0.0, self.hover, 0.0, 0.0)
return Finish.over(res, fr)
}
// List rows: 22 points, the words 20 points in. Each row carries a small
// orange ball at its left; the chosen row is a red key with its ball
// yellow.
mod.widgets.ListItem.height = 22.0
mod.widgets.ListItemOne.height = 22.0
mod.widgets.ListItem.padding = mod.turtle.Inset{left: 20.0 right: 8.0 top: 0.0 bottom: 2.0}
mod.widgets.ListItemOne.padding = mod.turtle.Inset{left: 20.0 right: 8.0 top: 0.0 bottom: 2.0}
mod.widgets.ListItem.draw_bg.pixel = fn() {
let px = kt_px(self.draw_pass.dpi_factor)
let o = self.rect_pos
let p = self.pos * self.rect_size
let live = 1.0 - self.disabled
let lo = vec2(kt_snap(1.0, o.x, px), kt_snap(1.0, o.y, px))
let hi = vec2(kt_snap(self.rect_size.x - 2.0, o.x, px), kt_snap(self.rect_size.y - 1.0, o.y, px))
let d = Material.sd_box(p, (lo + hi) * 0.5, (hi - lo) * 0.5, 5.0)
var res = vec4(1.0, 1.0, 1.0, 1.0) * (0.07 * self.hover + 0.04 * self.down) * live * Finish.cover(d, px)
let key = kt_block(p, lo, hi, 5.0, px, kt_col(0.0), self.hover, self.down, self.disabled, 0.0, 0.0)
res = mix(res, key, self.selected)
let wall = clamp(0.14 * min(hi.y - lo.y, 40.0), 1.5, 4.5)
let bc = vec2(kt_snap(10.0, o.x, px), (lo.y + hi.y - wall * self.selected) * 0.5)
return kt_ball(res, p, bc, 3.6, px, mix(kt_col(4.0), kt_col(2.0), self.selected), self.hover, 0.0, self.disabled, 0.6)
}
mod.widgets.ListItem.draw_text.color = #ffffff
mod.widgets.ListItem.draw_text_2.color = #c6d6f4
// The tree's rows stand on the board, clear; the open row is a red key.
mod.widgets.FileTreeNode.draw_bg.pixel = fn() {
let px = kt_px(self.draw_pass.dpi_factor)
let p = self.pos * self.rect_size
let lo = vec2(1.0, 1.0)
let hi = self.rect_size - vec2(2.0, 1.0)
let d = Material.sd_box(p, (lo + hi) * 0.5, (hi - lo) * 0.5, 5.0)
let key = kt_block(p, lo, hi, 5.0, px, kt_col(0.0), 0.0, 0.0, 0.0, 0.0, 0.0)
return key * self.active
}
mod.widgets.FileTreeNode.draw_text.color_active = #ffffff
mod.widgets.FileTreeNode.draw_icon.color_active = #ffffff
// ---------------------------------------------------------------- ink
// Words on the parts are moulded in: a soft dark shadow one device pixel
// under each glyph (on the yellow key, a pale one); a key's label sinks with
// it when it is pressed. Words on the board carry the same shadow. The word
// of a chosen segment or tab is lit: a light-blue glow round it.
mod.widgets.ButtonFlat.draw_text.pixel = fn() {
let nudge = dFdy(self.t.xy) * 2.0 * step(0.5, self.down)
let c = self.get_color()
let dxt = length(dFdx(self.t))
let dyt = length(dFdy(self.t))
if self.texture_index > 2.5 {
if self.slug_curve_count() < 0.5 {
return vec4(0.0, 0.0, 0.0, 0.0)
}
let dy = dFdy(self.pos)
let sample = self.pos - dFdy(self.pos) * 2.0 * step(0.5, self.down)
let px_x = max(abs(dFdx(self.pos.x)) + abs(dFdy(self.pos.x)), 0.00001)
let px_y = max(abs(dFdx(self.pos.y)) + abs(dFdy(self.pos.y)), 0.00001)
let grid = if self.aa_4x4 > 0.5 {4.0} else if self.aa_2x2 > 0.5 {2.0} else {1.0}
let inv_grid = if self.aa_4x4 > 0.5 {0.25} else if self.aa_2x2 > 0.5 {0.5} else {1.0}
var alpha_sum = 0.0
var sample_index = 0.0
loop {
if sample_index >= grid * grid { break }
let row = floor(sample_index * inv_grid)
let col = sample_index - row * grid
let offset = (vec2(col, row) + 0.5 - grid * 0.5) * inv_grid * vec2(px_x, px_y)
alpha_sum = alpha_sum + self.alpha_at(sample + offset, px_x, px_y)
sample_index = sample_index + 1.0
}
let alpha_base = clamp(alpha_sum * inv_grid * inv_grid, 0.0, 1.0)
let darken = clamp(max(px_x, px_y) * self.stem_darken, 0.0, self.stem_darken_max)
let edge_weight = clamp(1.0 - abs(alpha_base * 2.0 - 1.0), 0.0, 1.0)
let alpha = clamp(alpha_base + darken * edge_weight, 0.0, 1.0)
let c = self.get_color()
let main = vec4(c.rgb * c.a * alpha, c.a * alpha)
let s = clamp(self.alpha_at(sample - dy, px_x, px_y), 0.0, 1.0) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
if self.texture_index < 0.5 {
let tex_size = self.grayscale_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
if self.texture_index > 1.5 && self.texture_index < 2.5 {
let tex_size = self.msdf_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
return self.sample_text_pixel()
}
mod.widgets.Button.draw_text.pixel = fn() {
let nudge = dFdy(self.t.xy) * 2.0 * step(0.5, self.down)
let c = self.get_color()
let dxt = length(dFdx(self.t))
let dyt = length(dFdy(self.t))
if self.texture_index > 2.5 {
if self.slug_curve_count() < 0.5 {
return vec4(0.0, 0.0, 0.0, 0.0)
}
let dy = dFdy(self.pos)
let sample = self.pos - dFdy(self.pos) * 2.0 * step(0.5, self.down)
let px_x = max(abs(dFdx(self.pos.x)) + abs(dFdy(self.pos.x)), 0.00001)
let px_y = max(abs(dFdx(self.pos.y)) + abs(dFdy(self.pos.y)), 0.00001)
let grid = if self.aa_4x4 > 0.5 {4.0} else if self.aa_2x2 > 0.5 {2.0} else {1.0}
let inv_grid = if self.aa_4x4 > 0.5 {0.25} else if self.aa_2x2 > 0.5 {0.5} else {1.0}
var alpha_sum = 0.0
var sample_index = 0.0
loop {
if sample_index >= grid * grid { break }
let row = floor(sample_index * inv_grid)
let col = sample_index - row * grid
let offset = (vec2(col, row) + 0.5 - grid * 0.5) * inv_grid * vec2(px_x, px_y)
alpha_sum = alpha_sum + self.alpha_at(sample + offset, px_x, px_y)
sample_index = sample_index + 1.0
}
let alpha_base = clamp(alpha_sum * inv_grid * inv_grid, 0.0, 1.0)
let darken = clamp(max(px_x, px_y) * self.stem_darken, 0.0, self.stem_darken_max)
let edge_weight = clamp(1.0 - abs(alpha_base * 2.0 - 1.0), 0.0, 1.0)
let alpha = clamp(alpha_base + darken * edge_weight, 0.0, 1.0)
let c = self.get_color()
let main = vec4(c.rgb * c.a * alpha, c.a * alpha)
let s = clamp(self.alpha_at(sample - dy, px_x, px_y), 0.0, 1.0) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
if self.texture_index < 0.5 {
let tex_size = self.grayscale_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
if self.texture_index > 1.5 && self.texture_index < 2.5 {
let tex_size = self.msdf_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
return self.sample_text_pixel()
}
mod.widgets.ButtonPrimary.draw_text.pixel = fn() {
let nudge = dFdy(self.t.xy) * 2.0 * step(0.5, self.down)
let c = self.get_color()
let dxt = length(dFdx(self.t))
let dyt = length(dFdy(self.t))
if self.texture_index > 2.5 {
if self.slug_curve_count() < 0.5 {
return vec4(0.0, 0.0, 0.0, 0.0)
}
let dy = dFdy(self.pos)
let sample = self.pos - dFdy(self.pos) * 2.0 * step(0.5, self.down)
let px_x = max(abs(dFdx(self.pos.x)) + abs(dFdy(self.pos.x)), 0.00001)
let px_y = max(abs(dFdx(self.pos.y)) + abs(dFdy(self.pos.y)), 0.00001)
let grid = if self.aa_4x4 > 0.5 {4.0} else if self.aa_2x2 > 0.5 {2.0} else {1.0}
let inv_grid = if self.aa_4x4 > 0.5 {0.25} else if self.aa_2x2 > 0.5 {0.5} else {1.0}
var alpha_sum = 0.0
var sample_index = 0.0
loop {
if sample_index >= grid * grid { break }
let row = floor(sample_index * inv_grid)
let col = sample_index - row * grid
let offset = (vec2(col, row) + 0.5 - grid * 0.5) * inv_grid * vec2(px_x, px_y)
alpha_sum = alpha_sum + self.alpha_at(sample + offset, px_x, px_y)
sample_index = sample_index + 1.0
}
let alpha_base = clamp(alpha_sum * inv_grid * inv_grid, 0.0, 1.0)
let darken = clamp(max(px_x, px_y) * self.stem_darken, 0.0, self.stem_darken_max)
let edge_weight = clamp(1.0 - abs(alpha_base * 2.0 - 1.0), 0.0, 1.0)
let alpha = clamp(alpha_base + darken * edge_weight, 0.0, 1.0)
let c = self.get_color()
let main = vec4(c.rgb * c.a * alpha, c.a * alpha)
let s = clamp(self.alpha_at(sample - dy, px_x, px_y), 0.0, 1.0) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
if self.texture_index < 0.5 {
let tex_size = self.grayscale_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
if self.texture_index > 1.5 && self.texture_index < 2.5 {
let tex_size = self.msdf_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
return self.sample_text_pixel()
}
mod.widgets.ButtonSecondary.draw_text.pixel = fn() {
let nudge = dFdy(self.t.xy) * 2.0 * step(0.5, self.down)
let c = self.get_color()
let dxt = length(dFdx(self.t))
let dyt = length(dFdy(self.t))
if self.texture_index > 2.5 {
if self.slug_curve_count() < 0.5 {
return vec4(0.0, 0.0, 0.0, 0.0)
}
let dy = dFdy(self.pos)
let sample = self.pos - dFdy(self.pos) * 2.0 * step(0.5, self.down)
let px_x = max(abs(dFdx(self.pos.x)) + abs(dFdy(self.pos.x)), 0.00001)
let px_y = max(abs(dFdx(self.pos.y)) + abs(dFdy(self.pos.y)), 0.00001)
let grid = if self.aa_4x4 > 0.5 {4.0} else if self.aa_2x2 > 0.5 {2.0} else {1.0}
let inv_grid = if self.aa_4x4 > 0.5 {0.25} else if self.aa_2x2 > 0.5 {0.5} else {1.0}
var alpha_sum = 0.0
var sample_index = 0.0
loop {
if sample_index >= grid * grid { break }
let row = floor(sample_index * inv_grid)
let col = sample_index - row * grid
let offset = (vec2(col, row) + 0.5 - grid * 0.5) * inv_grid * vec2(px_x, px_y)
alpha_sum = alpha_sum + self.alpha_at(sample + offset, px_x, px_y)
sample_index = sample_index + 1.0
}
let alpha_base = clamp(alpha_sum * inv_grid * inv_grid, 0.0, 1.0)
let darken = clamp(max(px_x, px_y) * self.stem_darken, 0.0, self.stem_darken_max)
let edge_weight = clamp(1.0 - abs(alpha_base * 2.0 - 1.0), 0.0, 1.0)
let alpha = clamp(alpha_base + darken * edge_weight, 0.0, 1.0)
let c = self.get_color()
let main = vec4(c.rgb * c.a * alpha, c.a * alpha)
let s = clamp(self.alpha_at(sample - dy, px_x, px_y), 0.0, 1.0) * 0.45 * c.a
return main + vec4(1.0, 1.0, 0.9, 1.0) * s * (1.0 - main.a)
}
if self.texture_index < 0.5 {
let tex_size = self.grayscale_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) * 0.45 * c.a
return main + vec4(1.0, 1.0, 0.9, 1.0) * s * (1.0 - main.a)
}
if self.texture_index > 1.5 && self.texture_index < 2.5 {
let tex_size = self.msdf_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) * 0.45 * c.a
return main + vec4(1.0, 1.0, 0.9, 1.0) * s * (1.0 - main.a)
}
return self.sample_text_pixel()
}
mod.widgets.ButtonTertiary.draw_text.pixel = fn() {
let nudge = dFdy(self.t.xy) * 2.0 * step(0.5, self.down)
let c = self.get_color()
let dxt = length(dFdx(self.t))
let dyt = length(dFdy(self.t))
if self.texture_index > 2.5 {
if self.slug_curve_count() < 0.5 {
return vec4(0.0, 0.0, 0.0, 0.0)
}
let dy = dFdy(self.pos)
let sample = self.pos - dFdy(self.pos) * 2.0 * step(0.5, self.down)
let px_x = max(abs(dFdx(self.pos.x)) + abs(dFdy(self.pos.x)), 0.00001)
let px_y = max(abs(dFdx(self.pos.y)) + abs(dFdy(self.pos.y)), 0.00001)
let grid = if self.aa_4x4 > 0.5 {4.0} else if self.aa_2x2 > 0.5 {2.0} else {1.0}
let inv_grid = if self.aa_4x4 > 0.5 {0.25} else if self.aa_2x2 > 0.5 {0.5} else {1.0}
var alpha_sum = 0.0
var sample_index = 0.0
loop {
if sample_index >= grid * grid { break }
let row = floor(sample_index * inv_grid)
let col = sample_index - row * grid
let offset = (vec2(col, row) + 0.5 - grid * 0.5) * inv_grid * vec2(px_x, px_y)
alpha_sum = alpha_sum + self.alpha_at(sample + offset, px_x, px_y)
sample_index = sample_index + 1.0
}
let alpha_base = clamp(alpha_sum * inv_grid * inv_grid, 0.0, 1.0)
let darken = clamp(max(px_x, px_y) * self.stem_darken, 0.0, self.stem_darken_max)
let edge_weight = clamp(1.0 - abs(alpha_base * 2.0 - 1.0), 0.0, 1.0)
let alpha = clamp(alpha_base + darken * edge_weight, 0.0, 1.0)
let c = self.get_color()
let main = vec4(c.rgb * c.a * alpha, c.a * alpha)
let s = clamp(self.alpha_at(sample - dy, px_x, px_y), 0.0, 1.0) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
if self.texture_index < 0.5 {
let tex_size = self.grayscale_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
if self.texture_index > 1.5 && self.texture_index < 2.5 {
let tex_size = self.msdf_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
return self.sample_text_pixel()
}
mod.widgets.ButtonDanger.draw_text.pixel = fn() {
let nudge = dFdy(self.t.xy) * 2.0 * step(0.5, self.down)
let c = self.get_color()
let dxt = length(dFdx(self.t))
let dyt = length(dFdy(self.t))
if self.texture_index > 2.5 {
if self.slug_curve_count() < 0.5 {
return vec4(0.0, 0.0, 0.0, 0.0)
}
let dy = dFdy(self.pos)
let sample = self.pos - dFdy(self.pos) * 2.0 * step(0.5, self.down)
let px_x = max(abs(dFdx(self.pos.x)) + abs(dFdy(self.pos.x)), 0.00001)
let px_y = max(abs(dFdx(self.pos.y)) + abs(dFdy(self.pos.y)), 0.00001)
let grid = if self.aa_4x4 > 0.5 {4.0} else if self.aa_2x2 > 0.5 {2.0} else {1.0}
let inv_grid = if self.aa_4x4 > 0.5 {0.25} else if self.aa_2x2 > 0.5 {0.5} else {1.0}
var alpha_sum = 0.0
var sample_index = 0.0
loop {
if sample_index >= grid * grid { break }
let row = floor(sample_index * inv_grid)
let col = sample_index - row * grid
let offset = (vec2(col, row) + 0.5 - grid * 0.5) * inv_grid * vec2(px_x, px_y)
alpha_sum = alpha_sum + self.alpha_at(sample + offset, px_x, px_y)
sample_index = sample_index + 1.0
}
let alpha_base = clamp(alpha_sum * inv_grid * inv_grid, 0.0, 1.0)
let darken = clamp(max(px_x, px_y) * self.stem_darken, 0.0, self.stem_darken_max)
let edge_weight = clamp(1.0 - abs(alpha_base * 2.0 - 1.0), 0.0, 1.0)
let alpha = clamp(alpha_base + darken * edge_weight, 0.0, 1.0)
let c = self.get_color()
let main = vec4(c.rgb * c.a * alpha, c.a * alpha)
let s = clamp(self.alpha_at(sample - dy, px_x, px_y), 0.0, 1.0) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
if self.texture_index < 0.5 {
let tex_size = self.grayscale_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
if self.texture_index > 1.5 && self.texture_index < 2.5 {
let tex_size = self.msdf_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
return self.sample_text_pixel()
}
mod.widgets.ButtonOutline.draw_text.pixel = fn() {
let nudge = dFdy(self.t.xy) * 2.0 * step(0.5, self.down)
let c = self.get_color()
let dxt = length(dFdx(self.t))
let dyt = length(dFdy(self.t))
if self.texture_index > 2.5 {
if self.slug_curve_count() < 0.5 {
return vec4(0.0, 0.0, 0.0, 0.0)
}
let dy = dFdy(self.pos)
let sample = self.pos - dFdy(self.pos) * 2.0 * step(0.5, self.down)
let px_x = max(abs(dFdx(self.pos.x)) + abs(dFdy(self.pos.x)), 0.00001)
let px_y = max(abs(dFdx(self.pos.y)) + abs(dFdy(self.pos.y)), 0.00001)
let grid = if self.aa_4x4 > 0.5 {4.0} else if self.aa_2x2 > 0.5 {2.0} else {1.0}
let inv_grid = if self.aa_4x4 > 0.5 {0.25} else if self.aa_2x2 > 0.5 {0.5} else {1.0}
var alpha_sum = 0.0
var sample_index = 0.0
loop {
if sample_index >= grid * grid { break }
let row = floor(sample_index * inv_grid)
let col = sample_index - row * grid
let offset = (vec2(col, row) + 0.5 - grid * 0.5) * inv_grid * vec2(px_x, px_y)
alpha_sum = alpha_sum + self.alpha_at(sample + offset, px_x, px_y)
sample_index = sample_index + 1.0
}
let alpha_base = clamp(alpha_sum * inv_grid * inv_grid, 0.0, 1.0)
let darken = clamp(max(px_x, px_y) * self.stem_darken, 0.0, self.stem_darken_max)
let edge_weight = clamp(1.0 - abs(alpha_base * 2.0 - 1.0), 0.0, 1.0)
let alpha = clamp(alpha_base + darken * edge_weight, 0.0, 1.0)
let c = self.get_color()
let main = vec4(c.rgb * c.a * alpha, c.a * alpha)
let s = clamp(self.alpha_at(sample - dy, px_x, px_y), 0.0, 1.0) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
if self.texture_index < 0.5 {
let tex_size = self.grayscale_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
if self.texture_index > 1.5 && self.texture_index < 2.5 {
let tex_size = self.msdf_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
return self.sample_text_pixel()
}
mod.widgets.ButtonFlatter.draw_text.pixel = fn() {
let nudge = dFdy(self.t.xy) * 2.0 * step(0.5, self.down)
let c = self.get_color()
let dxt = length(dFdx(self.t))
let dyt = length(dFdy(self.t))
if self.texture_index > 2.5 {
if self.slug_curve_count() < 0.5 {
return vec4(0.0, 0.0, 0.0, 0.0)
}
let dy = dFdy(self.pos)
let sample = self.pos - dFdy(self.pos) * 2.0 * step(0.5, self.down)
let px_x = max(abs(dFdx(self.pos.x)) + abs(dFdy(self.pos.x)), 0.00001)
let px_y = max(abs(dFdx(self.pos.y)) + abs(dFdy(self.pos.y)), 0.00001)
let grid = if self.aa_4x4 > 0.5 {4.0} else if self.aa_2x2 > 0.5 {2.0} else {1.0}
let inv_grid = if self.aa_4x4 > 0.5 {0.25} else if self.aa_2x2 > 0.5 {0.5} else {1.0}
var alpha_sum = 0.0
var sample_index = 0.0
loop {
if sample_index >= grid * grid { break }
let row = floor(sample_index * inv_grid)
let col = sample_index - row * grid
let offset = (vec2(col, row) + 0.5 - grid * 0.5) * inv_grid * vec2(px_x, px_y)
alpha_sum = alpha_sum + self.alpha_at(sample + offset, px_x, px_y)
sample_index = sample_index + 1.0
}
let alpha_base = clamp(alpha_sum * inv_grid * inv_grid, 0.0, 1.0)
let darken = clamp(max(px_x, px_y) * self.stem_darken, 0.0, self.stem_darken_max)
let edge_weight = clamp(1.0 - abs(alpha_base * 2.0 - 1.0), 0.0, 1.0)
let alpha = clamp(alpha_base + darken * edge_weight, 0.0, 1.0)
let c = self.get_color()
let main = vec4(c.rgb * c.a * alpha, c.a * alpha)
let s = clamp(self.alpha_at(sample - dy, px_x, px_y), 0.0, 1.0) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
if self.texture_index < 0.5 {
let tex_size = self.grayscale_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
if self.texture_index > 1.5 && self.texture_index < 2.5 {
let tex_size = self.msdf_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
return self.sample_text_pixel()
}
mod.widgets.ButtonXs.draw_text.pixel = fn() {
let nudge = dFdy(self.t.xy) * 2.0 * step(0.5, self.down)
let c = self.get_color()
let dxt = length(dFdx(self.t))
let dyt = length(dFdy(self.t))
if self.texture_index > 2.5 {
if self.slug_curve_count() < 0.5 {
return vec4(0.0, 0.0, 0.0, 0.0)
}
let dy = dFdy(self.pos)
let sample = self.pos - dFdy(self.pos) * 2.0 * step(0.5, self.down)
let px_x = max(abs(dFdx(self.pos.x)) + abs(dFdy(self.pos.x)), 0.00001)
let px_y = max(abs(dFdx(self.pos.y)) + abs(dFdy(self.pos.y)), 0.00001)
let grid = if self.aa_4x4 > 0.5 {4.0} else if self.aa_2x2 > 0.5 {2.0} else {1.0}
let inv_grid = if self.aa_4x4 > 0.5 {0.25} else if self.aa_2x2 > 0.5 {0.5} else {1.0}
var alpha_sum = 0.0
var sample_index = 0.0
loop {
if sample_index >= grid * grid { break }
let row = floor(sample_index * inv_grid)
let col = sample_index - row * grid
let offset = (vec2(col, row) + 0.5 - grid * 0.5) * inv_grid * vec2(px_x, px_y)
alpha_sum = alpha_sum + self.alpha_at(sample + offset, px_x, px_y)
sample_index = sample_index + 1.0
}
let alpha_base = clamp(alpha_sum * inv_grid * inv_grid, 0.0, 1.0)
let darken = clamp(max(px_x, px_y) * self.stem_darken, 0.0, self.stem_darken_max)
let edge_weight = clamp(1.0 - abs(alpha_base * 2.0 - 1.0), 0.0, 1.0)
let alpha = clamp(alpha_base + darken * edge_weight, 0.0, 1.0)
let c = self.get_color()
let main = vec4(c.rgb * c.a * alpha, c.a * alpha)
let s = clamp(self.alpha_at(sample - dy, px_x, px_y), 0.0, 1.0) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
if self.texture_index < 0.5 {
let tex_size = self.grayscale_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
if self.texture_index > 1.5 && self.texture_index < 2.5 {
let tex_size = self.msdf_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
return self.sample_text_pixel()
}
mod.widgets.ButtonSm.draw_text.pixel = fn() {
let nudge = dFdy(self.t.xy) * 2.0 * step(0.5, self.down)
let c = self.get_color()
let dxt = length(dFdx(self.t))
let dyt = length(dFdy(self.t))
if self.texture_index > 2.5 {
if self.slug_curve_count() < 0.5 {
return vec4(0.0, 0.0, 0.0, 0.0)
}
let dy = dFdy(self.pos)
let sample = self.pos - dFdy(self.pos) * 2.0 * step(0.5, self.down)
let px_x = max(abs(dFdx(self.pos.x)) + abs(dFdy(self.pos.x)), 0.00001)
let px_y = max(abs(dFdx(self.pos.y)) + abs(dFdy(self.pos.y)), 0.00001)
let grid = if self.aa_4x4 > 0.5 {4.0} else if self.aa_2x2 > 0.5 {2.0} else {1.0}
let inv_grid = if self.aa_4x4 > 0.5 {0.25} else if self.aa_2x2 > 0.5 {0.5} else {1.0}
var alpha_sum = 0.0
var sample_index = 0.0
loop {
if sample_index >= grid * grid { break }
let row = floor(sample_index * inv_grid)
let col = sample_index - row * grid
let offset = (vec2(col, row) + 0.5 - grid * 0.5) * inv_grid * vec2(px_x, px_y)
alpha_sum = alpha_sum + self.alpha_at(sample + offset, px_x, px_y)
sample_index = sample_index + 1.0
}
let alpha_base = clamp(alpha_sum * inv_grid * inv_grid, 0.0, 1.0)
let darken = clamp(max(px_x, px_y) * self.stem_darken, 0.0, self.stem_darken_max)
let edge_weight = clamp(1.0 - abs(alpha_base * 2.0 - 1.0), 0.0, 1.0)
let alpha = clamp(alpha_base + darken * edge_weight, 0.0, 1.0)
let c = self.get_color()
let main = vec4(c.rgb * c.a * alpha, c.a * alpha)
let s = clamp(self.alpha_at(sample - dy, px_x, px_y), 0.0, 1.0) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
if self.texture_index < 0.5 {
let tex_size = self.grayscale_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
if self.texture_index > 1.5 && self.texture_index < 2.5 {
let tex_size = self.msdf_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
return self.sample_text_pixel()
}
mod.widgets.ButtonLg.draw_text.pixel = fn() {
let nudge = dFdy(self.t.xy) * 2.0 * step(0.5, self.down)
let c = self.get_color()
let dxt = length(dFdx(self.t))
let dyt = length(dFdy(self.t))
if self.texture_index > 2.5 {
if self.slug_curve_count() < 0.5 {
return vec4(0.0, 0.0, 0.0, 0.0)
}
let dy = dFdy(self.pos)
let sample = self.pos - dFdy(self.pos) * 2.0 * step(0.5, self.down)
let px_x = max(abs(dFdx(self.pos.x)) + abs(dFdy(self.pos.x)), 0.00001)
let px_y = max(abs(dFdx(self.pos.y)) + abs(dFdy(self.pos.y)), 0.00001)
let grid = if self.aa_4x4 > 0.5 {4.0} else if self.aa_2x2 > 0.5 {2.0} else {1.0}
let inv_grid = if self.aa_4x4 > 0.5 {0.25} else if self.aa_2x2 > 0.5 {0.5} else {1.0}
var alpha_sum = 0.0
var sample_index = 0.0
loop {
if sample_index >= grid * grid { break }
let row = floor(sample_index * inv_grid)
let col = sample_index - row * grid
let offset = (vec2(col, row) + 0.5 - grid * 0.5) * inv_grid * vec2(px_x, px_y)
alpha_sum = alpha_sum + self.alpha_at(sample + offset, px_x, px_y)
sample_index = sample_index + 1.0
}
let alpha_base = clamp(alpha_sum * inv_grid * inv_grid, 0.0, 1.0)
let darken = clamp(max(px_x, px_y) * self.stem_darken, 0.0, self.stem_darken_max)
let edge_weight = clamp(1.0 - abs(alpha_base * 2.0 - 1.0), 0.0, 1.0)
let alpha = clamp(alpha_base + darken * edge_weight, 0.0, 1.0)
let c = self.get_color()
let main = vec4(c.rgb * c.a * alpha, c.a * alpha)
let s = clamp(self.alpha_at(sample - dy, px_x, px_y), 0.0, 1.0) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
if self.texture_index < 0.5 {
let tex_size = self.grayscale_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
if self.texture_index > 1.5 && self.texture_index < 2.5 {
let tex_size = self.msdf_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
return self.sample_text_pixel()
}
mod.widgets.ButtonXl.draw_text.pixel = fn() {
let nudge = dFdy(self.t.xy) * 2.0 * step(0.5, self.down)
let c = self.get_color()
let dxt = length(dFdx(self.t))
let dyt = length(dFdy(self.t))
if self.texture_index > 2.5 {
if self.slug_curve_count() < 0.5 {
return vec4(0.0, 0.0, 0.0, 0.0)
}
let dy = dFdy(self.pos)
let sample = self.pos - dFdy(self.pos) * 2.0 * step(0.5, self.down)
let px_x = max(abs(dFdx(self.pos.x)) + abs(dFdy(self.pos.x)), 0.00001)
let px_y = max(abs(dFdx(self.pos.y)) + abs(dFdy(self.pos.y)), 0.00001)
let grid = if self.aa_4x4 > 0.5 {4.0} else if self.aa_2x2 > 0.5 {2.0} else {1.0}
let inv_grid = if self.aa_4x4 > 0.5 {0.25} else if self.aa_2x2 > 0.5 {0.5} else {1.0}
var alpha_sum = 0.0
var sample_index = 0.0
loop {
if sample_index >= grid * grid { break }
let row = floor(sample_index * inv_grid)
let col = sample_index - row * grid
let offset = (vec2(col, row) + 0.5 - grid * 0.5) * inv_grid * vec2(px_x, px_y)
alpha_sum = alpha_sum + self.alpha_at(sample + offset, px_x, px_y)
sample_index = sample_index + 1.0
}
let alpha_base = clamp(alpha_sum * inv_grid * inv_grid, 0.0, 1.0)
let darken = clamp(max(px_x, px_y) * self.stem_darken, 0.0, self.stem_darken_max)
let edge_weight = clamp(1.0 - abs(alpha_base * 2.0 - 1.0), 0.0, 1.0)
let alpha = clamp(alpha_base + darken * edge_weight, 0.0, 1.0)
let c = self.get_color()
let main = vec4(c.rgb * c.a * alpha, c.a * alpha)
let s = clamp(self.alpha_at(sample - dy, px_x, px_y), 0.0, 1.0) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
if self.texture_index < 0.5 {
let tex_size = self.grayscale_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
if self.texture_index > 1.5 && self.texture_index < 2.5 {
let tex_size = self.msdf_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
return self.sample_text_pixel()
}
mod.widgets.Label.draw_text.pixel = fn() {
let nudge = vec2(0.0, 0.0)
let c = self.get_color()
let dxt = length(dFdx(self.t))
let dyt = length(dFdy(self.t))
if self.texture_index > 2.5 {
if self.slug_curve_count() < 0.5 {
return vec4(0.0, 0.0, 0.0, 0.0)
}
let dy = dFdy(self.pos)
let sample = self.pos - vec2(0.0, 0.0)
let px_x = max(abs(dFdx(self.pos.x)) + abs(dFdy(self.pos.x)), 0.00001)
let px_y = max(abs(dFdx(self.pos.y)) + abs(dFdy(self.pos.y)), 0.00001)
let grid = if self.aa_4x4 > 0.5 {4.0} else if self.aa_2x2 > 0.5 {2.0} else {1.0}
let inv_grid = if self.aa_4x4 > 0.5 {0.25} else if self.aa_2x2 > 0.5 {0.5} else {1.0}
var alpha_sum = 0.0
var sample_index = 0.0
loop {
if sample_index >= grid * grid { break }
let row = floor(sample_index * inv_grid)
let col = sample_index - row * grid
let offset = (vec2(col, row) + 0.5 - grid * 0.5) * inv_grid * vec2(px_x, px_y)
alpha_sum = alpha_sum + self.alpha_at(sample + offset, px_x, px_y)
sample_index = sample_index + 1.0
}
let alpha_base = clamp(alpha_sum * inv_grid * inv_grid, 0.0, 1.0)
let darken = clamp(max(px_x, px_y) * self.stem_darken, 0.0, self.stem_darken_max)
let edge_weight = clamp(1.0 - abs(alpha_base * 2.0 - 1.0), 0.0, 1.0)
let alpha = clamp(alpha_base + darken * edge_weight, 0.0, 1.0)
let c = self.get_color()
let main = vec4(c.rgb * c.a * alpha, c.a * alpha)
let s = clamp(self.alpha_at(sample - dy, px_x, px_y), 0.0, 1.0) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
if self.texture_index < 0.5 {
let tex_size = self.grayscale_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
if self.texture_index > 1.5 && self.texture_index < 2.5 {
let tex_size = self.msdf_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
return self.sample_text_pixel()
}
mod.widgets.CheckBoxFlat.draw_text.pixel = fn() {
let nudge = vec2(0.0, 0.0)
let c = self.get_color()
let dxt = length(dFdx(self.t))
let dyt = length(dFdy(self.t))
if self.texture_index > 2.5 {
if self.slug_curve_count() < 0.5 {
return vec4(0.0, 0.0, 0.0, 0.0)
}
let dy = dFdy(self.pos)
let sample = self.pos - vec2(0.0, 0.0)
let px_x = max(abs(dFdx(self.pos.x)) + abs(dFdy(self.pos.x)), 0.00001)
let px_y = max(abs(dFdx(self.pos.y)) + abs(dFdy(self.pos.y)), 0.00001)
let grid = if self.aa_4x4 > 0.5 {4.0} else if self.aa_2x2 > 0.5 {2.0} else {1.0}
let inv_grid = if self.aa_4x4 > 0.5 {0.25} else if self.aa_2x2 > 0.5 {0.5} else {1.0}
var alpha_sum = 0.0
var sample_index = 0.0
loop {
if sample_index >= grid * grid { break }
let row = floor(sample_index * inv_grid)
let col = sample_index - row * grid
let offset = (vec2(col, row) + 0.5 - grid * 0.5) * inv_grid * vec2(px_x, px_y)
alpha_sum = alpha_sum + self.alpha_at(sample + offset, px_x, px_y)
sample_index = sample_index + 1.0
}
let alpha_base = clamp(alpha_sum * inv_grid * inv_grid, 0.0, 1.0)
let darken = clamp(max(px_x, px_y) * self.stem_darken, 0.0, self.stem_darken_max)
let edge_weight = clamp(1.0 - abs(alpha_base * 2.0 - 1.0), 0.0, 1.0)
let alpha = clamp(alpha_base + darken * edge_weight, 0.0, 1.0)
let c = self.get_color()
let main = vec4(c.rgb * c.a * alpha, c.a * alpha)
let s = clamp(self.alpha_at(sample - dy, px_x, px_y), 0.0, 1.0) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
if self.texture_index < 0.5 {
let tex_size = self.grayscale_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
if self.texture_index > 1.5 && self.texture_index < 2.5 {
let tex_size = self.msdf_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
return self.sample_text_pixel()
}
mod.widgets.RadioButtonFlat.draw_text.pixel = fn() {
let nudge = vec2(0.0, 0.0)
let c = self.get_color()
let dxt = length(dFdx(self.t))
let dyt = length(dFdy(self.t))
if self.texture_index > 2.5 {
if self.slug_curve_count() < 0.5 {
return vec4(0.0, 0.0, 0.0, 0.0)
}
let dy = dFdy(self.pos)
let sample = self.pos - vec2(0.0, 0.0)
let px_x = max(abs(dFdx(self.pos.x)) + abs(dFdy(self.pos.x)), 0.00001)
let px_y = max(abs(dFdx(self.pos.y)) + abs(dFdy(self.pos.y)), 0.00001)
let grid = if self.aa_4x4 > 0.5 {4.0} else if self.aa_2x2 > 0.5 {2.0} else {1.0}
let inv_grid = if self.aa_4x4 > 0.5 {0.25} else if self.aa_2x2 > 0.5 {0.5} else {1.0}
var alpha_sum = 0.0
var sample_index = 0.0
loop {
if sample_index >= grid * grid { break }
let row = floor(sample_index * inv_grid)
let col = sample_index - row * grid
let offset = (vec2(col, row) + 0.5 - grid * 0.5) * inv_grid * vec2(px_x, px_y)
alpha_sum = alpha_sum + self.alpha_at(sample + offset, px_x, px_y)
sample_index = sample_index + 1.0
}
let alpha_base = clamp(alpha_sum * inv_grid * inv_grid, 0.0, 1.0)
let darken = clamp(max(px_x, px_y) * self.stem_darken, 0.0, self.stem_darken_max)
let edge_weight = clamp(1.0 - abs(alpha_base * 2.0 - 1.0), 0.0, 1.0)
let alpha = clamp(alpha_base + darken * edge_weight, 0.0, 1.0)
let c = self.get_color()
let main = vec4(c.rgb * c.a * alpha, c.a * alpha)
let s = clamp(self.alpha_at(sample - dy, px_x, px_y), 0.0, 1.0) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
if self.texture_index < 0.5 {
let tex_size = self.grayscale_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
if self.texture_index > 1.5 && self.texture_index < 2.5 {
let tex_size = self.msdf_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
return self.sample_text_pixel()
}
mod.widgets.DropDownFlat.draw_text.pixel = fn() {
let nudge = vec2(0.0, 0.0)
let c = self.get_color()
let dxt = length(dFdx(self.t))
let dyt = length(dFdy(self.t))
if self.texture_index > 2.5 {
if self.slug_curve_count() < 0.5 {
return vec4(0.0, 0.0, 0.0, 0.0)
}
let dy = dFdy(self.pos)
let sample = self.pos - vec2(0.0, 0.0)
let px_x = max(abs(dFdx(self.pos.x)) + abs(dFdy(self.pos.x)), 0.00001)
let px_y = max(abs(dFdx(self.pos.y)) + abs(dFdy(self.pos.y)), 0.00001)
let grid = if self.aa_4x4 > 0.5 {4.0} else if self.aa_2x2 > 0.5 {2.0} else {1.0}
let inv_grid = if self.aa_4x4 > 0.5 {0.25} else if self.aa_2x2 > 0.5 {0.5} else {1.0}
var alpha_sum = 0.0
var sample_index = 0.0
loop {
if sample_index >= grid * grid { break }
let row = floor(sample_index * inv_grid)
let col = sample_index - row * grid
let offset = (vec2(col, row) + 0.5 - grid * 0.5) * inv_grid * vec2(px_x, px_y)
alpha_sum = alpha_sum + self.alpha_at(sample + offset, px_x, px_y)
sample_index = sample_index + 1.0
}
let alpha_base = clamp(alpha_sum * inv_grid * inv_grid, 0.0, 1.0)
let darken = clamp(max(px_x, px_y) * self.stem_darken, 0.0, self.stem_darken_max)
let edge_weight = clamp(1.0 - abs(alpha_base * 2.0 - 1.0), 0.0, 1.0)
let alpha = clamp(alpha_base + darken * edge_weight, 0.0, 1.0)
let c = self.get_color()
let main = vec4(c.rgb * c.a * alpha, c.a * alpha)
let s = clamp(self.alpha_at(sample - dy, px_x, px_y), 0.0, 1.0) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
if self.texture_index < 0.5 {
let tex_size = self.grayscale_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
if self.texture_index > 1.5 && self.texture_index < 2.5 {
let tex_size = self.msdf_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
return self.sample_text_pixel()
}
mod.widgets.SegmentedControlFlat.draw_text.pixel = fn() {
let nudge = vec2(0.0, 0.0)
let c = self.get_color()
let dxt = length(dFdx(self.t))
let dyt = length(dFdy(self.t))
if self.texture_index > 2.5 {
if self.slug_curve_count() < 0.5 {
return vec4(0.0, 0.0, 0.0, 0.0)
}
let dy = dFdy(self.pos)
let sample = self.pos - vec2(0.0, 0.0)
let px_x = max(abs(dFdx(self.pos.x)) + abs(dFdy(self.pos.x)), 0.00001)
let px_y = max(abs(dFdx(self.pos.y)) + abs(dFdy(self.pos.y)), 0.00001)
let grid = if self.aa_4x4 > 0.5 {4.0} else if self.aa_2x2 > 0.5 {2.0} else {1.0}
let inv_grid = if self.aa_4x4 > 0.5 {0.25} else if self.aa_2x2 > 0.5 {0.5} else {1.0}
var alpha_sum = 0.0
var sample_index = 0.0
loop {
if sample_index >= grid * grid { break }
let row = floor(sample_index * inv_grid)
let col = sample_index - row * grid
let offset = (vec2(col, row) + 0.5 - grid * 0.5) * inv_grid * vec2(px_x, px_y)
alpha_sum = alpha_sum + self.alpha_at(sample + offset, px_x, px_y)
sample_index = sample_index + 1.0
}
let alpha_base = clamp(alpha_sum * inv_grid * inv_grid, 0.0, 1.0)
let darken = clamp(max(px_x, px_y) * self.stem_darken, 0.0, self.stem_darken_max)
let edge_weight = clamp(1.0 - abs(alpha_base * 2.0 - 1.0), 0.0, 1.0)
let alpha = clamp(alpha_base + darken * edge_weight, 0.0, 1.0)
let c = self.get_color()
let main = vec4(c.rgb * c.a * alpha, c.a * alpha)
let s = clamp(self.alpha_at(sample - dy, px_x, px_y), 0.0, 1.0) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
if self.texture_index < 0.5 {
let tex_size = self.grayscale_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
if self.texture_index > 1.5 && self.texture_index < 2.5 {
let tex_size = self.msdf_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
return self.sample_text_pixel()
}
mod.widgets.SegmentedControlFlat.draw_text_selected.pixel = fn() {
let main = self.sample_text_pixel()
if self.texture_index > 2.5 {
if self.slug_curve_count() < 0.5 {
return vec4(0.0, 0.0, 0.0, 0.0)
}
let px_x = max(abs(dFdx(self.pos.x)) + abs(dFdy(self.pos.x)), 0.00001)
let px_y = max(abs(dFdx(self.pos.y)) + abs(dFdy(self.pos.y)), 0.00001)
let ex = dFdx(self.pos) * 1.5
let ey = dFdy(self.pos) * 1.5
var g = self.alpha_at(self.pos + ex, px_x, px_y)
g = g + self.alpha_at(self.pos - ex, px_x, px_y)
g = g + self.alpha_at(self.pos + ey, px_x, px_y)
g = g + self.alpha_at(self.pos - ey, px_x, px_y)
let lit = vec4(0.70, 0.88, 1.0, self.color.a)
let cov = clamp(self.alpha_at(self.pos, px_x, px_y), 0.0, 1.0)
let lm = vec4(lit.rgb * lit.a * cov, lit.a * cov)
let a = clamp(g * 0.25, 0.0, 1.0) * 0.6 * lit.a
return lm + vec4(0.35 * a, 0.62 * a, 1.0 * a, a) * (1.0 - lm.a)
}
if self.texture_index < 0.5 {
let dxt = length(dFdx(self.t))
let dyt = length(dFdy(self.t))
let tex_size = self.grayscale_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let ex = dFdx(self.t.xy) * 1.5
let ey = dFdy(self.t.xy) * 1.5
let k = vec4(0.0, 0.0, 0.0, 1.0)
var g = self.sdf(scale, clamp(self.t.xy + ex, self.t_min + half_texel, self.t_max - half_texel), k)
g = g + self.sdf(scale, clamp(self.t.xy - ex, self.t_min + half_texel, self.t_max - half_texel), k)
g = g + self.sdf(scale, clamp(self.t.xy + ey, self.t_min + half_texel, self.t_max - half_texel), k)
g = g + self.sdf(scale, clamp(self.t.xy - ey, self.t_min + half_texel, self.t_max - half_texel), k)
let c = self.get_color()
let a = clamp(g * 0.25, 0.0, 1.0) * 0.5 * c.a
return main + vec4(0.35 * a, 0.62 * a, 1.0 * a, a) * (1.0 - main.a)
}
if self.texture_index > 1.5 && self.texture_index < 2.5 {
let dxt = length(dFdx(self.t))
let dyt = length(dFdy(self.t))
let tex_size = self.msdf_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let ex = dFdx(self.t.xy) * 1.5
let ey = dFdy(self.t.xy) * 1.5
let k = vec4(0.0, 0.0, 0.0, 1.0)
var g = self.msdf(scale, clamp(self.t.xy + ex, self.t_min + half_texel, self.t_max - half_texel), k)
g = g + self.msdf(scale, clamp(self.t.xy - ex, self.t_min + half_texel, self.t_max - half_texel), k)
g = g + self.msdf(scale, clamp(self.t.xy + ey, self.t_min + half_texel, self.t_max - half_texel), k)
g = g + self.msdf(scale, clamp(self.t.xy - ey, self.t_min + half_texel, self.t_max - half_texel), k)
let c = self.get_color()
let a = clamp(g * 0.25, 0.0, 1.0) * 0.5 * c.a
return main + vec4(0.35 * a, 0.62 * a, 1.0 * a, a) * (1.0 - main.a)
}
return main
}
mod.widgets.Tabs.draw_text.pixel = fn() {
let nudge = vec2(0.0, 0.0)
let c = self.get_color()
let dxt = length(dFdx(self.t))
let dyt = length(dFdy(self.t))
if self.texture_index > 2.5 {
if self.slug_curve_count() < 0.5 {
return vec4(0.0, 0.0, 0.0, 0.0)
}
let dy = dFdy(self.pos)
let sample = self.pos - vec2(0.0, 0.0)
let px_x = max(abs(dFdx(self.pos.x)) + abs(dFdy(self.pos.x)), 0.00001)
let px_y = max(abs(dFdx(self.pos.y)) + abs(dFdy(self.pos.y)), 0.00001)
let grid = if self.aa_4x4 > 0.5 {4.0} else if self.aa_2x2 > 0.5 {2.0} else {1.0}
let inv_grid = if self.aa_4x4 > 0.5 {0.25} else if self.aa_2x2 > 0.5 {0.5} else {1.0}
var alpha_sum = 0.0
var sample_index = 0.0
loop {
if sample_index >= grid * grid { break }
let row = floor(sample_index * inv_grid)
let col = sample_index - row * grid
let offset = (vec2(col, row) + 0.5 - grid * 0.5) * inv_grid * vec2(px_x, px_y)
alpha_sum = alpha_sum + self.alpha_at(sample + offset, px_x, px_y)
sample_index = sample_index + 1.0
}
let alpha_base = clamp(alpha_sum * inv_grid * inv_grid, 0.0, 1.0)
let darken = clamp(max(px_x, px_y) * self.stem_darken, 0.0, self.stem_darken_max)
let edge_weight = clamp(1.0 - abs(alpha_base * 2.0 - 1.0), 0.0, 1.0)
let alpha = clamp(alpha_base + darken * edge_weight, 0.0, 1.0)
let c = self.get_color()
let main = vec4(c.rgb * c.a * alpha, c.a * alpha)
let s = clamp(self.alpha_at(sample - dy, px_x, px_y), 0.0, 1.0) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
if self.texture_index < 0.5 {
let tex_size = self.grayscale_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
if self.texture_index > 1.5 && self.texture_index < 2.5 {
let tex_size = self.msdf_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
return self.sample_text_pixel()
}
mod.widgets.Tabs.draw_text_active.pixel = fn() {
let main = self.sample_text_pixel()
if self.texture_index > 2.5 {
if self.slug_curve_count() < 0.5 {
return vec4(0.0, 0.0, 0.0, 0.0)
}
let px_x = max(abs(dFdx(self.pos.x)) + abs(dFdy(self.pos.x)), 0.00001)
let px_y = max(abs(dFdx(self.pos.y)) + abs(dFdy(self.pos.y)), 0.00001)
let ex = dFdx(self.pos) * 1.5
let ey = dFdy(self.pos) * 1.5
var g = self.alpha_at(self.pos + ex, px_x, px_y)
g = g + self.alpha_at(self.pos - ex, px_x, px_y)
g = g + self.alpha_at(self.pos + ey, px_x, px_y)
g = g + self.alpha_at(self.pos - ey, px_x, px_y)
let lit = vec4(0.70, 0.88, 1.0, self.color.a)
let cov = clamp(self.alpha_at(self.pos, px_x, px_y), 0.0, 1.0)
let lm = vec4(lit.rgb * lit.a * cov, lit.a * cov)
let a = clamp(g * 0.25, 0.0, 1.0) * 0.6 * lit.a
return lm + vec4(0.35 * a, 0.62 * a, 1.0 * a, a) * (1.0 - lm.a)
}
if self.texture_index < 0.5 {
let dxt = length(dFdx(self.t))
let dyt = length(dFdy(self.t))
let tex_size = self.grayscale_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let ex = dFdx(self.t.xy) * 1.5
let ey = dFdy(self.t.xy) * 1.5
let k = vec4(0.0, 0.0, 0.0, 1.0)
var g = self.sdf(scale, clamp(self.t.xy + ex, self.t_min + half_texel, self.t_max - half_texel), k)
g = g + self.sdf(scale, clamp(self.t.xy - ex, self.t_min + half_texel, self.t_max - half_texel), k)
g = g + self.sdf(scale, clamp(self.t.xy + ey, self.t_min + half_texel, self.t_max - half_texel), k)
g = g + self.sdf(scale, clamp(self.t.xy - ey, self.t_min + half_texel, self.t_max - half_texel), k)
let c = self.get_color()
let a = clamp(g * 0.25, 0.0, 1.0) * 0.5 * c.a
return main + vec4(0.35 * a, 0.62 * a, 1.0 * a, a) * (1.0 - main.a)
}
if self.texture_index > 1.5 && self.texture_index < 2.5 {
let dxt = length(dFdx(self.t))
let dyt = length(dFdy(self.t))
let tex_size = self.msdf_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let ex = dFdx(self.t.xy) * 1.5
let ey = dFdy(self.t.xy) * 1.5
let k = vec4(0.0, 0.0, 0.0, 1.0)
var g = self.msdf(scale, clamp(self.t.xy + ex, self.t_min + half_texel, self.t_max - half_texel), k)
g = g + self.msdf(scale, clamp(self.t.xy - ex, self.t_min + half_texel, self.t_max - half_texel), k)
g = g + self.msdf(scale, clamp(self.t.xy + ey, self.t_min + half_texel, self.t_max - half_texel), k)
g = g + self.msdf(scale, clamp(self.t.xy - ey, self.t_min + half_texel, self.t_max - half_texel), k)
let c = self.get_color()
let a = clamp(g * 0.25, 0.0, 1.0) * 0.5 * c.a
return main + vec4(0.35 * a, 0.62 * a, 1.0 * a, a) * (1.0 - main.a)
}
return main
}
mod.widgets.SliderMinimal.draw_text.pixel = fn() {
let nudge = vec2(0.0, 0.0)
let c = self.get_color()
let dxt = length(dFdx(self.t))
let dyt = length(dFdy(self.t))
if self.texture_index > 2.5 {
if self.slug_curve_count() < 0.5 {
return vec4(0.0, 0.0, 0.0, 0.0)
}
let dy = dFdy(self.pos)
let sample = self.pos - vec2(0.0, 0.0)
let px_x = max(abs(dFdx(self.pos.x)) + abs(dFdy(self.pos.x)), 0.00001)
let px_y = max(abs(dFdx(self.pos.y)) + abs(dFdy(self.pos.y)), 0.00001)
let grid = if self.aa_4x4 > 0.5 {4.0} else if self.aa_2x2 > 0.5 {2.0} else {1.0}
let inv_grid = if self.aa_4x4 > 0.5 {0.25} else if self.aa_2x2 > 0.5 {0.5} else {1.0}
var alpha_sum = 0.0
var sample_index = 0.0
loop {
if sample_index >= grid * grid { break }
let row = floor(sample_index * inv_grid)
let col = sample_index - row * grid
let offset = (vec2(col, row) + 0.5 - grid * 0.5) * inv_grid * vec2(px_x, px_y)
alpha_sum = alpha_sum + self.alpha_at(sample + offset, px_x, px_y)
sample_index = sample_index + 1.0
}
let alpha_base = clamp(alpha_sum * inv_grid * inv_grid, 0.0, 1.0)
let darken = clamp(max(px_x, px_y) * self.stem_darken, 0.0, self.stem_darken_max)
let edge_weight = clamp(1.0 - abs(alpha_base * 2.0 - 1.0), 0.0, 1.0)
let alpha = clamp(alpha_base + darken * edge_weight, 0.0, 1.0)
let c = self.get_color()
let main = vec4(c.rgb * c.a * alpha, c.a * alpha)
let s = clamp(self.alpha_at(sample - dy, px_x, px_y), 0.0, 1.0) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
if self.texture_index < 0.5 {
let tex_size = self.grayscale_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
if self.texture_index > 1.5 && self.texture_index < 2.5 {
let tex_size = self.msdf_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) * 0.55 * c.a
return main + vec4(0.02, 0.05, 0.16, 1.0) * s * (1.0 - main.a)
}
return self.sample_text_pixel()
}
// ---------------------------------------------------------------- field row
// A ROW OF FIELDS IS ONE HEIGHT. Left to themselves the library's fields each
// come out at a size of their own, so the box metrics are stated ONCE here
// and handed to each field below; a field added to the library later joins
// the row by being named here.
let field_height = 24.0
let field_pad_v = 3.0
let field_radius = 2.5
let field_border = 0.67
// The room a Fit field takes: a minimum height, and the padding with it.
let field_room = fn(w) {
w.min_height = field_height
w.padding.top = field_pad_v
w.padding.bottom = field_pad_v + 1.0
}
// The box a field draws, given the draw object.
let field_face = fn(d) {
d.border_radius = field_radius
d.border_size = field_border
}
// A field that states a frame and lays its own parts out inside it: the
// height is all these take.
let field_frame = fn(w) {
w.min_height = field_height
}
field_room(mod.widgets.TextInput)
field_face(mod.widgets.TextInput.draw_bg)
field_room(mod.widgets.ComboBox)
field_face(mod.widgets.ComboBox.draw_bg)
field_room(mod.widgets.DropDown)
field_face(mod.widgets.DropDown.draw_bg)
field_room(mod.widgets.DropDown2)
field_face(mod.widgets.DropDown2.draw_bg)
field_room(mod.widgets.FieldWell)
field_face(mod.widgets.FieldWell.draw_bg)
field_room(mod.widgets.TagField)
field_face(mod.widgets.TagField.draw_bg)
field_frame(mod.widgets.NumberField)
mod.widgets.NumberField.draw_bg.border_radius = field_radius
field_frame(mod.widgets.ValueInput)
field_frame(mod.widgets.DateField)
field_frame(mod.widgets.TimeField)
field_frame(mod.widgets.DatePicker)
field_frame(mod.widgets.DateRangePicker)
mod.widgets.WellInput.min_height = 0.0
// The drop-down stands in the field row: the text box's padding, so the
// two are one height (sheet_checks::field_row).
mod.widgets.DropDown.padding.top = 3.0
mod.widgets.DropDown.padding.bottom = 4.0
// ---------------------------------------------------------------- rules
// A rule is a groove moulded into the board: a navy line over a pale one.
mod.widgets.Hr.draw_bg.pixel = fn() {
let px = kt_px(self.draw_pass.dpi_factor)
let p = self.pos * self.rect_size
let y0 = kt_snap(self.rect_size.y * 0.5 - px, self.rect_pos.y, px)
let dark = step(y0, p.y) * step(p.y, y0 + px)
let light = step(y0 + px, p.y) * step(p.y, y0 + px * 2.0)
return vec4(0.04, 0.12, 0.32, 1.0) * dark * 0.8 + vec4(0.45, 0.62, 0.92, 1.0) * light * 0.6
}
mod.widgets.Vr.draw_bg.pixel = fn() {
let px = kt_px(self.draw_pass.dpi_factor)
let p = self.pos * self.rect_size
let x0 = kt_snap(self.rect_size.x * 0.5 - px, self.rect_pos.x, px)
let dark = step(x0, p.x) * step(p.x, x0 + px)
let light = step(x0 + px, p.x) * step(p.x, x0 + px * 2.0)
return vec4(0.04, 0.12, 0.32, 1.0) * dark * 0.8 + vec4(0.45, 0.62, 0.92, 1.0) * light * 0.6
}
// ---------------------------------------------------------------- ground
// Plain views stop clipping so a shadow can fall past their edge; scroll
// views keep clipping their content.
mod.widgets.View.clip_x = false
// A splitter's strip is the board itself: nothing is drawn there at rest.
mod.widgets.Splitter.draw_bg.color_bg = uniform(#00000000)
mod.widgets.View.clip_y = false
mod.widgets.Window.pass.clear_color = #2763b9
// The window's own ground: the board. Round its edge the board's moulded
// rim -- a thick rounded lip lit along its upper and left edges and in
// shade along its lower and right, its corners rounded off over a dark
// gap -- so the detail stays at the frame and the floor under the content
// is calm. The board has been played with: a light scatter of scratches.
mod.widgets.Window.show_bg = true
mod.widgets.Window.draw_bg.pixel = fn() {
let px = kt_px(self.draw_pass.dpi_factor)
let sz = self.rect_size
let p = self.pos * sz
let w = p + self.rect_pos
var col = kt_board(w, sz, px)
let c = sz * 0.5
let h = sz * 0.5 - vec2(1.0, 1.0)
let r = 20.0
let d = Material.sd_box(p, c, h, r)
let n = kt_box_n(p, c, h, r)
let rim = 7.0
let e = clamp(1.0 + d / rim, 0.0, 1.0)
let facing = dot(n, vec2(-0.38, -0.92))
col = col * (1.0 + 0.32 * facing * e * e)
col = mix(col, vec3(1.0, 1.0, 1.0), smoothstep(0.35, 0.95, facing) * smoothstep(0.3, 0.75, e) * (1.0 - smoothstep(0.8, 1.0, e)) * 0.3)
// The inner foot of the lip: a soft shade where the rim rises off the
// floor.
let foot = exp(-((d + rim) * (d + rim)) / 6.0) * 0.12
col = col * (1.0 - foot)
col = mix(col, vec3(0.02, 0.06, 0.16), Finish.band(d, 0.0, px * 1.5, px) * 0.8)
// The wear: scratches lift the plastic toward a pale grey-white where
// they scatter the light; a deeper gouge darkens at its core. Under the
// content they stay faint, and a little stronger toward the frame.
let sc = kt_scratches(w, px)
let edge = 1.0 - smoothstep(0.0, 90.0, 0.0 - d)
let amt = mix(0.25, 0.34, edge)
col = mix(col, vec3(0.80, 0.86, 0.95), clamp(sc.x * amt, 0.0, 0.5))
col = col * (1.0 - 0.3 * sc.y)
// Past the rounded corners: the dark gap behind the board.
col = mix(vec3(0.03, 0.07, 0.17), col, Finish.cover(d, px))
return vec4(col, 1.0)
}
// ---------------------------------------------------------------- screens
// A SCREEN: navy glass in a thick yellow plastic frame, the readouts' and the
// meter's one housing; the glass shaded under its top edge, a quiet diagonal
// reflection over it. Its figures are the screen ink, a warm white.
mod.widgets.ScreenView.clip_x = false
mod.widgets.ScreenView.clip_y = false
mod.widgets.ScreenView.padding = mod.turtle.Inset{top: 9.0 right: 11.0 bottom: 9.0 left: 11.0}
mod.widgets.ScreenView.draw_bg.vertex = fn() {
let m = 6.0
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
mod.widgets.ScreenView.draw_bg.pixel = fn() {
let m = 6.0
let px = kt_px(self.draw_pass.dpi_factor)
let o = self.rect_pos
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let lo = vec2(kt_snap(0.0, o.x, px), kt_snap(0.0, o.y, px))
let hi = vec2(kt_snap(self.rect_size.x, o.x, px), kt_snap(self.rect_size.y, o.y, px))
let c = (lo + hi) * 0.5
let h = (hi - lo) * 0.5
let sh = Finish.drop(p, c, h - vec2(1.0, 0.0), 7.0, vec2(0.6, 2.2), 1.8) * 0.45
var res = vec4(kt_shade() * sh, sh)
var fill = self.color.rgb
if self.scan > 0.0 {
fill = fill * mix(1.0, Finish.scan(p.y, px, 3.0 * px, self.scan), 0.6)
}
res = Finish.over(res, kt_frame(p, lo, hi, 7.0, px, max(self.bezel, 3.0), kt_col(2.0), fill, 1.0, 0.0, 0.0, 0.0))
return res * self.color.a
}
mod.widgets.ScreenView.sheen = 0.04
mod.widgets.ScreenView.draw_glass.pixel = fn() {
let p = self.pos * self.rect_size
let px = kt_px(self.draw_pass.dpi_factor)
let lo = vec2(Finish.snap(self.rect_pos.x, px), Finish.snap(self.rect_pos.y, px)) - self.rect_pos
let hi = vec2(Finish.snap(self.rect_pos.x + self.rect_size.x, px), Finish.snap(self.rect_pos.y + self.rect_size.y, px)) - self.rect_pos
let c = (lo + hi) * 0.5
let ho = (hi - lo) * 0.5
let b = max(self.bezel, 3.0) + px
let hf = max(ho - vec2(b, b), vec2(0.5, 0.5))
let fd = Material.sd_box(p, c, hf, 4.0)
let u = (p - (c - hf)) / max(hf * 2.0, vec2(1.0, 1.0))
let diag = u.x + u.y * 0.6
let band = smoothstep(0.0, 0.12, diag) * (1.0 - smoothstep(0.28, 0.5, diag))
let a = clamp(self.sheen * band, 0.0, 1.0) * Finish.cover(fd + px, px)
return vec4(vec3(0.85, 0.92, 1.0) * a, a)
}
// THE NEEDLE METER is a screen: the same yellow frame and navy glass, its
// scale printed on the glass in the screen ink, the red zone a red band
// along the arc, the needle red, its root under a red plastic ball.
mod.widgets.NeedleMeter.clip_x = false
mod.widgets.NeedleMeter.clip_y = false
mod.widgets.NeedleMeter.draw_bg.color_zone = uniform(#ff4a3a)
mod.widgets.NeedleMeter.draw_bg.color_needle = uniform(#ff4a3a)
mod.widgets.NeedleMeter.draw_bg.vertex = fn() {
let m = 6.0
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
mod.widgets.NeedleMeter.draw_bg.pixel = fn() {
let m = 6.0
let px = kt_px(self.draw_pass.dpi_factor)
let o = self.rect_pos
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let lo = vec2(kt_snap(0.0, o.x, px), kt_snap(0.0, o.y, px))
let hi = vec2(kt_snap(self.rect_size.x, o.x, px), kt_snap(self.rect_size.y, o.y, px))
let c = (lo + hi) * 0.5
let h = (hi - lo) * 0.5
let sh = Finish.drop(p, c, h - vec2(1.0, 0.0), 7.0, vec2(0.6, 2.2), 1.8) * 0.45
var res = vec4(kt_shade() * sh, sh)
var face = vec3(0.051, 0.129, 0.329)
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 = vec3(1.0, 0.953, 0.722)
let arc = Finish.cover(abs(r - rr) - px * 0.75, px) * on_scale
let nn = 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) * nn + 0.5)
let ak = (k / nn - 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))
face = mix(face, ink, max(arc, tick) * 0.9)
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 - 3.0), px)
face = mix(face, self.color_zone.rgb, zone_t * zone_r)
}
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 - 0.7, px) * Finish.cover(along - (rr + 3.0), px) * Finish.cover(0.0 - along, px)
face = mix(face, self.color_needle.rgb, needle)
res = Finish.over(res, kt_frame(p, lo, hi, 7.0, px, 3.0, kt_col(2.0), face, 1.0, 0.0, 0.0, 0.0))
// The pivot: a red plastic ball.
res = kt_ball(res, p, pv, max(self.pivot_size, 3.0), px, kt_col(0.0), 0.0, 0.0, 0.0, 0.4)
// The glass's quiet reflection.
let hf = h - vec2(3.0, 3.0)
let u = (p - (c - hf)) / max(hf * 2.0, vec2(1.0, 1.0))
let diag = u.x + u.y * 0.6
let band = smoothstep(0.0, 0.12, diag) * (1.0 - smoothstep(0.28, 0.5, diag))
let ga = 0.04 * band * Finish.cover(Material.sd_box(p, c, hf, 4.0), px)
res = Finish.over(res, vec4(vec3(0.85, 0.92, 1.0) * ga, ga))
return res * self.color.a * self.opacity
}
// ---------------------------------------------------------------- switches
// THE SWITCHES are the toggle's clear plastic in their own colours: the
// rocker lights orange, the slide switch green.
mod.widgets.ToggleRocker.draw_bg.size = uniform(18.0)
mod.widgets.ToggleSlide.draw_bg.size = uniform(18.0)
mod.widgets.ToggleRocker.draw_bg.pill_aspect = uniform(1.89)
mod.widgets.ToggleSlide.draw_bg.pill_aspect = uniform(1.89)
mod.widgets.ToggleRocker.label_walk.margin.left = 34.0
mod.widgets.ToggleSlide.label_walk.margin.left = 34.0
mod.widgets.ToggleRocker.draw_bg.pixel = fn() {
let m = 5.0
let px = kt_px(self.draw_pass.dpi_factor)
let o = self.rect_pos
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let tw = self.size * self.pill_aspect
let th = self.size
let x0 = (self.rect_size.x - tw) * self.mark_at_end
let y0 = self.rect_size.y * 0.5 - th * 0.5
let lo = vec2(kt_snap(x0, o.x, px), kt_snap(y0, o.y, px))
let hi = vec2(kt_snap(x0 + tw, o.x, px), kt_snap(y0 + th, o.y, px))
let kt = mix(self.active, self.drag_pos, self.drag)
return kt_toggle(p, lo, hi, o, px, kt, self.active, self.hover, max(self.pressed, self.down), self.disabled, self.focus, kt_col(4.0))
}
mod.widgets.ToggleSlide.draw_bg.pixel = fn() {
let m = 5.0
let px = kt_px(self.draw_pass.dpi_factor)
let o = self.rect_pos
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let tw = self.size * self.pill_aspect
let th = self.size
let x0 = (self.rect_size.x - tw) * self.mark_at_end
let y0 = self.rect_size.y * 0.5 - th * 0.5
let lo = vec2(kt_snap(x0, o.x, px), kt_snap(y0, o.y, px))
let hi = vec2(kt_snap(x0 + tw, o.x, px), kt_snap(y0 + th, o.y, px))
let kt = mix(self.active, self.drag_pos, self.drag)
return kt_toggle(p, lo, hi, o, px, kt, self.active, self.hover, max(self.pressed, self.down), self.disabled, self.focus, kt_col(1.0))
}
// ---------------------------------------------------------------- states
// Every held state is written as a plain value that cuts in at once (the
// library writes some as snap() values, which a state held as a default --
// the states page -- never applies). Live, the cut is the same as the snap.
mod.widgets.ButtonFlat.animator.hover.on.from = {all: Snap}
mod.widgets.ButtonFlat.animator.hover.on.apply = {draw_bg: {down: 0.0 hover: 1.0} draw_text: {down: 0.0 hover: 1.0}}
mod.widgets.ButtonFlat.animator.hover.down.from = {all: Snap}
mod.widgets.ButtonFlat.animator.hover.down.apply = {draw_bg: {down: 1.0 hover: 1.0} draw_text: {down: 1.0 hover: 1.0}}
mod.widgets.CheckBoxFlat.animator.hover.down.from = {all: Snap}
mod.widgets.CheckBoxFlat.animator.hover.down.apply = {draw_bg: {down: 1.0 hover: 1.0} draw_text: {down: 1.0 hover: 1.0}}
mod.widgets.RadioButtonFlat.animator.hover.down.from = {all: Snap}
mod.widgets.RadioButtonFlat.animator.hover.down.apply = {draw_bg: {down: 1.0 hover: 1.0} draw_text: {down: 1.0 hover: 1.0}}
mod.widgets.RadioButtonTabFlat.animator.hover.down.from = {all: Snap}
mod.widgets.RadioButtonTabFlat.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.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.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}}
// ---------------------------------------------------------------- lamps
// LAMPS: an LED as a toy has it, a glossy bead of coloured glass. At full
// current its middle burns toward white and its rim is its most saturated
// colour; at lower current the middle is no longer white, the colour deeper,
// the glow smaller; out, it is dark tinted glass, its colour barely there.
// Round it a bezel: a raised ring of white plastic lit from the upper left,
// or a socket cut into the board, its upper wall in shadow and a catch
// light on its lower rim. Over the bead 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.
let kt_led_lamp_add = fn(res: vec4, light: vec3, ground: vec3) -> vec4 {
let est = res.rgb + ground * (1.0 - res.a)
let add = (vec3(1.0, 1.0, 1.0) - exp(-light)) * max(vec3(1.0, 1.0, 1.0) - est, vec3(0.0, 0.0, 0.0))
return vec4(res.rgb + add, res.a)
}
// An LED's colour at full strength: its brightest channel at one.
let kt_led_lamp_full = fn(ink: vec3) -> vec3 {
return ink / max(max(ink.x, max(ink.y, ink.z)), 0.01)
}
// The same colour at the emitter's own chroma: the hue kept and its
// saturation raised by `vivid` (0 leaves a pale colour pale; a colour with
// next to no hue, a white or a plain lamp, stays neutral either way).
let kt_led_lamp_hue = fn(ink: vec3, vivid: float) -> vec3 {
let f = kt_led_lamp_full(ink)
let mn = min(f.x, min(f.y, f.z))
let ch = 1.0 - mn
let pure = (f - vec3(mn, mn, mn)) / max(ch, 0.001)
return mix(f, pure, clamp(vivid, 0.0, 1.0) * 0.72 * smoothstep(0.03, 0.12, ch))
}
// The lamp: its lens a rounded box `hb` either side of `c` (round when the
// two are equal), `lit` its current 0..1, `ink` its colour and `vivid` how
// far its chroma is raised, `ground` what shows through where nothing is
// drawn, `room` how far its light may reach, `bez` and `raised` its bezel
// (raised 0: a recess cut in what is under it; 1: a raised ring of `bez`;
// 2: the same ring polished metal). Premultiplied over `dst`.
let kt_led_lamp = fn(dst: vec4, p: vec2, c: vec2, hb: vec2, lit: float, ink: vec3, vivid: float, px: float, ground: vec3, room: float, bez: vec3, raised: float) -> vec4 {
let rt = max(min(hb.x, hb.y), 0.4)
let d = Material.sd_box(p, c, hb, rt)
let bw = clamp(0.3 * rt, 0.7, 2.4)
let ds = d - bw
let L = clamp(lit, 0.0, 1.0)
let b = kt_led_lamp_full(ink)
let hue = kt_led_lamp_hue(ink, vivid)
let pw = L * sqrt(L)
let white = vec3(1.0, 1.0, 1.0)
let lum = vec3(0.2126, 0.7152, 0.0722)
let pale = smoothstep(0.35, 0.75, dot(ground, lum))
var res = dst
let x = max(ds, 0.0)
let reach = max(room, 1.0)
let win = 1.0 - smoothstep(0.55 * reach, reach, x)
// The lens's own frame: `q` in lens radii from its centre line, `t` its
// distance out, `nrm` the way its outline faces.
let ex = max(hb.x - hb.y, 0.0)
let ey = max(hb.y - hb.x, 0.0)
let qx = p.x - c.x
let qy = p.y - c.y
let q = vec2(qx - clamp(qx, 0.0 - ex, ex), qy - clamp(qy, 0.0 - ey, ey)) / rt
let t = clamp(length(q), 0.0, 1.0)
let nrm = q / max(length(q), 0.001)
// What the bead and its bezel throw on the ground: a soft shadow, down.
let shd = Finish.drop(p, c, hb + vec2(bw, bw), rt + bw, vec2(0.0, 0.22 * rt + 0.3), 0.32 * rt + px) * mix(0.18, 0.34, min(raised, 1.0)) * (1.0 - 0.6 * pw)
res = Finish.over(res, vec4(ground * 0.4 * shd, shd))
if L > 0.001 && pale > 0.001 {
// The tint, on a pale ground only: its hue laid over, never darker.
let h = clamp(0.42 * pw * pow(2.0, -x / max(0.4 * reach, 0.5)) * win, 0.0, 0.45) * pale
let est = res.rgb + ground * (1.0 - res.a)
let w = clamp(dot(hue, lum) / max(dot(est, lum), 0.001), 0.0, 1.0)
res = Finish.over(res, vec4(hue * h * w, h * w))
}
// The bezel: raised, a ring of `bez` rounded over, lit from the upper
// left; or a socket cut into what is under it, its upper wall in shadow
// and a catch light on its lower rim.
let a_s = Finish.cover(ds, px)
let u = clamp(d / max(bw, 0.01), 0.0, 1.0)
let facing = dot(nrm * (u - 0.5) * 2.0, vec2(-0.45, -0.89))
let metal = clamp(raised - 1.0, 0.0, 1.0)
var rc = bez * (0.94 + mix(0.12, 0.55, metal) * facing)
rc = mix(rc, min(bez + vec3(0.30, 0.30, 0.30), white), smoothstep(mix(0.35, 0.55, metal), 0.9, facing) * mix(0.6, 0.85, metal))
rc = mix(rc, rc * 0.8, Finish.band(ds, 0.0, max(px, 0.45), px) * smoothstep(0.0, 0.8, nrm.y) * 0.8)
let under = res.rgb / max(res.a, 0.001) * step(0.001, res.a) + ground * (1.0 - step(0.001, res.a))
let gy = clamp(nrm.y * t, -1.0, 1.0)
var sc = under * mix(0.55, 0.92, smoothstep(-0.9, 0.9, gy))
sc = mix(sc, min(under * 1.06 + vec3(0.28, 0.28, 0.28), white), Finish.band(ds, 0.0, max(px, 0.45), px) * smoothstep(0.2, 0.95, nrm.y) * 0.85)
sc = min(mix(sc, rc, min(raised, 1.0)), white)
res = Finish.over(res, vec4(sc * a_s, a_s))
// The light: a tight hot halo and a short bloom in its hue, gone in
// about two radii, falling on the bezel too.
if L > 0.001 {
let xl = max(d, 0.0)
let halo = exp(-xl / max(0.30 * rt, 0.4)) * 1.5 + exp(-xl / max(0.65 * rt, 0.7)) * 0.32
let hwin = 1.0 - smoothstep(1.1 * rt, 2.0 * rt + 1.0, xl)
res = kt_led_lamp_add(res, mix(hue, white, 0.15 * pw) * halo * pw * hwin * win, ground)
}
// THE BEAD: a glossy dome of coloured glass. Its colour deepens toward
// the top; light pools low inside, where it comes through the dome; lit,
// its middle burns toward white at full current and its rim is its most
// saturated colour.
let top = smoothstep(-1.0, 0.6, q.y)
let pool = exp(-(q.y - 0.55) * (q.y - 0.55) / 0.09 - q.x * q.x / 0.30)
var on = mix(hue * mix(0.55, 0.8, L), mix(b, hue, 0.5), top)
on = mix(on, min(mix(b, hue, 0.4) + vec3(0.35, 0.35, 0.35), white), pool * 0.5)
on = mix(on, vec3(1.0, 0.99, 0.975), exp(-t * t / 0.20) * smoothstep(0.5, 1.0, L) * 0.85)
on = mix(on, hue * (0.82 + 0.18 * L), smoothstep(0.62, 1.0, t))
on = on * (0.42 + 0.58 * L)
// Out: dark tinted glass on a dark ground, a grey bead on a pale one,
// its colour barely there, the same dome, a catch light along its lower
// rim.
let offc = mix(mix(vec3(0.085, 0.09, 0.115), ground * 0.62, pale), b * 0.4, 0.12)
var off = mix(offc * 0.62, offc * 1.08, top)
off = mix(off, min(offc + vec3(0.18, 0.18, 0.18), white), pool * 0.35)
off = off * mix(1.0, 0.75, smoothstep(0.75, 1.0, t))
let catchl = smoothstep(0.62, 0.9, t) * (1.0 - smoothstep(0.9, 1.0, t)) * smoothstep(0.2, 0.9, nrm.y)
off = off + vec3(0.30, 0.30, 0.32) * catchl * (1.0 - 0.5 * pale)
var col = mix(off, min(on, white), smoothstep(0.0, 0.35, L))
// One crisp highlight: a white oval across its upper third.
let e = length(vec2(q.x / 0.62, (q.y + 0.46) / 0.30))
let spec = (1.0 - smoothstep(0.55, 1.0, e)) * mix(1.0, 0.3, clamp((q.y + 0.76) / 0.6, 0.0, 1.0))
col = mix(col, white, spec * mix(0.8, 0.55, L) * smoothstep(0.9, 2.2, rt))
// Out, a faint seam where the dome meets its bezel; lit, none: the rim
// is the colour.
col = mix(col, col * 0.7, Finish.band(d, 0.0, px * 0.6, px) * 0.6 * (1.0 - smoothstep(0.0, 0.25, L)))
col = min(col, white)
let a_l = Finish.cover(d, px)
return Finish.over(res, vec4(col * a_l, a_l))
}
// The Lamp widget and the bar lamp: a glossy toy light bead set in a raised
// ring of white plastic on the board, with its own soft shadow. The halo's
// room is held in the lamp's own box (the library reserves a lens
// thickness either side once a halo is asked for), so the light is never
// cut at its edge.
mod.widgets.Lamp.halo = 0.3
mod.widgets.Lamp.halo_reach = 1.0
mod.widgets.Lamp.draw_bg.color_accent = uniform(#3aa8ff)
mod.widgets.Lamp.draw_bg.color_success = uniform(#3ee05a)
mod.widgets.Lamp.draw_bg.color_warning = uniform(#ffc21a)
mod.widgets.Lamp.draw_bg.color_error = uniform(#ff3a2a)
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)
// The bead takes nearly half its box, the rest is room for its bezel
// and light.
let t = min(self.rect_size.y / (1.0 + 2.0 * reach) * 1.35, self.rect_size.y * 0.5)
let room = (self.rect_size.y - t) * 0.5
let c = self.rect_size * 0.5
// A round lamp: the bead the lamp's size in the middle of its box. A
// bar: a short light pipe three fifths of a bead thick and at most 11
// points long, set from the start of its box with its light gone
// within a point and a half, so in a row it never reaches toward what
// stands after it.
let tb = t * 0.6
let blen = min(max(self.rect_size.x - 2.0 * room, tb), min(2.0 * tb, 11.0))
let cb = vec2(1.5 + 0.9 + blen * 0.5, self.rect_size.y * 0.5)
let cc = mix(c, cb, self.bar)
let hb = mix(vec2(t * 0.5, t * 0.5), vec2(blen * 0.5, tb * 0.5), self.bar)
let reach_px = mix(max(room - 0.5, 2.0), 1.5, self.bar)
let res = kt_led_lamp(vec4(0.0, 0.0, 0.0, 0.0), p, cc, hb, self.lit, self.intent_color().rgb, 1.0 - step(3.5, self.intent), px, vec3(0.153, 0.388, 0.725), reach_px, vec3(0.93, 0.93, 0.91), 1.0)
return res * self.opacity
}
mod.widgets.LampBar.draw_bg.color_accent = uniform(#3aa8ff)
mod.widgets.LampBar.draw_bg.color_success = uniform(#3ee05a)
mod.widgets.LampBar.draw_bg.color_warning = uniform(#ffc21a)
mod.widgets.LampBar.draw_bg.color_error = uniform(#ff3a2a)
true