All outline text draws with Slug curves on every platform (SDF/MSDF stays selectable per size); a frame never presents a blank glyph and retained draw lists keep theirs. One coverage model for vector feathering, widget families split out of the core crate, fewer steady-state allocations, video keeps its aspect (Contain/Cover), render and SSAO pass unification. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
3731 lines
213 KiB
Text
3731 lines
213 KiB
Text
use mod.prelude.widgets_internal.*
|
|
use mod.sdf.Cap
|
|
|
|
// Candy-red lacquer and chrome. Every face here is drawn in true points (one device
|
|
// pixel is `px`), and every shadow, lip and glow is drawn OUTSIDE the layout
|
|
// box: the quad is grown in `vertex`, the templates that throw a shadow stop
|
|
// clipping, and no padding is spent on relief.
|
|
|
|
// ---------------------------------------------------------------- helpers
|
|
|
|
// One device pixel in points.
|
|
let lq_px = fn(dpi: float) -> float {
|
|
return 1.0 / max(dpi, 0.5)
|
|
}
|
|
|
|
// An edge rounded to a whole device pixel in window space, handed back in
|
|
// box space: a hairline on it lands on one pixel row.
|
|
let lq_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 lq_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)
|
|
}
|
|
|
|
// A value picked by material: 0 red lacquer, 1 smoked chrome, 2 polished
|
|
// chrome.
|
|
let lq_pick = fn(mat: float, a: vec3, b: vec3, k: vec3) -> vec3 {
|
|
let m1 = step(0.5, mat) * (1.0 - step(1.5, mat))
|
|
let m2 = step(1.5, mat)
|
|
return mix(mix(a, b, m1), k, m2)
|
|
}
|
|
|
|
// A raised cap of lacquer or chrome: the drop and contact shadows under it,
|
|
// the dark outline, a face whose upper part carries the gloss down to a soft
|
|
// horizon, the top edge line, a chrome catch-light along the foot, and the
|
|
// reflected red lifting the lowest rows. `lift` 1 casts the drop shadow, 0
|
|
// seats the cap on the ground with the contact shadow only. `rim` is the
|
|
// foot rim's ink where a lamp spills on it. `gloss` 1 is the full lacquer
|
|
// sheen, 0 a matte face. `mat` picks the material. Premultiplied.
|
|
let lq_capm = fn(p: vec2, lo: vec2, hi: vec2, r: float, px: float, hover: float, down: float, disabled: float, focus: float, lift: float, tint: vec3, rim: vec4, gloss: float, mat: float) -> vec4 {
|
|
let c = (lo + hi) * 0.5
|
|
let h = (hi - lo) * 0.5
|
|
let hh = hi.y - lo.y
|
|
let sh_h = min(hh, 24.0)
|
|
let d = Material.sd_box(p, c, h, r)
|
|
let up = (1.0 - down) * (1.0 - disabled)
|
|
|
|
// What the cap throws on the lacquer: a firm drop close under it and a
|
|
// soft tail, the sides gone within a point or two.
|
|
let drop1 = Finish.drop(p, c, h - vec2(0.05 * sh_h, 0.0), r, vec2(0.0, 0.10 * sh_h), 0.06 * sh_h) * 0.75
|
|
let drop2 = Finish.drop(p, c, h - vec2(0.1 * sh_h, 0.04 * sh_h), r, vec2(0.0, 0.16 * sh_h), 0.11 * sh_h) * 0.30
|
|
let drop = (1.0 - (1.0 - drop1) * (1.0 - drop2)) * lift * up
|
|
let contact = Finish.drop(p, c, h, r, vec2(0.0, px), px * 0.6) * 0.85 * (1.0 - 0.6 * disabled)
|
|
let sh = 1.0 - (1.0 - drop) * (1.0 - contact)
|
|
var under = vec4(0.03, 0.0, 0.0, 1.0) * sh
|
|
// A held cap sits in the lacquer: a 1 dp chrome lip shows on the ground
|
|
// right under its lower edge.
|
|
let lip = Finish.ring_out(d, 0.0, px, px) * step(c.y + h.y * 0.5, p.y) * down * (1.0 - disabled)
|
|
under = Finish.over(under, vec4(0.62, 0.44, 0.46, 1.0) * lip * 0.8)
|
|
|
|
let t = clamp((p.y - lo.y) / max(hh, 1.0), 0.0, 1.0)
|
|
let uv = clamp((p - lo) / max(hi - lo, vec2(1.0, 1.0)), vec2(0.0, 0.0), vec2(1.0, 1.0))
|
|
let small = 1.0 - step(23.5, hh)
|
|
|
|
let top_c = lq_pick(mat, vec3(0.860, 0.500, 0.520), vec3(0.560, 0.560, 0.585), vec3(0.900, 0.895, 0.910))
|
|
let hor_c = lq_pick(mat, vec3(0.660, 0.235, 0.255), vec3(0.330, 0.330, 0.352), vec3(0.640, 0.630, 0.655))
|
|
let low_c = lq_pick(mat, vec3(0.560, 0.120, 0.138), vec3(0.205, 0.200, 0.218), vec3(0.260, 0.240, 0.255))
|
|
let foot_c = lq_pick(mat, vec3(0.420, 0.085, 0.100), vec3(0.150, 0.140, 0.155), vec3(0.560, 0.470, 0.490))
|
|
let line_c = lq_pick(mat, vec3(0.930, 0.760, 0.770), vec3(0.760, 0.760, 0.780), vec3(1.000, 1.000, 1.000))
|
|
let rim_c = lq_pick(mat, vec3(0.780, 0.660, 0.680), vec3(0.600, 0.580, 0.600), vec3(0.930, 0.900, 0.915))
|
|
let ol_c = lq_pick(mat, vec3(0.110, 0.016, 0.020), vec3(0.055, 0.045, 0.050), vec3(0.120, 0.090, 0.100))
|
|
|
|
// The gloss: a lighter upper band whose lower edge is a soft horizon a
|
|
// little under the middle, rising a touch toward the sides.
|
|
let sx = uv.x * 2.0 - 1.0
|
|
let hz = 0.47 - 0.06 * sx * sx
|
|
let band = 1.0 - smoothstep(hz - 0.06, hz + 0.02, t)
|
|
let upper = mix(top_c, hor_c, pow(clamp(t / max(hz, 0.01), 0.0, 1.0), 0.75))
|
|
let lower = mix(low_c, foot_c, smoothstep(hz, 1.0, t))
|
|
let matte = mix(mix(hor_c, low_c, 0.55), foot_c, t)
|
|
var face = mix(matte, mix(lower, upper, band), gloss) + tint
|
|
// The reflected light: the lowest rows lift toward the chrome catch.
|
|
face = mix(face, rim_c, 0.30 * smoothstep(0.70, 1.0, t) * (1.0 - disabled))
|
|
// A window of light held in the gloss: a soft bright streak near the top.
|
|
let wy = (t - 0.14) / 0.09
|
|
face = mix(face, line_c, 0.20 * exp(-wy * wy) * gloss * up)
|
|
// Under the hand the gloss wakes: the face lifts and the streak grows.
|
|
face = face + mix(vec3(0.050, 0.022, 0.024), vec3(0.04, 0.04, 0.042), step(0.5, mat)) * (1.0 + small) * hover * up
|
|
face = mix(face, line_c, 0.10 * exp(-wy * wy) * gloss * hover * up)
|
|
// Held or chosen: sunk. The gloss goes out, the floor falls dark and a
|
|
// shade lies under the top edge for a fifth of the height.
|
|
let shade = 0.55 * (1.0 - smoothstep(0.0, 0.22 * min(hh, 24.0), p.y - lo.y - px))
|
|
let held = mix(mix(low_c, foot_c, t) * 0.78 + tint, vec3(0.0, 0.0, 0.0), shade)
|
|
face = mix(face, held, down * (1.0 - disabled))
|
|
// Disabled: the lacquer dulls, grey drawn into it.
|
|
let fl = Finish.lum(face)
|
|
face = mix(face, mix(face, vec3(fl, fl, fl), 0.45) * 0.8, disabled)
|
|
|
|
// Which way the outline faces here, so a round cap gets an arc where a
|
|
// square one gets a straight line.
|
|
let s = vec2(step(0.0, p.x - c.x) * 2.0 - 1.0, step(0.0, p.y - c.y) * 2.0 - 1.0)
|
|
let q = abs(p - c) - (h - vec2(r, r))
|
|
let qc = max(q, vec2(0.0, 0.0))
|
|
var n = vec2(0.0, s.y)
|
|
if q.x > q.y {
|
|
n = vec2(s.x, 0.0)
|
|
}
|
|
if q.x > 0.0 && q.y > 0.0 {
|
|
n = qc / max(length(qc), 0.0001) * s
|
|
}
|
|
let inner = Finish.band(d, px, px * 2.5, px)
|
|
// The top edge line, bright on the gloss, brighter under the hand.
|
|
face = mix(face, line_c, inner * smoothstep(0.5, 0.97, -n.y) * (0.75 + 0.2 * hover) * up)
|
|
// The chrome bezel all round: bright along the top, pale pink-grey down
|
|
// the sides, the brightest catch along the foot.
|
|
let bez = mix(line_c, rim_c, smoothstep(-0.7, 0.3, n.y))
|
|
let side = inner * smoothstep(0.7, 1.0, abs(n.x))
|
|
face = mix(face, bez, 0.75 * side * (1.0 - 0.6 * disabled) * (1.0 - 0.4 * down))
|
|
// The foot: the chrome catch-light, and a lamp's spill where there is one.
|
|
let foot = inner * smoothstep(0.5, 0.97, n.y)
|
|
face = mix(face, rim_c, foot * 0.9 * (1.0 - 0.6 * disabled) * (1.0 - 0.5 * down))
|
|
face = mix(face, rim.rgb, foot * rim.a * (1.0 - disabled))
|
|
face = mix(face, mix(ol_c, vec3(0.20, 0.10, 0.11), disabled), Finish.band(d, 0.0, px, px))
|
|
|
|
let a = Finish.cover(d, px)
|
|
var o = Finish.over(under, vec4(face * a, a))
|
|
// Focus: a thin ice-blue ring just outside the outline, and a breath of it.
|
|
let ring = Finish.ring_out(d, 0.0, px, px) * 0.9 + Finish.glow(d - px, 0.7) * 0.35 * (1.0 - Finish.cover(d, px))
|
|
o = Finish.over(o, vec4(0.498, 0.706, 0.910, 1.0) * clamp(ring, 0.0, 1.0) * focus * (1.0 - disabled))
|
|
return o
|
|
}
|
|
// The red lacquer cap.
|
|
let lq_cap = fn(p: vec2, lo: vec2, hi: vec2, r: float, px: float, hover: float, down: float, disabled: float, focus: float, lift: float, tint: vec3, rim: vec4, gloss: float) -> vec4 {
|
|
return lq_capm(p, lo, hi, r, px, hover, down, disabled, focus, lift, tint, rim, gloss, 0.0)
|
|
}
|
|
// The smoked-chrome cap: the scroll thumb.
|
|
let lq_smoke = fn(p: vec2, lo: vec2, hi: vec2, r: float, px: float, hover: float, down: float, disabled: float, focus: float, lift: float) -> vec4 {
|
|
return lq_capm(p, lo, hi, r, px, hover, down, disabled, focus, lift, vec3(0.0, 0.0, 0.0), vec4(0.0, 0.0, 0.0, 0.0), 1.0, 1.0)
|
|
}
|
|
// The polished-chrome cap: check boxes, thumbs, the fader cap.
|
|
let lq_chrome = fn(p: vec2, lo: vec2, hi: vec2, r: float, px: float, hover: float, down: float, disabled: float, focus: float, lift: float) -> vec4 {
|
|
return lq_capm(p, lo, hi, r, px, hover, down, disabled, focus, lift, vec3(0.0, 0.0, 0.0), vec4(0.0, 0.0, 0.0, 0.0), 1.0, 2.0)
|
|
}
|
|
|
|
// Brushed steel: the choice caps, segments, tabs, drop-downs, the chip and
|
|
// the badge. A raised cap of light steel with fine horizontal streaks (read
|
|
// in window space `q`, so neighbours share one brushing), a broad soft
|
|
// highlight across its upper part, a crisp light line on the top edge, a
|
|
// darker foot warmed by the red lacquer it stands on, and the thin dark
|
|
// outline; its labels are dark. Held (`down`) it sinks: the steel falls a
|
|
// step, a shade lies under the top edge and down the walls, and a light lip
|
|
// catches along the foot. `deep` 1 is the chosen answer: a deeper, cooler
|
|
// well whose label is lit. `tint` shades the steel toward a role colour.
|
|
// Premultiplied.
|
|
let lq_steel_tone = fn(t: float, sx: float, q: vec2, px: float) -> float {
|
|
let hl = exp(-((t - 0.26) / 0.20) * ((t - 0.26) / 0.20))
|
|
var l = mix(0.710, 0.585, smoothstep(0.42, 0.60, t))
|
|
l = mix(l, 0.455, smoothstep(0.76, 1.0, t))
|
|
l = l + 0.175 * hl * (1.0 - 0.30 * sx * sx)
|
|
// Fine streaks and a coarser, slower brushing under them.
|
|
let s1 = Finish.brushed(q, vec2(1.0, 0.0), px * 1.2) - 0.5
|
|
let s2 = Finish.brushed(q + vec2(31.0, 7.0), vec2(1.0, 0.0), px * 4.5) - 0.5
|
|
return l + s1 * 0.075 + s2 * 0.045
|
|
}
|
|
let lq_steel = fn(p: vec2, lo: vec2, hi: vec2, r: float, px: float, o: vec2, hover: float, down: float, deep: float, disabled: float, focus: float, lift: float, tint: vec3) -> vec4 {
|
|
let c = (lo + hi) * 0.5
|
|
let h = (hi - lo) * 0.5
|
|
let hh = hi.y - lo.y
|
|
let sh_h = min(hh, 24.0)
|
|
let d = Material.sd_box(p, c, h, r)
|
|
let up = (1.0 - down) * (1.0 - disabled)
|
|
|
|
let drop1 = Finish.drop(p, c, h - vec2(0.05 * sh_h, 0.0), r, vec2(0.0, 0.10 * sh_h), 0.06 * sh_h) * 0.75
|
|
let drop2 = Finish.drop(p, c, h - vec2(0.1 * sh_h, 0.04 * sh_h), r, vec2(0.0, 0.16 * sh_h), 0.11 * sh_h) * 0.30
|
|
let drop = (1.0 - (1.0 - drop1) * (1.0 - drop2)) * lift * up
|
|
let contact = Finish.drop(p, c, h, r, vec2(0.0, px), px * 0.6) * 0.85 * (1.0 - 0.6 * disabled)
|
|
let sh = 1.0 - (1.0 - drop) * (1.0 - contact)
|
|
var under = vec4(0.03, 0.0, 0.0, 1.0) * sh
|
|
let lip = Finish.ring_out(d, 0.0, px, px) * step(c.y + h.y * 0.5, p.y) * down * (1.0 - disabled)
|
|
under = Finish.over(under, vec4(0.62, 0.44, 0.46, 1.0) * lip * 0.8)
|
|
|
|
let t = clamp((p.y - lo.y) / max(hh, 1.0), 0.0, 1.0)
|
|
let uv = clamp((p - lo) / max(hi - lo, vec2(1.0, 1.0)), vec2(0.0, 0.0), vec2(1.0, 1.0))
|
|
let sx = uv.x * 2.0 - 1.0
|
|
let q = p + o
|
|
|
|
// The raised face.
|
|
let l = lq_steel_tone(t, sx, q, px) + 0.065 * hover * up
|
|
var face = vec3(l, l, l) * vec3(0.972, 0.982, 1.0)
|
|
// The red lacquer mirrored in the foot.
|
|
face = mix(face, face * vec3(1.10, 0.84, 0.86), 0.55 * smoothstep(0.66, 1.0, t))
|
|
|
|
// The sunk face: the same steel a step or two down, cooler when chosen,
|
|
// a shade under the top edge and along both walls.
|
|
let s1 = Finish.brushed(q, vec2(1.0, 0.0), px * 1.2) - 0.5
|
|
let s2 = Finish.brushed(q + vec2(31.0, 7.0), vec2(1.0, 0.0), px * 4.5) - 0.5
|
|
let wl = mix(mix(0.590, 0.330, deep), mix(0.520, 0.265, deep), t) + (s1 * 0.060 + s2 * 0.035) * mix(1.0, 0.7, deep) + 0.02 * hover
|
|
var well = vec3(wl, wl, wl) * mix(vec3(0.985, 0.975, 0.985), vec3(0.90, 0.955, 1.04), deep)
|
|
let top_sh = exp(-max(p.y - lo.y - px, 0.0) / (0.16 * sh_h))
|
|
let wall = exp(-max(min(p.x - lo.x, hi.x - p.x) - px, 0.0) / (2.5 * px))
|
|
well = mix(well, vec3(0.0, 0.0, 0.0), clamp(0.62 * top_sh + 0.40 * wall, 0.0, 0.85))
|
|
face = mix(face, well, down * (1.0 - disabled)) + tint
|
|
// Disabled: the steel dims and flattens.
|
|
let fl = Finish.lum(face)
|
|
face = mix(face, mix(face, vec3(fl, fl, fl), 0.5) * 0.66, disabled)
|
|
|
|
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 qq = abs(p - c) - (h - vec2(r, r))
|
|
let qc = max(qq, vec2(0.0, 0.0))
|
|
var n = vec2(0.0, s.y)
|
|
if qq.x > qq.y {
|
|
n = vec2(s.x, 0.0)
|
|
}
|
|
if qq.x > 0.0 && qq.y > 0.0 {
|
|
n = qc / max(length(qc), 0.0001) * s
|
|
}
|
|
let inner = Finish.band(d, px, px * 2.0, px)
|
|
// The crisp light line along the top edge, brighter under the hand.
|
|
let topl = inner * smoothstep(0.5, 0.97, -n.y)
|
|
face = mix(face, vec3(0.985, 0.985, 1.0), topl * (0.85 + 0.15 * hover) * up)
|
|
face = mix(face, vec3(0.80, 0.80, 0.82), topl * 0.35 * (1.0 - down) * disabled)
|
|
// The sides: a soft light bevel on a raised cap.
|
|
let side = inner * smoothstep(0.7, 1.0, abs(n.x))
|
|
face = mix(face, vec3(0.90, 0.90, 0.92), 0.35 * side * up)
|
|
// The foot: the machined edge catches a warm light under the darker foot;
|
|
// on a sunk face the lip is the light caught inside the well's foot.
|
|
let foot = inner * smoothstep(0.5, 0.97, n.y)
|
|
face = mix(face, vec3(0.80, 0.70, 0.72), foot * 0.55 * up)
|
|
face = mix(face, mix(vec3(0.62, 0.60, 0.63), vec3(0.50, 0.62, 0.74), deep), foot * 0.7 * down * (1.0 - disabled))
|
|
face = mix(face, mix(vec3(0.070, 0.030, 0.036), vec3(0.20, 0.12, 0.13), disabled), Finish.band(d, 0.0, px, px))
|
|
|
|
let a = Finish.cover(d, px)
|
|
var res = Finish.over(under, vec4(face * a, a))
|
|
let ring = Finish.ring_out(d, 0.0, px, px) * 0.9 + Finish.glow(d - px, 0.7) * 0.35 * (1.0 - Finish.cover(d, px))
|
|
res = Finish.over(res, vec4(0.498, 0.706, 0.910, 1.0) * clamp(ring, 0.0, 1.0) * focus * (1.0 - disabled))
|
|
return res
|
|
}
|
|
|
|
// A polished chrome ball: radios, slider handles. The ball mirrors a bright
|
|
// sky above, a dark horizon a little under its middle and the red lacquer
|
|
// below, brightening toward its rim; a hot specular up and to the left; a
|
|
// dark outline and a soft shadow on the ground. `held` darkens it and sinks
|
|
// its shadow. Premultiplied over `dst`.
|
|
let lq_ball = fn(dst: vec4, p: vec2, c: vec2, rad: float, px: float, hover: float, held: float, disabled: float, lift: float) -> vec4 {
|
|
let dd = length(p - c) - rad
|
|
let sh1 = Finish.drop(p, c, vec2(rad * 0.8, rad * 0.8), rad * 0.8, vec2(0.0, rad * 0.30), rad * 0.22) * 0.70
|
|
let sh2 = Finish.drop(p, c, vec2(rad * 0.7, rad * 0.7), rad * 0.7, vec2(0.0, rad * 0.55), rad * 0.45) * 0.30
|
|
let sh = (1.0 - (1.0 - sh1) * (1.0 - sh2)) * lift * (1.0 - 0.5 * held) * (1.0 - 0.6 * disabled)
|
|
var res = Finish.over(dst, vec4(0.03, 0.0, 0.0, 1.0) * sh)
|
|
let q = (p - c) / max(rad, 0.5)
|
|
let rl = min(length(q), 1.0)
|
|
let ny = q.y
|
|
let sky = mix(vec3(0.98, 0.97, 0.98), vec3(0.66, 0.64, 0.67), smoothstep(-0.95, -0.05, ny))
|
|
let gnd = mix(vec3(0.40, 0.17, 0.19), vec3(0.86, 0.66, 0.68), smoothstep(0.35, 0.98, rl) * smoothstep(0.1, 0.8, ny))
|
|
var col = mix(sky, vec3(0.17, 0.12, 0.13), smoothstep(-0.08, 0.16, ny))
|
|
col = mix(col, gnd, smoothstep(0.16, 0.45, ny))
|
|
let sp = q - vec2(-0.32, -0.40)
|
|
col = col + vec3(1.0, 1.0, 1.0) * exp(-dot(sp, sp) / (2.0 * 0.11 * 0.11)) * 0.75
|
|
col = mix(col, vec3(0.12, 0.08, 0.09), smoothstep(0.80, 1.0, rl) * 0.55)
|
|
col = col * (1.0 + 0.16 * hover) * (1.0 - 0.30 * min(held, 1.2))
|
|
// Under the hand a pale ice rim light wakes round the ball's edge.
|
|
col = mix(col, vec3(0.80, 0.90, 1.0), smoothstep(0.72, 0.95, rl) * 0.45 * hover * (1.0 - disabled))
|
|
let fl = Finish.lum(col)
|
|
col = mix(col, mix(vec3(fl, fl, fl), vec3(0.45, 0.25, 0.27), 0.4) * 0.75, disabled)
|
|
col = mix(col, vec3(0.10, 0.05, 0.06), Finish.band(dd, 0.0, px, px))
|
|
let a = Finish.cover(dd, px)
|
|
return Finish.over(res, vec4(col * a, a))
|
|
}
|
|
|
|
// An LED bar or dot laid over `dst`: a white-hot core, a 1 dp saturated rim
|
|
// and a short halo when lit (`halo` scales it; 0 for a light that shows a
|
|
// value); a dark lens when not (a bar: one dark glass). `hs` is the half
|
|
// size; the ends are round.
|
|
let lq_led = fn(dst: vec4, p: vec2, c: vec2, hs: vec2, lit: float, halo: float, px: float, core: vec3, rimc: vec3) -> vec4 {
|
|
let d = Finish.sd_pill(p, c, hs)
|
|
let slot = Finish.cover(d, px)
|
|
// Unlit: a dark lens, not a hole -- a black rim, near-black glass that
|
|
// catches a little of the light on its upper half, and on a dot large
|
|
// enough to show it a small catch-light up and to the left.
|
|
let tl = clamp((p.y - (c.y - hs.y)) / max(2.0 * hs.y, px), 0.0, 1.0)
|
|
var lens = mix(vec3(0.165, 0.176, 0.200), vec3(0.071, 0.078, 0.090), smoothstep(0.2, 0.9, tl))
|
|
let sp = p - (c + vec2(-0.3 * hs.x, -0.35 * hs.y))
|
|
lens = lens + vec3(0.16, 0.17, 0.19) * exp(-dot(sp, sp) / max(2.0 * 0.09 * hs.y * hs.y, 0.001)) * step(1.5, hs.y)
|
|
// A bar has no room for a rim, a light row and a catch-light: unlit it
|
|
// is one glass of three device pixels, falling #1e2127 -> #15171a with a
|
|
// faint cast of the lamp's blue, its top row lifted 0.03.
|
|
let bar = step(hs.y * 2.5, hs.x)
|
|
let gc = c + vec2(0.0, px * 0.5)
|
|
let gd = Finish.sd_pill(p, gc, vec2(hs.x + px * 0.5, px * 1.5))
|
|
let gt = clamp((p.y - (gc.y - px * 1.5)) / (px * 3.0), 0.0, 1.0)
|
|
var glass = mix(vec3(0.118, 0.129, 0.153), vec3(0.082, 0.090, 0.102), gt)
|
|
glass = mix(glass, vec3(0.40, 0.62, 0.84), 0.08) + vec3(0.03, 0.03, 0.03) * (1.0 - step(gc.y - px * 0.5, p.y))
|
|
var o = Finish.over(dst, vec4(0.024, 0.024, 0.031, 1.0) * Finish.ring_out(d, 0.0, px, px) * 0.55 * (1.0 - lit) * (1.0 - bar))
|
|
o = Finish.over(o, vec4(lens, 1.0) * slot * (1.0 - lit) * (1.0 - bar))
|
|
o = Finish.over(o, vec4(glass, 1.0) * Finish.cover(gd, px) * (1.0 - lit) * bar)
|
|
// Lit: the halo first, under the core. Two falls: a thin one that is
|
|
// gone in a device pixel or two, and a wide weak one that lies a little
|
|
// light on the floor round the lamp.
|
|
let x = max(d - px, 0.0)
|
|
let g = (0.45 * exp(-(x * x) / (2.0 * 0.5 * 0.5)) + 0.22 * exp(-(x * x) / (2.0 * 1.4 * 1.4))) * (1.0 - slot)
|
|
o = Finish.over(o, vec4(rimc, 1.0) * g * 0.8 * halo * lit)
|
|
// The saturated rim, one device pixel round the core.
|
|
let rim = Finish.ring_out(d, 0.0, px, px)
|
|
o = Finish.over(o, vec4(rimc, 1.0) * rim * lit)
|
|
// A hot top on the core.
|
|
let hot = 1.0 - smoothstep(0.0, hs.y * 1.2, p.y - (c.y - hs.y))
|
|
let col = mix(core, vec3(1.0, 1.0, 1.0), hot * 0.5)
|
|
o = Finish.over(o, vec4(col, 1.0) * slot * lit)
|
|
return o
|
|
}
|
|
|
|
// The lamp under a chosen label -- the chosen segment's, the tab in use's,
|
|
// the Primary's -- counted in whole device pixels. Its core is two rows
|
|
// between `xs.x` and `xs.y` (snapped), from the row at `y0` (snapped): the
|
|
// top row `core`, the lower one tinted toward `glow`, and one light-blue
|
|
// column at either end. Round it the halo lies on the floor in two layers:
|
|
// a tight one -- a pale row just above the core (brightest over its
|
|
// middle), a blue row under it, two blue-grey rows fading after that, and
|
|
// at the ends a pale fall gone three columns out -- over a soft wide one
|
|
// that rises three rows above the core, lies faintly along its sides and
|
|
// runs on down to the foot of the well. What lies above the core stays
|
|
// under an eighth of its light past the pale row, so a descender keeps its
|
|
// well. `halo` scales the light round the core; premultiplied over `dst`.
|
|
let lq_lamp = fn(dst: vec4, p: vec2, xs: vec2, y0: float, px: float, lit: float, halo: float, core: vec3, glow: vec3) -> vec4 {
|
|
// Which row from the core's top, and how many columns past its ends
|
|
// (0 inside, 0.5 on the first column out).
|
|
let k = floor((p.y - y0) / px)
|
|
let e = max(max(xs.x - p.x, p.x - xs.y), 0.0) / px
|
|
let hw = max((xs.y - xs.x) * 0.5, px)
|
|
let dx = clamp(abs(p.x - (xs.x + xs.y) * 0.5) / hw, 0.0, 1.0)
|
|
let pale = mix(glow, core, 0.5)
|
|
let dim = mix(glow, vec3(0.56, 0.58, 0.64), 0.45)
|
|
let m4 = step(-4.5, k) * step(k, -3.5)
|
|
let m3 = step(-3.5, k) * step(k, -2.5)
|
|
let m2 = step(-2.5, k) * step(k, -1.5)
|
|
let m1 = step(-1.5, k) * step(k, -0.5)
|
|
let body = step(-0.5, k) * step(k, 1.5)
|
|
let p1 = step(1.5, k) * step(k, 2.5)
|
|
let p2 = step(2.5, k) * step(k, 3.5)
|
|
let p3 = step(3.5, k) * step(k, 4.5)
|
|
let p4 = step(4.5, k) * step(k, 5.5)
|
|
let p5 = step(5.5, k) * step(k, 6.5)
|
|
// The soft wide layer.
|
|
let wide = exp(-(e * e) / (2.0 * 3.4 * 3.4)) * (1.0 - 0.3 * dx * dx)
|
|
let wa = (0.12 * m4 + 0.22 * m3 + 0.32 * m2 + 0.15 * m1 + 0.20 * body + 0.08 * (p1 + p2 + p3) + 0.25 * p4 + 0.15 * p5) * wide
|
|
var o = Finish.over(dst, vec4(dim, 1.0) * clamp(wa * halo, 0.0, 1.0) * lit)
|
|
// The tight layer.
|
|
let top = exp(-(e * e) / (2.0 * 1.6 * 1.6)) * (1.0 - 0.3 * dx * dx)
|
|
let side = exp(-(e * e) / (2.0 * 2.2 * 2.2))
|
|
let ce = max(e - 1.5, 0.0)
|
|
let ends = 0.5 * exp(-(ce * ce) / (2.0 * 0.75 * 0.75)) * step(1.0, e) * (1.0 - 0.4 * step(0.5, k))
|
|
var a = 0.72 * top * m1 + ends * body
|
|
a = a + (0.50 * p1 + 0.46 * p2 + 0.36 * p3 + 0.10 * p4) * side
|
|
let hc = mix(mix(pale, glow, p1), dim, p2 + p3 + p4)
|
|
o = Finish.over(o, vec4(hc, 1.0) * clamp(a * halo, 0.0, 1.0) * lit)
|
|
// The core and its two end columns; the lower row takes a little of
|
|
// the glow's own hue.
|
|
let hue = glow / max(max(glow.x, glow.y), max(glow.z, 0.001))
|
|
let lower = core * mix(vec3(1.0, 1.0, 1.0), hue, 0.35)
|
|
let inside = 1.0 - step(0.25, e)
|
|
let endc = step(0.25, e) * (1.0 - step(1.0, e))
|
|
let cc = mix(mix(core, lower, step(0.5, k)), mix(core, glow, 0.4), endc)
|
|
return Finish.over(o, vec4(cc, 1.0) * (inside + 0.95 * endc) * body * lit)
|
|
}
|
|
|
|
// The three-quarter arc a waiting button turns in place of its label.
|
|
let lq_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(0.94, 0.98, 1.0, amt))
|
|
return Finish.over(dst, sdf.result)
|
|
}
|
|
|
|
// ---------------------------------------------------------------- buttons
|
|
|
|
// Every button is the same slim cap: 24 points on a 13 point line, 12
|
|
// points either side. Its shadow falls outside the box, so the box is the
|
|
// outline and nothing is paid in padding.
|
|
mod.widgets.ButtonFlat.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 3.0 bottom: 3.0}
|
|
mod.widgets.ButtonFlat.min_height = 24.0
|
|
// Glyphs sit at 14 points so an icon never makes a cap taller than the row;
|
|
// an icon alone gets a square cap.
|
|
mod.widgets.ButtonFlat.icon_walk.width = 14.0
|
|
mod.widgets.ButtonIcon.padding = mod.turtle.Inset{left: 5.0 right: 5.0 top: 3.0 bottom: 3.0}
|
|
mod.widgets.ButtonFlatIcon.padding = mod.turtle.Inset{left: 5.0 right: 5.0 top: 3.0 bottom: 3.0}
|
|
mod.widgets.ButtonFlatterIcon.padding = mod.turtle.Inset{left: 5.0 right: 5.0 top: 3.0 bottom: 3.0}
|
|
// The caps that carry a lamp lift their label one device pixel, so the lamp
|
|
// can stand two rows off the bottom rim and still clear the descenders.
|
|
mod.widgets.ButtonPrimary.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 2.3333 bottom: 3.6667}
|
|
mod.widgets.ButtonSecondary.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 2.3333 bottom: 3.6667}
|
|
mod.widgets.ButtonDanger.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 2.3333 bottom: 3.6667}
|
|
// The short rungs keep their own heights under the row minimum.
|
|
mod.widgets.ButtonXs.min_height = 0.0
|
|
mod.widgets.ButtonSm.min_height = 0.0
|
|
// The smallest rung's label one step down, so its cap height is a third of
|
|
// its cap like every other rung's.
|
|
mod.widgets.ButtonXs.draw_text.text_style.font_size = 6.5
|
|
mod.widgets.ButtonFlat.draw_bg.border_radius = uniform(1.5)
|
|
mod.widgets.ButtonFlat.draw_bg.border_size = uniform(0.34)
|
|
mod.widgets.ButtonFlat.clip_x = false
|
|
mod.widgets.ButtonFlat.clip_y = false
|
|
mod.widgets.ButtonFlat.draw_bg.vertex = fn() {
|
|
let m = 7.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 cap and its states, shared by every button template; `kind` picks the
|
|
// variant: 0 seated cap, 1 raised cap, 2 primary (lit LED), 3 secondary (dark
|
|
// LED slot), 4 tonal (tinted face), 5 danger (red LED), 6 outline, 7 flatter.
|
|
let lq_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 = 7.0
|
|
let px = lq_px(dpi)
|
|
let p = pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
let lo = vec2(lq_snap(0.0, o.x, px), lq_snap(0.0, o.y, px))
|
|
let hi = vec2(lq_snap(size.x, o.x, px), lq_snap(size.y, o.y, px))
|
|
let c = (lo + hi) * 0.5
|
|
let h = (hi - lo) * 0.5
|
|
let r = 3.0
|
|
var res = vec4(0.0, 0.0, 0.0, 0.0)
|
|
if kind > 5.5 {
|
|
// Outline: no face, the black ring cut into the ground with its lip;
|
|
// under the hand a faint cap shows, held it sinks.
|
|
let d = Material.sd_box(p, c, h, r)
|
|
if kind > 6.5 {
|
|
// Flatter: nothing at rest.
|
|
if hover + down + focus > 0.001 {
|
|
res = lq_cap(p, lo, hi, r, px, hover, down, disabled, 0.0, 0.0, vec3(0.0, 0.0, 0.0), vec4(0.0, 0.0, 0.0, 0.0), 1.0) * clamp(max(hover, down), 0.0, 1.0)
|
|
}
|
|
} else {
|
|
var ring = Finish.band(d, 0.0, px, px)
|
|
if dash > 0.0 {
|
|
ring = ring * step(0.5, fract((p.x + p.y) / (dash * 2.0)))
|
|
}
|
|
let lip = Finish.ring_out(d, 0.0, px, px) * step(c.y + h.y * 0.5, p.y)
|
|
let inner = Finish.band(d, px, px * 2.0, px) * step(p.y, c.y - h.y * 0.5)
|
|
// The ring is chrome: a pale bezel line with the dark cut inside
|
|
// it and the chrome lip on the ground under the foot.
|
|
res = vec4(0.70, 0.52, 0.54, 1.0) * lip * 0.9
|
|
res = Finish.over(res, vec4(0.0, 0.0, 0.0, 1.0) * inner * 0.35)
|
|
let chrome = mix(vec3(0.80, 0.62, 0.64), vec3(0.50, 0.34, 0.36), clamp((p.y - lo.y) / max(hi.y - lo.y, 1.0), 0.0, 1.0))
|
|
res = Finish.over(res, vec4(chrome, 1.0) * ring)
|
|
res = Finish.over(res, vec4(0.110, 0.016, 0.020, 1.0) * Finish.band(d, px, px * 2.0, px))
|
|
if hover + down > 0.001 {
|
|
res = Finish.over(res, lq_cap(p, lo, hi, r, px, hover, down, disabled, 0.0, 0.0, vec3(0.0, 0.0, 0.0), vec4(0.0, 0.0, 0.0, 0.0), 1.0) * clamp(max(hover, down), 0.0, 1.0) * 0.85)
|
|
}
|
|
}
|
|
let fr = Finish.ring_out(d, 0.0, px, px) * 0.9 + Finish.glow(d - px, 0.7) * 0.3 * (1.0 - Finish.cover(d, px))
|
|
res = Finish.over(res, vec4(0.40, 0.62, 0.84, 1.0) * clamp(fr, 0.0, 1.0) * focus * (1.0 - disabled))
|
|
} else {
|
|
var tint = vec3(0.0, 0.0, 0.0)
|
|
var rim = vec4(0.0, 0.0, 0.0, 0.0)
|
|
let lit = 1.0 - disabled
|
|
if kind > 3.5 && kind < 4.5 {
|
|
// The tonal cap: the lacquer a breath deeper and cooler.
|
|
tint = vec3(-0.035, -0.006, 0.010)
|
|
}
|
|
if kind > 1.5 && kind < 2.5 {
|
|
// Primary: the LED's spill lies along the bottom inner rim, and
|
|
// a tight breath of it under the cap.
|
|
rim = vec4(0.40, 0.62, 0.84, 0.45 * lit)
|
|
let dg = Material.sd_box(p, c + vec2(0.0, 0.05 * (hi.y - lo.y)), h - vec2(2.0, 0.0), r)
|
|
let g = exp(-(max(dg, 0.0) * max(dg, 0.0)) / (2.0 * 1.2 * 1.2)) * (1.0 - Finish.cover(dg, px))
|
|
res = vec4(0.40, 0.62, 0.84, 1.0) * g * 0.18 * lit * (1.0 - down * 0.5)
|
|
}
|
|
if kind > 4.5 {
|
|
rim = vec4(0.95, 0.62, 0.30, 0.40 * lit)
|
|
}
|
|
var lift = 1.0
|
|
if kind < 0.5 {
|
|
lift = 0.0
|
|
}
|
|
res = Finish.over(res, lq_cap(p, lo, hi, r, px, hover, down, disabled, focus, lift, tint, rim, 1.0))
|
|
if kind > 1.5 && kind < 3.5 || kind > 4.5 {
|
|
// The lamp: the chosen segment's lamp (`lq_lamp`), its core two
|
|
// device pixels whose middle stands 4 points above the bottom
|
|
// edge, 16 device pixels wide with a light end column either
|
|
// side; the label (raised one device pixel on these caps)
|
|
// clears its pale top row by two rows of face. Held, its halo
|
|
// halves. Unlit (Secondary, or disabled) it is the dark glass.
|
|
let lc = vec2(c.x, lq_snap(hi.y - 4.0, o.y, px))
|
|
let hs = vec2(min(5.5, h.x * 0.4), px)
|
|
let lw = min(5.3333, h.x * 0.4)
|
|
let xs = vec2(lq_snap(c.x - lw, o.x, px), lq_snap(c.x + lw, o.x, px))
|
|
var on = lit
|
|
var core = vec3(0.910, 0.918, 0.922)
|
|
var glow = vec3(0.405, 0.593, 0.752)
|
|
if kind > 2.5 && kind < 3.5 {
|
|
on = 0.0
|
|
}
|
|
if kind > 4.5 {
|
|
// Danger: the same lamp, amber, so it reads on the red.
|
|
core = vec3(1.0, 0.93, 0.78)
|
|
glow = vec3(1.0, 0.58, 0.18)
|
|
}
|
|
let dark = lq_led(res, p, lc, hs, 0.0, 0.0, px, core, glow)
|
|
let lamp = lq_lamp(res, p, xs, lc.y - px, px, 1.0, 1.0 - down * 0.5, core, glow)
|
|
res = mix(dark, lamp, on)
|
|
}
|
|
}
|
|
if loading > 0.001 {
|
|
// The arc ends two device pixels or more above where a lamp's rim
|
|
// stands: a smaller ring, lifted one device pixel.
|
|
let rad = max(min(h.x, h.y) - 7.25, 2.0)
|
|
res = lq_spinner(res, p, c - vec2(0.0, px), rad, anim, loading, 1.5)
|
|
}
|
|
return res
|
|
}
|
|
|
|
mod.widgets.ButtonFlat.draw_bg.pixel = fn() {
|
|
return self.host_over(lq_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), 7.0)
|
|
}
|
|
mod.widgets.Button.draw_bg.pixel = fn() {
|
|
return self.host_over(lq_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), 7.0)
|
|
}
|
|
mod.widgets.ButtonPrimary.draw_bg.pixel = fn() {
|
|
return self.host_over(lq_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), 7.0)
|
|
}
|
|
mod.widgets.ButtonSecondary.draw_bg.pixel = fn() {
|
|
return self.host_over(lq_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), 7.0)
|
|
}
|
|
mod.widgets.ButtonTertiary.draw_bg.pixel = fn() {
|
|
return self.host_over(lq_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), 7.0)
|
|
}
|
|
mod.widgets.ButtonDanger.draw_bg.pixel = fn() {
|
|
return self.host_over(lq_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), 7.0)
|
|
}
|
|
mod.widgets.ButtonOutline.draw_bg.pixel = fn() {
|
|
return self.host_over(lq_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), 7.0)
|
|
}
|
|
mod.widgets.ButtonFlatter.draw_bg.pixel = fn() {
|
|
return self.host_over(lq_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), 7.0)
|
|
}
|
|
|
|
// Label ink per state: caps #f6e8e6, the primary's lit legend near white,
|
|
// every variant dims to #b07a7c when disabled.
|
|
mod.widgets.ButtonPrimary.draw_text.color = #ffffff
|
|
mod.widgets.ButtonPrimary.draw_text.color_hover = #ffffff
|
|
mod.widgets.ButtonPrimary.draw_text.color_down = #ffffff
|
|
mod.widgets.ButtonPrimary.draw_text.color_focus = #ffffff
|
|
mod.widgets.ButtonPrimary.draw_text.color_disabled = #b07a7c
|
|
mod.widgets.ButtonPrimary.draw_icon.color = #ffffff
|
|
mod.widgets.ButtonSecondary.draw_text.color = #f6e8e6
|
|
mod.widgets.ButtonSecondary.draw_text.color_hover = #fbf0ee
|
|
mod.widgets.ButtonSecondary.draw_text.color_down = #ecdcda
|
|
mod.widgets.ButtonSecondary.draw_text.color_focus = #f6e8e6
|
|
mod.widgets.ButtonSecondary.draw_icon.color = #f6e8e6
|
|
mod.widgets.ButtonTertiary.draw_text.color = #f6e8e6
|
|
mod.widgets.ButtonTertiary.draw_text.color_hover = #fbf0ee
|
|
mod.widgets.ButtonTertiary.draw_text.color_down = #ecdcda
|
|
mod.widgets.ButtonTertiary.draw_text.color_focus = #f6e8e6
|
|
mod.widgets.ButtonTertiary.draw_icon.color = #f6e8e6
|
|
mod.widgets.ButtonDanger.draw_text.color = #ffffff
|
|
mod.widgets.ButtonDanger.draw_text.color_hover = #ffffff
|
|
mod.widgets.ButtonDanger.draw_text.color_down = #ffffff
|
|
mod.widgets.ButtonDanger.draw_text.color_focus = #ffffff
|
|
mod.widgets.ButtonDanger.draw_icon.color = #ffffff
|
|
mod.widgets.ButtonOutline.draw_text.color = #f6e8e6
|
|
mod.widgets.ButtonOutline.draw_text.color_hover = #fbf0ee
|
|
mod.widgets.ButtonOutline.draw_text.color_down = #ecdcda
|
|
mod.widgets.ButtonOutline.draw_text.color_focus = #f6e8e6
|
|
mod.widgets.ButtonOutline.draw_text.color_disabled = #b07a7c
|
|
mod.widgets.ButtonOutline.draw_icon.color = #f6e8e6
|
|
// The trailing icon takes the cap's ink too, never the drawing's own black.
|
|
mod.widgets.ButtonFlat.draw_icon_end.color = #f6e8e6
|
|
|
|
// ---------------------------------------------------------------- stud
|
|
|
|
// The chrome pyramid stud, after his photograph of pyramid studs: the
|
|
// toggle's knob, the rocker switch's raised half, the slide switch's knob
|
|
// and the smaller sliders' thumbs. Every one of them is drawn by `lq_stud`
|
|
// from the one material below, so they cannot drift apart; the fader's ring
|
|
// pull is the same silver.
|
|
|
|
// The stud's material, as uniforms on every control that carries it, so the
|
|
// tweaker moves it live; the numbers are his export's. The key light is
|
|
// screen space (x right, y down, z toward the viewer), then its intensity;
|
|
// the highlight, its roughness, the sheen and the rim toward grazing; the
|
|
// occlusion and how far up the walls it reaches; the reflection of the
|
|
// studio and how near the eye is over the stud; metal, spun grain and glow;
|
|
// the exposure in stops and the highlight roll-off; the cast shadow's
|
|
// strength and blur and the contact shadow; the height in points of the 25
|
|
// point stud on the fader (a smaller stud stands as much lower and its
|
|
// shadow's blur shrinks with it, so its facets slope the same). The inks:
|
|
// his silver, white light, his cool shade, and a dark floor for what his
|
|
// metal mirrors where the studio does not reach.
|
|
let lq_stud_material = fn(d) {
|
|
d.cap_light = uniform(vec3(-0.25, -0.65, 0.7))
|
|
d.cap_light_intensity = uniform(1.0)
|
|
d.cap_specular = uniform(1.0)
|
|
d.cap_roughness = uniform(0.86)
|
|
d.cap_gloss = uniform(0.56)
|
|
d.cap_rim = uniform(0.0)
|
|
d.cap_ao = uniform(0.7)
|
|
d.cap_ao_reach = uniform(0.9)
|
|
d.cap_reflection = uniform(1.0)
|
|
d.cap_reflection_perspective = uniform(0.48)
|
|
d.cap_metal = uniform(1.0)
|
|
d.cap_spun = uniform(0.0)
|
|
d.cap_glow = uniform(0.0)
|
|
d.cap_exposure = uniform(0.0)
|
|
d.cap_rolloff = uniform(0.47)
|
|
d.cap_shadow = uniform(0.55)
|
|
d.cap_shadow_blur = uniform(9.0)
|
|
d.cap_contact = uniform(0.55)
|
|
d.cap_height = uniform(14.0)
|
|
d.handle_color = uniform(#c4c7cc)
|
|
d.light_color = uniform(#ffffff)
|
|
d.shadow_color = uniform(#3a3c40)
|
|
d.sky_ground_color = uniform(#3a3c40)
|
|
}
|
|
|
|
// How far the quad of a control carrying the stud reaches past its rect,
|
|
// in points: the throw of the stud's shadow from its height along the key
|
|
// light, the blur at its tip (the engine holds it between 0.35 and 1 times
|
|
// the height), the glow's reach when there is one, and four points for the
|
|
// contact shadow's tail, a slot's lip past its ends and the focus ring's
|
|
// breath. The stud itself stays in the rect: on the fader never wider than
|
|
// the fader, its ends at the track's ends at the stops, its point magnified
|
|
// by the perspective by 16% of its half size at most.
|
|
let lq_stud_room = fn(l: vec3, height: float, blur: float, glow: float, cap: float) -> float {
|
|
let n = normalize(l)
|
|
let rise = max(height, 0.0)
|
|
let reach = rise * 0.7 * length(n.xy) / max(n.z, 0.25)
|
|
let tip = clamp(blur, max(rise * 0.35, 1.0), max(rise, 1.0))
|
|
return reach + tip + step(0.001, glow) * max(cap * 0.4, 1.0) * 4.0 + 4.0
|
|
}
|
|
|
|
// The studio his photographs of chrome pyramid studs were taken in, as the
|
|
// stud's facets mirror it: a big soft box above and to the left, a dark
|
|
// wall to the right, a white sweep below, a grey room behind, the camera
|
|
// dark overhead and the table a step darker below the horizon. `r` the
|
|
// mirrored direction in screen space (x right, y down, z toward the
|
|
// viewer). Its edges are sharp, as a studio's are, so as the eye's angle
|
|
// changes across the stud they cross its facets in the light and dark
|
|
// splits his photographs show.
|
|
let lq_studio = fn(r: vec3) -> vec3 {
|
|
let a = atan2(r.y, r.x)
|
|
var c = vec3(0.66, 0.67, 0.69)
|
|
c = mix(c, vec3(0.94, 0.95, 0.96), smoothstep(0.85, 1.0, a) * (1.0 - smoothstep(2.45, 2.6, a)))
|
|
c = mix(c, vec3(1.08, 1.08, 1.08), smoothstep(-2.4, -2.25, a) * (1.0 - smoothstep(-0.95, -0.8, a)))
|
|
c = mix(c, vec3(0.30, 0.30, 0.32), smoothstep(-0.7, -0.55, a) * (1.0 - smoothstep(0.7, 0.85, a)))
|
|
c = c * mix(0.9, 1.0, smoothstep(-0.14, -0.02, r.z))
|
|
return mix(c, vec3(0.38, 0.38, 0.40), smoothstep(0.5, 0.7, r.z))
|
|
}
|
|
|
|
// The cove cap's solid at `w` (stud-local points), his curves as the bench
|
|
// reads them: along, from the centre to one end and mirrored, the ridge
|
|
// falling in a cove to the foot; across, over the half width, level to 0.94
|
|
// and then down; the edge over the whole smaller half in from the footprint
|
|
// `fp`, a straight rise, so the four facets of the stud are planes (his
|
|
// export's rise was soft); his plan is level at 1, which is no plan. No
|
|
// ribs, no mark, no cut.
|
|
let lq_cove = fn(w: vec2, hs: vec2, fp: vec3, shape: vec4) -> vec4 {
|
|
let c_along = Cap.mirror(Cap.curve(abs(w.x) / hs.x, 2.0, vec2(0.0, 1.0), vec2(0.3333, 0.0), vec2(-0.0049, 0.255), vec2(1.0, 0.0), vec2(0.0049, -0.255), vec2(0.0, 0.0), vec2(1.0, 0.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 0.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 0.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 0.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 0.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 0.0)), w.x)
|
|
let c_across = Cap.curve(abs(w.y) / hs.y, 3.0, vec2(0.0, 1.0), vec2(0.1567, 0.0), vec2(0.0, 0.0), vec2(0.94, 1.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 0.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 0.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 0.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 0.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 0.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 0.0))
|
|
let c_edge = Cap.curve(Cap.edge_x(fp.x, hs, shape.y), 2.0, vec2(0.0, 0.0), vec2(0.3333, 0.3333), vec2(-0.3333, -0.3333), vec2(1.0, 1.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 1.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 1.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 1.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 1.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 1.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 1.0))
|
|
let zero4 = vec4(0.0, 0.0, 0.0, 0.0)
|
|
return Cap.field(w, hs, fp, c_along, c_across, c_edge, vec2(1.0, 0.0), shape, zero4, zero4, zero4, zero4)
|
|
}
|
|
|
|
// THE STUD, drawn by the cap engine (`mod.sdf.Cap`) from his cove cap export
|
|
// (the slider bench's "Cove cap, silver", a wedge, 2026-10-03): a
|
|
// square-based pyramid, four flat facets meeting in crisp creases at its
|
|
// point in the middle, its base corners nearly square, the point brought
|
|
// toward the eye by its height. The studs make the facets flat (a straight
|
|
// edge rise over the whole smaller half), the creases crisp (blend 0.02),
|
|
// the corners 0.12, and drop the turned grain. His light, highlight, sheen
|
|
// and rim, with the studio mirrored in place of the plain sky: the shade
|
|
// without the sky and without the highlight, the studio laid in as his
|
|
// metal mirrors it, darkened toward the foot by his occlusion, and the
|
|
// highlight put back over it.
|
|
//
|
|
// `q` the point in the stud's own frame in points (x along the travel, as
|
|
// the fader's track space has it), `hs` its half size, `rise` its height in
|
|
// points and `blur` its shadow's, `press` 1 while it is held, `reach` how
|
|
// far the quad runs past it (nothing farther out is worked out), `vert` 1
|
|
// on a fader stood on end. The material: `light_dir` the key light; `k1`
|
|
// the highlight, its roughness, the sheen and the rim; `k2` the occlusion,
|
|
// its reach, the reflection and how near the eye is; `k3` metal, spun
|
|
// grain, glow and the light's intensity; `k4` the exposure, the roll-off,
|
|
// the cast shadow and the contact shadow; then the inks.
|
|
//
|
|
// It is laid over what lies under it as the bench lays a cap: `under_ink`
|
|
// what is there lit on black, `keep` the share of the ground that still
|
|
// shows through it. The stud's shadow darkens both (the ground by the
|
|
// shadow ink's mean, as one alpha must), a glow adds its light, `toned` 1
|
|
// tones that as the bench tones the slot, and the stud covers all of it.
|
|
// Hovered, it lifts as the sheet's chrome caps do, twice as much when it
|
|
// stands less than 23.5 points tall on screen; held, it sinks half a point
|
|
// and the lift goes; focused, the sheet's thin ice-blue ring round its
|
|
// foot and a breath of it; disabled, it dulls as the sheet's caps do and
|
|
// throws no shadow. Premultiplied.
|
|
let lq_stud = fn(under_ink: vec3, keep: float, toned: float, q: vec2, hs: vec2, rise: float, blur: float, press: float, reach: float, px: float, vert: float, hover: float, focus: float, disabled: float, light_dir: vec3, k1: vec4, k2: vec4, k3: vec4, k4: vec4, body: vec3, light_ink: vec3, shadow_ink: vec3, sky: vec3) -> vec4 {
|
|
let live = 1.0 - disabled
|
|
let l = normalize(light_dir)
|
|
let lt = Cap.light(l, vert)
|
|
let ev = pow(2.0, k4.x)
|
|
let ro = k4.y
|
|
// Corner 0.12 of the smaller half, the edge over the whole of it, blend
|
|
// 0.02, the height less the half point a press sinks it, the point
|
|
// brought 16% toward the eye.
|
|
let shape = vec4(0.12, 1.0, 0.02, max(rise - 0.5 * press, 0.5))
|
|
let persp = 0.16
|
|
let r = shape.x * min(hs.x, hs.y)
|
|
// `cd` the footprint's own outline, which the focus ring and the shadow
|
|
// follow; `fp` the outline as drawn, the magnified top's where it
|
|
// overhangs. The plan curve is level at 1: no pinch, the engine's none.
|
|
let cd = Cap.sd_cap(q, hs, r, vec2(1.0, 0.0)).x
|
|
var fp = Cap.sd_cap(q, hs, r, vec2(1.0, 0.0))
|
|
var cov = 0.0
|
|
var cap = body
|
|
var under = 0.0
|
|
var halo = 0.0
|
|
// Only a pixel within reach of the stud and what it throws runs the rest.
|
|
let far = abs(q) - hs
|
|
if max(far.x, far.y) < reach + 2.0 {
|
|
// Where the stud or its magnified top can lie: the solid read three
|
|
// times, each at the point the height read before brings toward the
|
|
// eye. Farther out every read is the plain footprint's.
|
|
if max(far.x - persp * hs.x, far.y - persp * hs.y) < px {
|
|
var w = q / (1.0 + persp)
|
|
fp = Cap.sd_cap(w, hs, r, vec2(1.0, 0.0))
|
|
var f = lq_cove(w, hs, fp, shape)
|
|
w = Cap.perspective(q, f.x, fp.x, persp)
|
|
fp = Cap.sd_cap(w, hs, r, vec2(1.0, 0.0))
|
|
f = lq_cove(w, hs, fp, shape)
|
|
w = Cap.perspective(q, f.x, fp.x, persp)
|
|
fp = Cap.sd_cap(w, hs, r, vec2(1.0, 0.0))
|
|
f = lq_cove(w, hs, fp, shape)
|
|
let n = Cap.normal(f.yz, shape.w, vert)
|
|
let v = Cap.view(q, hs, k2.w, vert)
|
|
let lit = Cap.shade(n, l, v, f.x, body, 0.0, 0.0, 1.0, k1.x, k1.y, k1.z, k1.w, k2.x, k2.y, 0.0, k3.x, k3.w, body, light_ink, shadow_ink, sky)
|
|
let dull = Cap.shade(n, l, v, f.x, body, 0.0, 0.0, 1.0, 0.0, k1.y, 0.0, 0.0, k2.x, k2.y, 0.0, k3.x, k3.w, body, light_ink, shadow_ink, sky)
|
|
let rv = -v - n * (2.0 * dot(n, -v))
|
|
let occ = 1.0 - k2.x * pow(1.0 - clamp(f.x, 0.0, 1.0), max(k2.y, 0.2))
|
|
let mirror = lq_studio(rv) * mix(vec3(1.0, 1.0, 1.0), body, k3.x) * mix(1.0, occ, 0.35)
|
|
cap = mix(dull, mirror, k2.z) + (lit - dull)
|
|
let grain = Cap.spun(q, hs, lt.xy) * k3.y
|
|
cap = Cap.in_shadow(Cap.in_light(cap, clamp(grain, 0.0, 1.0) * 0.55, light_ink), clamp(-grain, 0.0, 1.0) * 0.42, shadow_ink)
|
|
cap = Cap.emit(cap, f.x, body, light_ink, k3.z)
|
|
cov = Cap.cover(fp.x, px)
|
|
}
|
|
// Under it, the cast shadow, thrown from his top outline (0.451 of
|
|
// the length, 0.5098 of the width, its corner by the bench's rule at
|
|
// this size), and the contact shade. Disabled, the cast shadow goes
|
|
// and the contact thins, as under the sheet's caps.
|
|
let top_x = hs.x * 0.451
|
|
let top_y = hs.y * 0.5098
|
|
let top_r = min(r + max(hs.x - top_x, hs.y - top_y), min(top_x, top_y))
|
|
let top = vec3(0.451, 0.5098, top_r / max(min(top_x, top_y), 0.01))
|
|
let sh = Cap.shadow(q, hs, r, top, lt, shape.w, blur, k4.z * live, px)
|
|
let ct = Cap.contact(fp.x, max(shape.w * 0.3, 1.0), k4.w * (1.0 - 0.6 * disabled))
|
|
under = clamp(sh + ct, 0.0, 1.0) * (1.0 - cov)
|
|
halo = Cap.glow(fp.x, hs, k3.z) * 0.9 * (1.0 - cov)
|
|
}
|
|
|
|
// The stud toned as the bench tones, lifted under the hand, dulled when
|
|
// disabled.
|
|
var face = Cap.tone(cap, ev, ro)
|
|
let small = 1.0 - step(23.5, mix(hs.y, hs.x, vert) * 2.0)
|
|
face = face + vec3(0.04, 0.04, 0.042) * (1.0 + small) * hover * (1.0 - press) * live
|
|
let fl = Finish.lum(face)
|
|
face = mix(face, mix(face, vec3(fl, fl, fl), 0.45) * 0.8, disabled)
|
|
// Laid over what lies under it, in the bench's order.
|
|
let dim = mix(vec3(1.0, 1.0, 1.0), shadow_ink, under)
|
|
let kept = keep * (1.0 - under * (1.0 - (shadow_ink.x + shadow_ink.y + shadow_ink.z) / 3.0))
|
|
var low = under_ink * dim + body * halo
|
|
if toned > 0.5 {
|
|
low = Cap.tone(low, ev, ro)
|
|
}
|
|
var res = vec4(low, 1.0 - kept)
|
|
res = mix(res, vec4(face, 1.0), cov)
|
|
let ring = Finish.ring_out(cd, 0.0, px, px) * 0.9 + Finish.glow(cd - px, 0.7) * 0.35 * (1.0 - Finish.cover(cd, px))
|
|
return Finish.over(res, vec4(0.498, 0.706, 0.910, 1.0) * clamp(ring, 0.0, 1.0) * focus * live)
|
|
}
|
|
|
|
// ---------------------------------------------------------------- selection
|
|
|
|
// A well cut into the lacquer: a chrome rim of one device pixel, dim at the
|
|
// top and bright at the foot, a dark cut inside it, a wine-black floor with
|
|
// an inner shadow under its top edge, and a faint chrome lip on the ground
|
|
// under its lower edge. Premultiplied; `fill` is the floor, `lip` 0 drops
|
|
// the lip.
|
|
let lq_well = fn(p: vec2, lo: vec2, hi: vec2, r: float, px: float, fill: vec3, focus: float, disabled: float, lip: 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 res = vec4(0.70, 0.52, 0.54, 1.0) * Finish.ring_out(d, 0.0, px, px) * step(c.y, p.y) * lip * 0.45 * (1.0 - disabled * 0.6)
|
|
// The floor lifts a little toward the foot; under the top edge the
|
|
// inner shadow, black for two device pixels and gone in about five.
|
|
var col = fill + vec3(0.030, 0.012, 0.014) * t
|
|
let top = max(p.y - lo.y - px * 2.0, 0.0)
|
|
col = mix(col, vec3(0.012, 0.0, 0.002), 0.75 * exp(-top / (1.6 * px)))
|
|
col = mix(col, mix(col, vec3(0.20, 0.12, 0.13), 0.5), disabled)
|
|
// The dark cut inside the rim, then the rim itself.
|
|
// A chrome rim of two device pixels on a wide well (one on a groove),
|
|
// the dark cut inside it.
|
|
let rw = mix(px, px * 2.0, step(10.0, hi.y - lo.y))
|
|
col = mix(col, vec3(0.030, 0.004, 0.008), Finish.band(d, rw, rw + px, px))
|
|
let rimc = mix(vec3(0.56, 0.42, 0.44), vec3(0.80, 0.70, 0.72), smoothstep(0.25, 1.0, t))
|
|
let rim_in = mix(rimc * 0.78, rimc, Finish.band(d, 0.0, px, px))
|
|
col = mix(col, mix(rim_in, vec3(0.36, 0.18, 0.20), disabled), Finish.band(d, 0.0, rw, px))
|
|
let a = Finish.cover(d, px)
|
|
res = Finish.over(res, vec4(col * a, a))
|
|
let ring = Finish.ring_out(d, 0.0, px, px) * 0.9 + Finish.glow(d - px, 0.7) * 0.35 * (1.0 - a)
|
|
res = Finish.over(res, vec4(0.498, 0.706, 0.910, 1.0) * clamp(ring, 0.0, 1.0) * focus * (1.0 - disabled))
|
|
return res
|
|
}
|
|
|
|
// 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 = 5.0
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
|
|
}
|
|
mod.widgets.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(14.0)
|
|
mod.widgets.CheckBoxFlat.draw_bg.border_radius = uniform(1.5)
|
|
mod.widgets.CheckBoxFlat.label_walk.margin.left = 14.0
|
|
|
|
// A lit mark: a pale core along the stroke's middle, the accent at its
|
|
// edges and a soft halo of the lamp's blue round it. `dk` is the distance
|
|
// to the stroke's edge, `w` its half width.
|
|
let lq_lit_mark = fn(dst: vec4, dk: float, w: float, px: float, on: float, disabled: float, error: float) -> vec4 {
|
|
let edge = mix(mix(vec3(0.227, 0.655, 0.965), vec3(0.94, 0.32, 0.23), error), vec3(0.294, 0.306, 0.341), disabled)
|
|
let core = mix(mix(vec3(0.88, 0.96, 1.0), vec3(1.0, 0.86, 0.82), error), vec3(0.36, 0.37, 0.40), disabled)
|
|
let glow = mix(vec3(0.40, 0.62, 0.84), vec3(0.72, 0.25, 0.18), error)
|
|
let x = max(dk, 0.0)
|
|
let g = (0.30 * exp(-(x * x) / (2.0 * 0.45 * 0.45)) + 0.14 * exp(-(x * x) / (2.0 * 1.3 * 1.3))) * (1.0 - Finish.cover(dk, px)) * (1.0 - disabled)
|
|
var res = Finish.over(dst, vec4(glow, 1.0) * g * on)
|
|
let ink = mix(edge, core, 1.0 - smoothstep(0.15 * w, 0.8 * w, dk + w))
|
|
return Finish.over(res, vec4(ink, 1.0) * Finish.cover(dk, px) * on)
|
|
}
|
|
|
|
// The check box: a 14 point polished chrome cap. Checked, it sinks into a
|
|
// dark well cast a little blue, and its mark is lit.
|
|
mod.widgets.CheckBoxFlat.draw_bg.pixel = fn() {
|
|
let m = 5.0
|
|
let px = lq_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(lq_snap(x0, o.x, px), lq_snap(y0, o.y, px))
|
|
let hi = vec2(lq_snap(x0 + sz, o.x, px), lq_snap(y0 + sz, o.y, px))
|
|
let c = (lo + hi) * 0.5
|
|
let r = min(self.border_radius * 2.0, sz * 0.5)
|
|
let on = max(self.active, self.mixed)
|
|
let held = max(max(self.down, self.pressed), on)
|
|
let tint = vec3(-0.006, 0.030, 0.062) * on * (1.0 - self.disabled)
|
|
var res = lq_capm(p, lo, hi, r, px, self.hover, held, self.disabled, self.focus, 1.0, tint, vec4(0.0, 0.0, 0.0, 0.0), 1.0, 2.0)
|
|
// The error: the outline turns the alt red.
|
|
let d = Material.sd_box(p, c, (hi - lo) * 0.5, r)
|
|
res = Finish.over(res, vec4(0.94, 0.32, 0.23, 1.0) * Finish.band(d, 0.0, px, px) * self.error)
|
|
// The mark: a check 8 points across in a 1.4 point stroke, lit; mixed
|
|
// is a dash of three device pixel rows, a pale core row between two of
|
|
// the accent, on the row through the middle of the well. The check
|
|
// stands one device pixel lower, in the well.
|
|
let cm = c + vec2(0.0, px * 0.5)
|
|
let dm = min(lq_seg(p, vec2(cm.x - 3.6, cm.y + 0.1), vec2(cm.x - 1.1, cm.y + 2.6)), lq_seg(p, vec2(cm.x - 1.1, cm.y + 2.6), vec2(cm.x + 3.7, cm.y - 2.8)))
|
|
let cy = lq_snap((lo.y + hi.y) * 0.5, o.y, px) + px * 0.5
|
|
let dd = lq_seg(p, vec2(c.x - 3.5, cy), vec2(c.x + 3.5, cy))
|
|
let dk = mix(dm - 0.7, dd - px * 1.5, self.mixed)
|
|
return lq_lit_mark(res, dk, mix(0.7, px * 1.5, self.mixed), px, on, self.disabled, self.error)
|
|
}
|
|
|
|
// The toggle: a wine-black 34 x 18 track, and for its knob the chrome
|
|
// pyramid stud (`lq_stud`), as tall as the track and square, its point in
|
|
// its middle, at the left end off and the right end on, travelling between
|
|
// them as the knob does (and following a drag). On, the track's floor takes
|
|
// the chosen segment's blue cast and the LED bar in the open end of the
|
|
// track is lit; off, the bar is a dark slot. The stud lifts under the hand,
|
|
// sinks while held, dulls when disabled; focused, the sheet's ice-blue ring
|
|
// lies round the track. The quad reaches as far as the stud's shadow does.
|
|
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
|
|
lq_stud_material(mod.widgets.ToggleFlat.draw_bg)
|
|
mod.widgets.ToggleFlat.draw_bg.vertex = fn() {
|
|
let m = lq_stud_room(self.cap_light, self.cap_height * self.size / 25.0, self.cap_shadow_blur * self.size / 25.0, self.cap_glow, self.size)
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
|
|
}
|
|
mod.widgets.ToggleFlat.draw_bg.pixel = fn() {
|
|
let m = lq_stud_room(self.cap_light, self.cap_height * self.size / 25.0, self.cap_shadow_blur * self.size / 25.0, self.cap_glow, self.size)
|
|
let px = lq_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(lq_snap(x0, o.x, px), lq_snap(y0, o.y, px))
|
|
let hi = vec2(lq_snap(x0 + tw, o.x, px), lq_snap(y0 + th, o.y, px))
|
|
let live = 1.0 - self.disabled
|
|
// On, the track's floor takes the chosen segment's blue cast.
|
|
let on = self.active * live
|
|
var res = lq_well(p, lo, hi, 2.5, px, mix(vec3(0.125, 0.043, 0.051), vec3(0.110, 0.080, 0.130), on), 0.0, self.disabled, 1.0)
|
|
// The stud: the track's height each way, travelling the track less its
|
|
// own length, its left edge on the pixel grid.
|
|
let kt = mix(self.active, self.drag_pos, self.drag)
|
|
let ks = hi.y - lo.y
|
|
let open = hi.x - lo.x - ks
|
|
let kx = lq_snap(x0 + (tw - ks) * kt, o.x, px)
|
|
let kc = vec2(kx + ks * 0.5, (lo.y + hi.y) * 0.5)
|
|
// The LED bar, 8 x 1.5 points, in the middle of the open end, on the
|
|
// side the stud has left.
|
|
let lc = vec2(mix(hi.x - open * 0.5, lo.x + open * 0.5, kt), lq_snap((lo.y + hi.y) * 0.5, o.y, px))
|
|
res = lq_led(res, p, lc, vec2(4.0, px), on, 0.8, px, vec3(0.94, 0.984, 1.0), vec3(0.40, 0.62, 0.84))
|
|
// The stud stands as much lower than the fader's 25 point one as it is
|
|
// smaller, and its shadow's blur shrinks with it.
|
|
let rise = self.cap_height * ks / 25.0
|
|
let blur = self.cap_shadow_blur * ks / 25.0
|
|
res = lq_stud(res.rgb, 1.0 - res.a, 0.0, p - kc, vec2(ks * 0.5, ks * 0.5), rise, blur, max(self.pressed, self.down) * live, m, px, 0.0, self.hover, 0.0, self.disabled, self.cap_light, vec4(self.cap_specular, self.cap_roughness, self.cap_gloss, self.cap_rim), vec4(self.cap_ao, self.cap_ao_reach, self.cap_reflection, self.cap_reflection_perspective), vec4(self.cap_metal, self.cap_spun, self.cap_glow, self.cap_light_intensity), vec4(self.cap_exposure, self.cap_rolloff, self.cap_shadow, self.cap_contact), self.handle_color.rgb, self.light_color.rgb, self.shadow_color.rgb, self.sky_ground_color.rgb)
|
|
// Focus: the sheet's thin ice-blue ring round the track, and a breath
|
|
// of it.
|
|
let d = Material.sd_box(p, (lo + hi) * 0.5, (hi - lo) * 0.5, 2.5)
|
|
let ring = Finish.ring_out(d, 0.0, px, px) * 0.9 + Finish.glow(d - px, 0.7) * 0.35 * (1.0 - Finish.cover(d, px))
|
|
return Finish.over(res, vec4(0.498, 0.706, 0.910, 1.0) * clamp(ring, 0.0, 1.0) * self.focus * live)
|
|
}
|
|
// The stud's middle runs from half the track's height in from one end to
|
|
// as far in from the other, so a mark on the knob stands on its point and a
|
|
// drag carries it under the pointer.
|
|
mod.widgets.ToggleFlat.draw_bg.knob_centred = uniform(1.0)
|
|
// A mark on the knob lies on the stud, whose lit facets are near white: a
|
|
// mark in a pale ink would vanish there, so it is cut into the chrome
|
|
// instead, in a dark of the stud's own; a dark ink stays as the page hands
|
|
// it. Premultiplied in and out.
|
|
let lq_knob_mark = fn(c: vec4) -> vec4 {
|
|
let ink = c.rgb / max(c.a, 0.0001)
|
|
let cut = smoothstep(0.3, 0.55, Finish.lum(ink))
|
|
return vec4(mix(ink, vec3(0.10, 0.105, 0.115), cut) * c.a, c.a)
|
|
}
|
|
mod.widgets.ToggleFlat.draw_icon_off.get_color = fn() {
|
|
let base = self.eval_gradient()
|
|
if self.color.x >= 0.0 {
|
|
return lq_knob_mark(vec4(self.color.rgb * self.color.a * base.a, self.color.a * base.a) * self.opacity)
|
|
}
|
|
return lq_knob_mark(base * self.opacity)
|
|
}
|
|
mod.widgets.ToggleFlat.draw_icon_on.get_color = fn() {
|
|
let base = self.eval_gradient()
|
|
if self.color.x >= 0.0 {
|
|
return lq_knob_mark(vec4(self.color.rgb * self.color.a * base.a, self.color.a * base.a) * self.opacity)
|
|
}
|
|
return lq_knob_mark(base * self.opacity)
|
|
}
|
|
mod.widgets.Toggle.draw_icon_off.get_color = fn() {
|
|
let base = self.eval_gradient()
|
|
if self.color.x >= 0.0 {
|
|
return lq_knob_mark(vec4(self.color.rgb * self.color.a * base.a, self.color.a * base.a) * self.opacity)
|
|
}
|
|
return lq_knob_mark(base * self.opacity)
|
|
}
|
|
mod.widgets.Toggle.draw_icon_on.get_color = fn() {
|
|
let base = self.eval_gradient()
|
|
if self.color.x >= 0.0 {
|
|
return lq_knob_mark(vec4(self.color.rgb * self.color.a * base.a, self.color.a * base.a) * self.opacity)
|
|
}
|
|
return lq_knob_mark(base * self.opacity)
|
|
}
|
|
|
|
// The rocker and slide switches keep their housing, legends and lamp as the
|
|
// sheet has always drawn them: the library's own drawing in the switch's
|
|
// rect, laid over the quad the sheet's small controls grow by five points
|
|
// each way (the switches take it from the check box), so on screen the
|
|
// housing stands that much wider and taller, its corner up and to the left
|
|
// of the rect's. Their moving part is the chrome pyramid stud (`lq_stud`),
|
|
// square in true points, standing where that drawing puts the rocker's
|
|
// raised half or the slide's knob, its shadow kept in the housing. Disabled,
|
|
// the whole switch fades, as the library's does.
|
|
mod.widgets.ToggleRocker.clip_x = false
|
|
mod.widgets.ToggleRocker.clip_y = false
|
|
mod.widgets.ToggleSlide.clip_x = false
|
|
mod.widgets.ToggleSlide.clip_y = false
|
|
lq_stud_material(mod.widgets.ToggleRocker.draw_bg)
|
|
lq_stud_material(mod.widgets.ToggleSlide.draw_bg)
|
|
mod.widgets.ToggleRocker.draw_bg.vertex = fn() {
|
|
let m = 5.0
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
|
|
}
|
|
mod.widgets.ToggleSlide.draw_bg.vertex = fn() {
|
|
let m = 5.0
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
|
|
}
|
|
|
|
// The rocker: the library's housing with the rocker in it split at the
|
|
// pivot, and of the rocker only the half pressed in, darker and lower with
|
|
// its legend (the "O" on the off half, the "I" on the on half, lit while
|
|
// on). The raised half is the stud, square, as wide as that half is drawn
|
|
// on screen, in its middle, so one half shows the stud and the other its
|
|
// legend. As the state turns the stud crosses to the other half.
|
|
mod.widgets.ToggleRocker.draw_bg.pixel = fn() {
|
|
let m = 5.0
|
|
let ext = self.rect_size + vec2(2.0 * m, 2.0 * m)
|
|
// `p` the pixel as the library's drawing reads it (the rect's own frame
|
|
// over the whole quad), `pt` the same pixel in true points from the
|
|
// rect's corner, `sc` how much larger the first stands on screen.
|
|
let p = self.pos * self.rect_size
|
|
let pt = self.pos * ext - vec2(m, m)
|
|
let sc = ext / self.rect_size
|
|
let px = lq_px(self.draw_pass.dpi_factor)
|
|
let live = 1.0 - self.disabled
|
|
let sz = vec2(self.size * self.pill_aspect, self.size)
|
|
let o0 = vec2((self.rect_size.x - sz.x) * self.mark_at_end, self.rect_size.y * 0.5 - sz.y * 0.5)
|
|
let lo = vec2(Finish.snap(self.rect_pos.x + o0.x, px), Finish.snap(self.rect_pos.y + o0.y, px)) - self.rect_pos
|
|
let hi = vec2(Finish.snap(self.rect_pos.x + o0.x + sz.x, px), Finish.snap(self.rect_pos.y + o0.y + sz.y, px)) - self.rect_pos
|
|
let c = (lo + hi) * 0.5
|
|
let h = (hi - lo) * 0.5
|
|
let k = min(self.border_radius * 0.5, h.y)
|
|
let housing = Material.sd_box(p, c, h, k)
|
|
let fill = self.color
|
|
.mix(self.color_focus, self.focus)
|
|
.mix(self.color_hover, self.hover)
|
|
.mix(self.color_disabled, self.disabled)
|
|
let stroke = self.border_color
|
|
.mix(self.border_color_focus, self.focus)
|
|
.mix(self.border_color_hover, self.hover)
|
|
.mix(self.border_color_error, self.error)
|
|
.mix(self.border_color_disabled, self.disabled)
|
|
// The rocker inside the housing; how far each half is pressed in
|
|
// follows `active`.
|
|
let ins = Finish.snap(self.border_size + self.knob_inset, px)
|
|
let hr = max(h - vec2(ins, ins), vec2(0.5, 0.5))
|
|
let rocker = Material.sd_box(p, c, hr, max(k - ins, 0.0))
|
|
let on_side = step(c.x, p.x)
|
|
let pressed = mix(1.0 - on_side, on_side, self.active)
|
|
let raised_col = vec4(mix(fill.rgb, self.rocker_color.rgb, self.rocker_color.a), max(fill.a, self.rocker_color.a))
|
|
let pressed_col = vec4(mix(fill.rgb, self.rocker_color_pressed.rgb, self.rocker_color_pressed.a), max(fill.a, self.rocker_color_pressed.a))
|
|
var cap = mix(raised_col, pressed_col, pressed)
|
|
let tilt = clamp(abs(p.x - c.x) / max(hr.x, 0.5), 0.0, 1.0)
|
|
cap = vec4(cap.rgb * mix(1.0, 0.9 + 0.14 * tilt, 1.0 - pressed), cap.a)
|
|
let top_edge = Finish.band(p.y - (c.y - hr.y), -px, 0.0, px)
|
|
cap = vec4(mix(cap.rgb, Finish.lift(cap.rgb, 0.18), top_edge * (1.0 - pressed)), cap.a)
|
|
let shade = 1.0 - smoothstep(0.0, hr.y * 0.9, p.y - (c.y - hr.y))
|
|
cap = vec4(cap.rgb * (1.0 - 0.28 * shade * pressed), cap.a)
|
|
let pivot = Finish.cover(abs(p.x - c.x) - px * 0.5, px)
|
|
cap = vec4(mix(cap.rgb, cap.rgb * 0.45, pivot), cap.a)
|
|
// The legends, a device pixel lower on the pressed half.
|
|
let drop = px * pressed
|
|
let at_o = vec2(c.x - hr.x * 0.5, c.y + drop)
|
|
let at_i = vec2(c.x + hr.x * 0.5, c.y + drop)
|
|
let ring = Finish.cover(abs(length(p - at_o) - hr.y * 0.34) - px * 0.6, px) * (1.0 - on_side)
|
|
let ix = Finish.snap(self.rect_pos.x + at_i.x, px) - self.rect_pos.x + px * 0.5
|
|
let bar = Finish.cover(max(abs(p.x - ix) - px * 0.5, abs(p.y - at_i.y) - hr.y * 0.38), px) * on_side
|
|
let legend = self.legend_color.mix(self.legend_on_color, self.active * on_side)
|
|
cap = vec4(mix(cap.rgb, legend.rgb, (ring + bar) * legend.a), cap.a)
|
|
var col = vec4(fill.rgb * fill.a, fill.a)
|
|
col = mix(col, vec4(stroke.rgb * stroke.a, stroke.a), Finish.band(housing, 0.0, max(self.border_size, px), px))
|
|
col = mix(col, vec4(cap.rgb * cap.a, cap.a), Finish.cover(rocker, px) * pressed)
|
|
let inside = Finish.cover(housing, px)
|
|
col = col * inside
|
|
// The stud: the middle of the raised half as it stands on screen,
|
|
// square, as wide as that half (no taller than the rocker), its edges
|
|
// on whole device pixels.
|
|
let at = vec2(c.x + hr.x * 0.5 * (1.0 - 2.0 * self.active), c.y) * sc - vec2(m, m)
|
|
let ss = max(floor(min(hr.x * sc.x, 2.0 * hr.y * sc.y) / px), 2.0) * px
|
|
let kc = vec2(lq_snap(at.x - ss * 0.5, self.rect_pos.x, px), lq_snap(at.y - ss * 0.5, self.rect_pos.y, px)) + vec2(ss * 0.5, ss * 0.5)
|
|
let rise = self.cap_height * ss / 25.0
|
|
let blur = self.cap_shadow_blur * ss / 25.0
|
|
let lit = lq_stud(col.rgb, 1.0 - col.a, 0.0, pt - kc, vec2(ss * 0.5, ss * 0.5), rise, blur, max(self.pressed, self.down) * live, 2.0 * h.x * sc.x, px, 0.0, self.hover, 0.0, self.disabled, self.cap_light, vec4(self.cap_specular, self.cap_roughness, self.cap_gloss, self.cap_rim), vec4(self.cap_ao, self.cap_ao_reach, self.cap_reflection, self.cap_reflection_perspective), vec4(self.cap_metal, self.cap_spun, self.cap_glow, self.cap_light_intensity), vec4(self.cap_exposure, self.cap_rolloff, self.cap_shadow, self.cap_contact), self.handle_color.rgb, self.light_color.rgb, self.shadow_color.rgb, self.sky_ground_color.rgb)
|
|
return mix(col, lit, inside) * (1.0 - 0.55 * self.disabled)
|
|
}
|
|
|
|
// The slide: no track -- on his word, the switch is the stud and its lamp
|
|
// standing on the lacquer, at the compact size the switch's own rect gives
|
|
// (the slot's place still sets where they stand, but it is not drawn). The
|
|
// lamp sits in the end the stud has left: a lit dot while on, an open ring
|
|
// while off. The stud is the library's knob square, its top and bottom on
|
|
// whole device pixels, as far clear of the slot's ends at both ends of its
|
|
// travel as of its long sides; it follows a drag, and its shadow falls on
|
|
// the lacquer. Disabled, the whole switch fades.
|
|
mod.widgets.ToggleSlide.draw_bg.pixel = fn() {
|
|
let m = 5.0
|
|
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
let px = lq_px(self.draw_pass.dpi_factor)
|
|
let o = self.rect_pos
|
|
let live = 1.0 - self.disabled
|
|
let sz = vec2(self.size * self.pill_aspect, self.size)
|
|
let o0 = vec2((self.rect_size.x - sz.x) * self.mark_at_end, self.rect_size.y * 0.5 - sz.y * 0.5)
|
|
let lo = vec2(lq_snap(o0.x, o.x, px), lq_snap(o0.y, o.y, px))
|
|
let hi = vec2(lq_snap(o0.x + sz.x, o.x, px), lq_snap(o0.y + sz.y, o.y, px))
|
|
let c = (lo + hi) * 0.5
|
|
let h = (hi - lo) * 0.5
|
|
// The knob's place, as the library works it out (the drag and the knob
|
|
// icons read `knob_centred` to agree): a whole number of device pixels,
|
|
// as many fewer than the slot as leaves the same clearance above and
|
|
// below, travelling the slot's length less its height.
|
|
let knob_t = mix(self.active, self.drag_pos, self.drag)
|
|
let slot_px = floor((hi.y - lo.y) / px + 0.5)
|
|
var knob_px = max(floor(2.0 * (h.y - self.border_size - self.knob_inset) / px), 2.0)
|
|
knob_px = knob_px - (slot_px - knob_px - 2.0 * floor((slot_px - knob_px) * 0.5))
|
|
let ks = knob_px * 0.5 * px
|
|
let k_top = lo.y + (slot_px - knob_px) * 0.5 * px
|
|
let kx = lq_snap(lo.x + h.y + 2.0 * (h.x - h.y) * knob_t - ks, o.x, px)
|
|
let kc = vec2(kx + ks, k_top + ks)
|
|
// The lamp in the end the stud left.
|
|
let free = vec2(mix(lo.x + 2.0 * h.x - h.y, lo.x + h.y, knob_t), c.y)
|
|
let mr = sz.y * 0.16
|
|
let dot = Finish.cover(length(p - free) - mr, px)
|
|
let ring = Finish.cover(abs(length(p - free) - mr) - px * 0.6, px)
|
|
let mark = mix(ring, dot, self.active)
|
|
let mark_ink = self.mark_color.mix(self.mark_on_color, self.active)
|
|
let col = vec4(mark_ink.rgb * mark_ink.a * mark, mark_ink.a * mark)
|
|
let rise = self.cap_height * ks * 2.0 / 25.0
|
|
let blur = self.cap_shadow_blur * ks * 2.0 / 25.0
|
|
let lit = lq_stud(col.rgb, 1.0 - col.a, 0.0, p - kc, vec2(ks, ks), rise, blur, max(self.pressed, self.down) * live, 2.0 * h.x, px, 0.0, self.hover, 0.0, self.disabled, self.cap_light, vec4(self.cap_specular, self.cap_roughness, self.cap_gloss, self.cap_rim), vec4(self.cap_ao, self.cap_ao_reach, self.cap_reflection, self.cap_reflection_perspective), vec4(self.cap_metal, self.cap_spun, self.cap_glow, self.cap_light_intensity), vec4(self.cap_exposure, self.cap_rolloff, self.cap_shadow, self.cap_contact), self.handle_color.rgb, self.light_color.rgb, self.shadow_color.rgb, self.sky_ground_color.rgb)
|
|
return lit * (1.0 - 0.55 * self.disabled)
|
|
}
|
|
|
|
// The radio: a polished chrome ball. Chosen, it sinks a little (its shadow
|
|
// shortens, the chrome darkens) and an LED dot is lit in its middle.
|
|
mod.widgets.RadioButtonFlat.draw_bg.vertex = fn() {
|
|
let m = 5.0
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
|
|
}
|
|
mod.widgets.RadioButtonFlat.clip_x = false
|
|
mod.widgets.RadioButtonFlat.clip_y = false
|
|
mod.widgets.RadioButtonFlat.draw_bg.size = uniform(16.0)
|
|
mod.widgets.RadioButtonFlat.padding = mod.turtle.Inset{left: 8.0 right: 8.0 top: 4.0 bottom: 4.0}
|
|
mod.widgets.RadioButtonFlat.label_walk.margin.left = 14.0
|
|
mod.widgets.RadioButtonFlat.draw_bg.pixel = fn() {
|
|
let m = 5.0
|
|
let px = lq_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(lq_snap(0.0, o.x, px), lq_snap(y0, o.y, px))
|
|
let hi = vec2(lq_snap(sz, o.x, px), lq_snap(y0 + sz, o.y, px))
|
|
let c = (lo + hi) * 0.5
|
|
let on = self.active * (1.0 - self.disabled)
|
|
let rad = (hi.x - lo.x) * 0.5
|
|
// The socket: a round well the ball sits in, its chrome lip below.
|
|
let ds = length(p - c) - rad
|
|
var res = vec4(0.70, 0.52, 0.54, 1.0) * Finish.ring_out(ds, 0.0, px, px) * step(c.y + rad * 0.3, p.y) * 0.6
|
|
let st = clamp((p.y - lo.y) / max(hi.y - lo.y, 1.0), 0.0, 1.0)
|
|
let sock = mix(vec3(0.02, 0.0, 0.004), vec3(0.16, 0.05, 0.06), st)
|
|
res = Finish.over(res, vec4(sock, 1.0) * Finish.cover(ds, px))
|
|
res = lq_ball(res, p, c + vec2(0.0, px * 0.5), rad - 1.6, px, self.hover, max(self.down, on * 1.6), self.disabled, 0.7)
|
|
// The dot: 4.5 points, lit ice blue with its halo on the chrome.
|
|
let lit = lq_led(res, p, c, vec2(2.25 - px, 2.25 - px), 1.0, 1.0, px, vec3(0.90, 0.97, 1.0), vec3(0.40, 0.62, 0.84))
|
|
res = mix(res, lit, on)
|
|
let d = length(p - c) - rad
|
|
let ring = Finish.ring_out(d, 0.0, px, px) * 0.9 + Finish.glow(d - px, 0.7) * 0.35 * (1.0 - Finish.cover(d, px))
|
|
return Finish.over(res, vec4(0.498, 0.706, 0.910, 1.0) * clamp(ring, 0.0, 1.0) * self.focus * (1.0 - self.disabled))
|
|
}
|
|
|
|
// The radio tab: a segment of a joined strip. At rest a raised smoked
|
|
// chrome cap; chosen, it sinks, its legend lit and the lamp under it.
|
|
mod.widgets.RadioButtonTabFlat.draw_bg.vertex = fn() {
|
|
let m = 7.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: 4.0 bottom: 4.0}
|
|
mod.widgets.RadioButtonTabFlat.min_height = 24.0
|
|
mod.widgets.RadioButtonTabFlat.draw_bg.pixel = fn() {
|
|
let m = 7.0
|
|
let px = lq_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(lq_snap(0.0, o.x, px), lq_snap(0.0, o.y, px))
|
|
let hi = vec2(lq_snap(self.rect_size.x, o.x, px), lq_snap(self.rect_size.y, o.y, px))
|
|
let sel = self.active
|
|
let res = lq_steel(p, lo, hi, 3.0, px, o, self.hover, max(self.down, sel), sel, self.disabled, self.focus, 1.0, vec3(0.0, 0.0, 0.0))
|
|
// The lamp, centred under the legend (which stands centred, 1.7 points
|
|
// above the cap's middle): its core 6.5 points under the middle, so a
|
|
// descender keeps two rows of well above its pale top row.
|
|
let c = (lo + hi) * 0.5
|
|
let xs = vec2(lq_snap(c.x - 5.3333, o.x, px), lq_snap(c.x + 5.3333, o.x, px))
|
|
return lq_lamp(res, p, xs, lq_snap(c.y + 6.5, o.y, px), px, sel * (1.0 - self.disabled), 1.0, vec3(0.910, 0.918, 0.922), vec3(0.405, 0.593, 0.752))
|
|
}
|
|
mod.widgets.RadioButtonTabFlat.draw_text.color = #1e1618
|
|
mod.widgets.RadioButtonTabFlat.draw_text.color_hover = #120c0d
|
|
mod.widgets.RadioButtonTabFlat.draw_text.color_active = #d4eaff
|
|
mod.widgets.RadioButtonTabFlat.draw_text.color_disabled = #2e2426
|
|
// The ink follows the face, not the pointer: dark on the raised steel in
|
|
// every state, lit blue-white in the chosen well even under the hand.
|
|
mod.widgets.RadioButtonTabFlat.draw_text.pixel = fn() {
|
|
let raw = self.sample_text_pixel()
|
|
var ink = mix(vec3(0.118, 0.086, 0.094), vec3(0.831, 0.918, 1.0), self.active)
|
|
ink = mix(ink, mix(vec3(0.180, 0.141, 0.149), vec3(0.55, 0.60, 0.66), self.active), self.disabled)
|
|
return vec4(ink * raw.a, raw.a)
|
|
}
|
|
|
|
// ---------------------------------------------------------------- sliders
|
|
|
|
// A lit bar laid in a groove, like the lamp and like the chosen segment's
|
|
// legend: counted in whole device-pixel rows from `lo.y` (snapped), one
|
|
// pale core row and one row of the body under it in the middle of the
|
|
// floor, and on the rows round them a soft halo on the floor -- 15 % of the
|
|
// core's light on the next row, 7 % on the one after. Past the bar's end
|
|
// the halo runs on along the floor, falling by half every device pixel,
|
|
// between `gx.x` and `gx.y` (the floor's ends). `body` is the role colour; the core
|
|
// is it taken most of the way to white. Premultiplied over `dst`.
|
|
let lq_lit_bar = fn(dst: vec4, p: vec2, lo: vec2, hi: vec2, gx: vec2, px: float, body: vec3, disabled: float) -> vec4 {
|
|
let n = max(floor((hi.y - lo.y) / px + 0.5), 1.0)
|
|
let k = floor((p.y - lo.y) / px)
|
|
let rows = step(0.0, k) * step(k, n - 1.0)
|
|
let kc = max(floor(n * 0.5) - 1.0, 0.0)
|
|
let kb = min(kc + 1.0, n - 1.0)
|
|
// How far this row stands from the lit pair, in rows.
|
|
let dr = max(max(kc - k, k - kb), 0.0)
|
|
let core = mix(body, vec3(1.0, 1.0, 1.0), 0.8)
|
|
let deep = mix(body, vec3(0.12, 0.04, 0.05), 0.3)
|
|
var col = mix(core, deep, step(kb - 0.5, k) * step(kc + 0.5, kb))
|
|
col = mix(col, mix(body, vec3(0.42, 0.26, 0.28), 0.7), disabled)
|
|
let lit = step(dr, 0.5)
|
|
let halo_row = mix(0.0, 0.34, step(0.5, dr) * step(dr, 1.5)) + mix(0.0, 0.16, step(1.5, dr) * step(dr, 2.5))
|
|
let inx = step(lo.x, p.x) * step(p.x, hi.x)
|
|
let past = max(max(p.x - hi.x, lo.x - p.x), 0.0) / px
|
|
let fall = exp(-max(past - 0.5, 0.0) * 0.76) * (1.0 - inx) * step(gx.x, p.x) * step(p.x, gx.y)
|
|
let along = mix(0.34, halo_row, step(0.5, dr))
|
|
let g = (halo_row * inx + along * fall) * rows * (1.0 - disabled)
|
|
var res = Finish.over(dst, vec4(deep, 1.0) * g)
|
|
return Finish.over(res, vec4(col, 1.0) * lit * inx * rows)
|
|
}
|
|
let lq_fill = fn(dst: vec4, p: vec2, lo: vec2, hi: vec2, gx: vec2, px: float, disabled: float) -> vec4 {
|
|
return lq_lit_bar(dst, p, lo, hi, gx, px, vec3(0.50, 0.76, 1.0), disabled)
|
|
}
|
|
|
|
// A slider's thumb: the chrome pyramid stud (`lq_stud`) the fader, the
|
|
// toggles and the switches carry, from the same material, `s` points square
|
|
// with its middle at `c` (its leading edge put on the device pixel grid by
|
|
// the caller, so its edges stand crisp and it keeps one shape as it moves).
|
|
// It stands as much lower than the fader's 25 point stud as it is smaller,
|
|
// and its shadow's blur shrinks with it, so its facets slope the same. `k5`
|
|
// the material's height and shadow blur at 25 points; the rest as
|
|
// `lq_stud` takes them. Laid over `dst`, premultiplied, as the toggle lays
|
|
// its stud.
|
|
let lq_thumb = fn(dst: vec4, p: vec2, c: vec2, s: float, reach: float, px: float, hover: float, press: float, focus: float, disabled: float, light_dir: vec3, k1: vec4, k2: vec4, k3: vec4, k4: vec4, k5: vec2, body: vec3, light_ink: vec3, shadow_ink: vec3, sky: vec3) -> vec4 {
|
|
return lq_stud(dst.rgb, 1.0 - dst.a, 0.0, p - c, vec2(s * 0.5, s * 0.5), k5.x * s / 25.0, k5.y * s / 25.0, press, reach, px, 0.0, hover, focus, disabled, light_dir, k1, k2, k3, k4, body, light_ink, shadow_ink, sky)
|
|
}
|
|
// How far the quad of a slider whose thumb is `s` points square reaches
|
|
// past its rect: the stud's own room at that size.
|
|
let lq_thumb_room = fn(light_dir: vec3, height: float, blur: float, glow: float, s: float) -> float {
|
|
return lq_stud_room(light_dir, height * s / 25.0, blur * s / 25.0, glow, s)
|
|
}
|
|
|
|
// The bar slider: a 4 point well under its label row, the lit fill, and for
|
|
// its thumb the 16 point stud riding it. Hovered the stud lifts, held it
|
|
// sinks half a point, focused it wears the sheet's ice-blue ring round its
|
|
// foot, disabled it dulls and throws no shadow. The quad reaches as far as
|
|
// its shadow does.
|
|
mod.widgets.SliderFlat.clip_x = false
|
|
mod.widgets.SliderFlat.clip_y = false
|
|
lq_stud_material(mod.widgets.SliderFlat.draw_bg)
|
|
lq_stud_material(mod.widgets.Slider.draw_bg)
|
|
mod.widgets.SliderFlat.draw_bg.vertex = fn() {
|
|
let m = lq_thumb_room(self.cap_light, self.cap_height, self.cap_shadow_blur, self.cap_glow, 16.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 = lq_thumb_room(self.cap_light, self.cap_height, self.cap_shadow_blur, self.cap_glow, 16.0)
|
|
let px = lq_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 = lq_snap(20.0 + (self.rect_size.y - 20.0) * 0.5, o.y, px)
|
|
let lo = vec2(lq_snap(0.0, o.x, px), cy - 2.0)
|
|
let hi = vec2(lq_snap(self.rect_size.x, o.x, px), cy + 2.0)
|
|
var res = lq_well(p, lo, hi, 2.0, px, vec3(0.125, 0.043, 0.051), 0.0, self.disabled, 1.0)
|
|
let span = self.rect_size.x - rad * 2.0
|
|
let hx = rad + self.slide_pos * span
|
|
let ox = rad + self.origin_pos * span
|
|
let f0 = mix(lo.x + px, min(ox, hx), self.arc_origin)
|
|
let f1 = mix(hx, max(ox, hx), self.arc_origin)
|
|
res = lq_fill(res, p, vec2(f0, lo.y + px), vec2(f1, hi.y - px), vec2(lo.x + px, hi.x - px), px, self.disabled)
|
|
let c = vec2(lq_snap(hx - rad, o.x, px) + rad, cy)
|
|
return lq_thumb(res, p, c, rad * 2.0, m, px, self.hover, self.drag * (1.0 - self.disabled), self.focus, self.disabled, self.cap_light, vec4(self.cap_specular, self.cap_roughness, self.cap_gloss, self.cap_rim), vec4(self.cap_ao, self.cap_ao_reach, self.cap_reflection, self.cap_reflection_perspective), vec4(self.cap_metal, self.cap_spun, self.cap_glow, self.cap_light_intensity), vec4(self.cap_exposure, self.cap_rolloff, self.cap_shadow, self.cap_contact), vec2(self.cap_height, self.cap_shadow_blur), self.handle_color.rgb, self.light_color.rgb, self.shadow_color.rgb, self.sky_ground_color.rgb)
|
|
}
|
|
|
|
// The range slider: the bar slider's 4 point well under its label row,
|
|
// the lit fill between its two ends, and the bar slider's 16 point stud on
|
|
// each end (the hit test takes the same size and inset, so the drawn stud
|
|
// is the grabbed one). The end the pointer is over lifts; the one held, or
|
|
// both while the band between them is dragged, sinks half a point.
|
|
mod.widgets.RangeSliderFlat.handle_size = 16.0
|
|
mod.widgets.RangeSliderFlat.track_inset = 0.0
|
|
lq_stud_material(mod.widgets.RangeSliderFlat.draw_bg)
|
|
mod.widgets.RangeSliderFlat.draw_bg.vertex = fn() {
|
|
let m = lq_thumb_room(self.cap_light, self.cap_height, self.cap_shadow_blur, self.cap_glow, self.handle_px)
|
|
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 = lq_thumb_room(self.cap_light, self.cap_height, self.cap_shadow_blur, self.cap_glow, self.handle_px)
|
|
let px = lq_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 = self.handle_px * 0.5
|
|
let cy = lq_snap(self.label_px + (self.rect_size.y - self.label_px) * 0.5, o.y, px)
|
|
let lo = vec2(lq_snap(0.0, o.x, px), cy - 2.0)
|
|
let hi = vec2(lq_snap(self.rect_size.x, o.x, px), cy + 2.0)
|
|
var res = lq_well(p, lo, hi, 2.0, px, vec3(0.125, 0.043, 0.051), 0.0, self.disabled, 1.0)
|
|
let span = self.rect_size.x - self.inset_px * 2.0 - self.handle_px
|
|
let sx = self.inset_px + rad + self.start_pos * span
|
|
let ex = self.inset_px + rad + self.end_pos * span
|
|
res = lq_fill(res, p, vec2(sx, lo.y + px), vec2(ex, hi.y - px), vec2(lo.x + px, hi.x - px), px, self.disabled)
|
|
let live = 1.0 - self.disabled
|
|
let held_s = clamp(self.drag * (self.hot_start + self.hot_band), 0.0, 1.0) * live
|
|
let held_e = clamp(self.drag * (self.hot_end + self.hot_band), 0.0, 1.0) * live
|
|
// One stud is worked out per pixel (two would double a shader the
|
|
// D3D compiler already takes long over): the end one where its own
|
|
// footprint is, so it lies over the start one when they meet, and
|
|
// elsewhere whichever end is nearer.
|
|
let cs = vec2(lq_snap(sx - rad, o.x, px) + rad, cy)
|
|
let ce = vec2(lq_snap(ex - rad, o.x, px) + rad, cy)
|
|
let fe = abs(p - ce) - vec2(rad * 1.16 + px, rad * 1.16 + px)
|
|
let pick = max(step(max(fe.x, fe.y), 0.0), step(abs(p.x - ce.x), abs(p.x - cs.x)))
|
|
let c = mix(cs, ce, pick)
|
|
let hover = self.hover * mix(self.hot_start, self.hot_end, pick)
|
|
let held = mix(held_s, held_e, pick)
|
|
return lq_thumb(res, p, c, self.handle_px, m, px, hover, held, 0.0, self.disabled, self.cap_light, vec4(self.cap_specular, self.cap_roughness, self.cap_gloss, self.cap_rim), vec4(self.cap_ao, self.cap_ao_reach, self.cap_reflection, self.cap_reflection_perspective), vec4(self.cap_metal, self.cap_spun, self.cap_glow, self.cap_light_intensity), vec4(self.cap_exposure, self.cap_rolloff, self.cap_shadow, self.cap_contact), vec2(self.cap_height, self.cap_shadow_blur), self.handle_color.rgb, self.light_color.rgb, self.shadow_color.rgb, self.sky_ground_color.rgb)
|
|
}
|
|
|
|
// A slider's value is a text input, and the field row's 24 point minimum
|
|
// pushed it half a row below its label: here it is only as tall as its line.
|
|
mod.widgets.SliderMinimal.text_input.min_height = 0.0
|
|
mod.widgets.SliderMinimal.text_input.padding = mod.turtle.Inset{left: 0.0 right: 0.0 top: 0.0 bottom: 0.0}
|
|
mod.widgets.SliderFlat.text_input.min_height = 0.0
|
|
mod.widgets.SliderFlat.text_input.padding = mod.turtle.Inset{left: 0.0 right: 0.0 top: 0.0 bottom: 0.0}
|
|
mod.widgets.Slider.text_input.min_height = 0.0
|
|
mod.widgets.Slider.text_input.padding = mod.turtle.Inset{left: 0.0 right: 0.0 top: 0.0 bottom: 0.0}
|
|
mod.widgets.SliderRoundFlat.text_input.min_height = 0.0
|
|
mod.widgets.SliderRoundFlat.text_input.padding = mod.turtle.Inset{left: 0.0 right: 0.0 top: 0.0 bottom: 0.0}
|
|
mod.widgets.SliderRound.text_input.min_height = 0.0
|
|
mod.widgets.SliderRound.text_input.padding = mod.turtle.Inset{left: 0.0 right: 0.0 top: 0.0 bottom: 0.0}
|
|
// A dial's value box stands in the 20 point legend row above the ring: at
|
|
// the field row's 24 points it hung over the ring's bezel, so here it is
|
|
// its line and two points either side, and clears the ring.
|
|
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}
|
|
|
|
// The minimal slider: only the slot and the lit value; under the hand a
|
|
// 12 point stud shows at the value, its point over the fill's end, and
|
|
// sinks half a point while held. The slot is drawn only inside the rect,
|
|
// as it was before the quad grew for the stud's shadow.
|
|
mod.widgets.SliderMinimal.clip_x = false
|
|
mod.widgets.SliderMinimal.clip_y = false
|
|
lq_stud_material(mod.widgets.SliderMinimal.draw_bg)
|
|
mod.widgets.SliderMinimal.draw_bg.vertex = fn() {
|
|
let m = lq_thumb_room(self.cap_light, self.cap_height, self.cap_shadow_blur, self.cap_glow, 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 = lq_thumb_room(self.cap_light, self.cap_height, self.cap_shadow_blur, self.cap_glow, 12.0)
|
|
let px = lq_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 cy = lq_snap(self.offset_y + (self.rect_size.y - self.offset_y) * 0.5, o.y, px)
|
|
// Four device pixels: the outline, a core row, a body row, the outline.
|
|
let lo = vec2(lq_snap(0.0, o.x, px), cy - px * 2.0)
|
|
let hi = vec2(lq_snap(self.rect_size.x, o.x, px), cy + px * 2.0)
|
|
var res = lq_well(p, lo, hi, 1.0, px, vec3(0.125, 0.043, 0.051), 0.0, self.disabled, 1.0)
|
|
let lo2 = min(self.origin_pos, self.slide_pos)
|
|
let hi2 = max(self.origin_pos, self.slide_pos)
|
|
let f0 = mix(0.0, lo2, self.arc_origin) * self.rect_size.x
|
|
let f1 = mix(self.slide_pos, hi2, self.arc_origin) * self.rect_size.x
|
|
res = lq_fill(res, p, vec2(max(f0, lo.x + px), lo.y + px), vec2(min(f1, hi.x - px), hi.y - px), vec2(lo.x + px, hi.x - px), px, self.disabled)
|
|
res = res * step(0.0, p.x) * step(p.x, self.rect_size.x) * step(0.0, p.y) * step(p.y, self.rect_size.y)
|
|
let hx = self.slide_pos * self.rect_size.x
|
|
let show = max(self.hover, self.drag)
|
|
let c = vec2(lq_snap(hx - 6.0, o.x, px) + 6.0, cy)
|
|
let stud = lq_thumb(res, p, c, 12.0, m, px, 0.0, self.drag * (1.0 - self.disabled), 0.0, self.disabled, self.cap_light, vec4(self.cap_specular, self.cap_roughness, self.cap_gloss, self.cap_rim), vec4(self.cap_ao, self.cap_ao_reach, self.cap_reflection, self.cap_reflection_perspective), vec4(self.cap_metal, self.cap_spun, self.cap_glow, self.cap_light_intensity), vec4(self.cap_exposure, self.cap_rolloff, self.cap_shadow, self.cap_contact), vec2(self.cap_height, self.cap_shadow_blur), self.handle_color.rgb, self.light_color.rgb, self.shadow_color.rgb, self.sky_ground_color.rgb)
|
|
return mix(res, stud, show)
|
|
}
|
|
|
|
// The round slider: a label column, then the bar slider's 4 point slot with
|
|
// the lit fill and the bar slider's 16 point stud. The slot ends short of
|
|
// the right edge, so the value the widget writes there stands on the
|
|
// ground, not on the track.
|
|
mod.widgets.SliderRoundFlat.clip_x = false
|
|
mod.widgets.SliderRoundFlat.clip_y = false
|
|
lq_stud_material(mod.widgets.SliderRoundFlat.draw_bg)
|
|
lq_stud_material(mod.widgets.SliderRound.draw_bg)
|
|
mod.widgets.SliderRoundFlat.draw_bg.vertex = fn() {
|
|
let m = lq_thumb_room(self.cap_light, self.cap_height, self.cap_shadow_blur, self.cap_glow, 16.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 = lq_thumb_room(self.cap_light, self.cap_height, self.cap_shadow_blur, self.cap_glow, 16.0)
|
|
let px = lq_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 = lq_snap(self.rect_size.y * 0.5, o.y, px)
|
|
let x0 = lq_snap(self.label_size, o.x, px)
|
|
let lo = vec2(x0, cy - 2.0)
|
|
let hi = vec2(lq_snap(self.rect_size.x - 37.0, o.x, px), cy + 2.0)
|
|
var res = lq_well(p, lo, hi, 2.0, px, vec3(0.125, 0.043, 0.051), 0.0, self.disabled, 1.0)
|
|
let span = hi.x - lo.x - rad * 2.0
|
|
let hx = lo.x + rad + self.slide_pos * span
|
|
res = lq_fill(res, p, vec2(lo.x + px, lo.y + px), vec2(hx, hi.y - px), vec2(lo.x + px, hi.x - px), px, self.disabled)
|
|
let c = vec2(lq_snap(hx - rad, o.x, px) + rad, cy)
|
|
return lq_thumb(res, p, c, rad * 2.0, m, px, self.hover, self.drag * (1.0 - self.disabled), self.focus, self.disabled, self.cap_light, vec4(self.cap_specular, self.cap_roughness, self.cap_gloss, self.cap_rim), vec4(self.cap_ao, self.cap_ao_reach, self.cap_reflection, self.cap_reflection_perspective), vec4(self.cap_metal, self.cap_spun, self.cap_glow, self.cap_light_intensity), vec4(self.cap_exposure, self.cap_rolloff, self.cap_shadow, self.cap_contact), vec2(self.cap_height, self.cap_shadow_blur), self.handle_color.rgb, self.light_color.rgb, self.shadow_color.rgb, self.sky_ground_color.rgb)
|
|
}
|
|
|
|
// THE RING PULL, the fader's cap: a drawer pull after his photographs,
|
|
// whose base is the knob bench's `skirted` style in silver, seen from the
|
|
// front, with a round wire ring piercing its nub and hanging from it.
|
|
//
|
|
// The skirted knob's revolve (`lq_skirted`), the bench's curve with its
|
|
// anchors and the handles its curve rules give them: from the middle out a
|
|
// nub level to a quarter of the radius, rolling over at 0.28 into a steep
|
|
// wall down to 0.30 of the height at 0.34, a skirt dished a little (0.23 at
|
|
// 0.63) and rising again to 0.25 at 0.93, then a rounded fall to the case
|
|
// at the rim. The height in .x (0..1), the slope over the radius in .y
|
|
// (held to the +-8 the bench's bake stores).
|
|
let lq_skirted = fn(r: float) -> vec2 {
|
|
return Cap.curve(r, 5.0, vec2(0.0, 1.0), vec2(0.0467, -0.005), vec2(-0.02, 0.0412), vec2(0.28, 0.97), vec2(0.02, -0.0412), vec2(-0.02, 0.12), vec2(0.34, 0.30), vec2(0.1083, -0.12), vec2(-0.0233, 0.0106), vec2(0.93, 0.25), vec2(0.0233, -0.0106), vec2(-0.0117, 0.0417), vec2(1.0, 0.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 0.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 0.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 0.0))
|
|
}
|
|
|
|
// The pull's knob: the skirted revolve with its skirt at half the radius
|
|
// (on his word), the nub and its wall as the bench has them. `r` in the
|
|
// bench knob's radii: to 0.34 the curve itself, and from there the skirt's
|
|
// span 0.34..1 laid into 0.34..0.5, so the flange runs a sixth of the
|
|
// radius round the nub's foot and ends at half the radius.
|
|
let lq_pull_profile = fn(r: float) -> vec2 {
|
|
return lq_skirted(mix(r, 0.34 + (r - 0.34) * 4.125, step(0.34, r)))
|
|
}
|
|
|
|
// `rad` is the radius of the bench's knob the nub, the ring and the heights
|
|
// are measured on (25/18 of the fader's cap_size, which the travel counts);
|
|
// the skirt ends at half of it. The
|
|
// nub stands 0.75 of `rad` tall, the bench's chrome depth. The ring's
|
|
// centre line is 0.49 of `rad` round, wider than the skirt, its round wire's
|
|
// own radius 0.07 of `rad` (on his word, two thirds of the 0.11 it was):
|
|
// its top runs level through the nub just above the skirt, so the nub
|
|
// hides it where the nub stands taller and the wire comes out of the nub's
|
|
// lower flanks on either side, a dark rim of the hole round it and the wire
|
|
// darkening as it goes in; from there the ring hangs down the screen,
|
|
// whichever way the fader lies, clearing the skirt's edge and leaning back
|
|
// until its foot rests on what lies under the knob, the track or the
|
|
// ground, a contact shade close round the wire wherever it lies low over a
|
|
// surface, the skirt or the ground. Both are the
|
|
// stud's silver, lit and mirroring the sheet's studio as the stud does
|
|
// (the shade without the highlight, the studio laid in, darkened toward
|
|
// the foot by the occlusion, the highlight put back). The knob throws its
|
|
// shadow from its skirt and from its nub (the nub's falling on the skirt
|
|
// and past it), the ring its own on the knob and the ground, each blurred
|
|
// by its height as the stud's is; the contact shade hugs the skirt's foot.
|
|
//
|
|
// Every edge is a one device pixel coverage: the knob's and the wire's
|
|
// outlines, where the wire goes into the nub (by how fast the two heights
|
|
// part across a pixel), the hole's rim and the shadows' feet; the knob's
|
|
// slope is the curve's rise over one device pixel, so the nub's wall and the
|
|
// rim's fall, each about a pixel wide, shade as a pixel-wide band and not
|
|
// as a stair.
|
|
//
|
|
// `q` the point from the knob's middle on screen in points, `rad` its
|
|
// radius, `press` 1 while it is held (it sinks half a point), `reach` how
|
|
// far the quad runs past the rect; the material as `lq_stud` takes it, `k5`
|
|
// the shadow's blur and the stud's height it is given for. Laid over what
|
|
// lies under it as the stud is; hovered it lifts, focused the sheet's
|
|
// ice-blue ring lies round the skirt's foot, disabled it dulls and throws no
|
|
// shadow. Premultiplied.
|
|
let lq_pull = fn(under_ink: vec3, keep: float, toned: float, q: vec2, rad: float, press: float, reach: float, px: float, hover: float, focus: float, disabled: float, light_dir: vec3, k1: vec4, k2: vec4, k3: vec4, k4: vec4, k5: vec2, body: vec3, light_ink: vec3, shadow_ink: vec3, sky: vec3) -> vec4 {
|
|
let live = 1.0 - disabled
|
|
let l = normalize(light_dir)
|
|
let ev = pow(2.0, k4.x)
|
|
let ro = k4.y
|
|
let hk = max(rad * 0.75 - 0.5 * press, 1.0)
|
|
let wr = rad * 0.07
|
|
let rc = rad * 0.49
|
|
let cr = vec2(0.0, rc)
|
|
let z_top = hk * 0.30 + rad * 0.16
|
|
let z_foot = wr
|
|
let rs = rad * 0.5
|
|
let tilt = (z_top - z_foot) / (2.0 * rc)
|
|
// The shadow's toss and blur per point of height, as the stud's.
|
|
let toss = -l.xy / max(l.z, 0.25) * 0.7
|
|
let bk = k5.x / max(k5.y, 0.001)
|
|
let ql = length(q)
|
|
let dk = ql - rs
|
|
let e = q - cr
|
|
let rho = length(e)
|
|
let dr = abs(rho - rc) - wr
|
|
var cov_k = 0.0
|
|
var cov_r = 0.0
|
|
var vis = 0.0
|
|
var face_k = body
|
|
var face_r = body
|
|
var under = 0.0
|
|
if min(dk, dr) < reach + 2.0 {
|
|
// The knob's surface here, and the ring's.
|
|
let r01 = ql / rad
|
|
let pr = lq_pull_profile(r01)
|
|
// The slope as the rise over one device pixel across the radius.
|
|
let dpx = px * 0.5 / rad
|
|
let slope = (lq_pull_profile(r01 + dpx).x - lq_pull_profile(max(r01 - dpx, 0.0)).x) / (r01 + dpx - max(r01 - dpx, 0.0))
|
|
let zk = pr.x * hk
|
|
let dir = q / max(ql, 0.0001)
|
|
let nk = normalize(vec3(-dir * slope * hk / rad, 1.0))
|
|
let er = e / max(rho, 0.0001)
|
|
let s = clamp((rho - rc) / (wr + px * 0.5), -1.0, 1.0)
|
|
let sq = sqrt(max(1.0 - s * s, 0.0))
|
|
let zc = z_top - tilt * rc * (1.0 + er.y)
|
|
let zr = zc + wr * sq
|
|
let nr = normalize(vec3(er.x * s, er.y * s + tilt * sq, sq - er.y * tilt * s))
|
|
cov_k = Cap.cover(dk, px)
|
|
cov_r = Cap.cover(dr, px)
|
|
vis = clamp((zr - zk) / (px * (abs(slope) * hk / rad + 0.5)) + 0.5, 0.0, 1.0)
|
|
// The shadows. The ring's, thrown from its height over whatever
|
|
// lies under this point, where the ring itself is not on top.
|
|
let hr = max(zc - zk, 0.0)
|
|
let qo = q - toss * hr
|
|
let dro = abs(length(qo - cr) - rc) - wr
|
|
let br = max(hr * bk, px)
|
|
let s_ring = (1.0 - smoothstep(-br * 0.5, br, dro)) * k4.z * live * (1.0 - cov_r * vis)
|
|
// The skirt's, on the ground; the nub's, on the skirt and the ground.
|
|
let hs_k = hk * 0.27
|
|
let bs = max(hs_k * bk, px)
|
|
let s_skirt = (1.0 - smoothstep(-bs * 0.35, bs, length(q - toss * hs_k) - rs * 0.96)) * (1.0 - cov_k)
|
|
let hn = max(hk * 0.98 - zk, 0.0)
|
|
let bn = max(hn * bk, px)
|
|
let s_nub = (1.0 - smoothstep(-bn * 0.35, bn, length(q - toss * hn) - rad * 0.31)) * smoothstep(rad * 0.31, rad * 0.34 + px, ql)
|
|
let ct = Cap.contact(dk, max(hk * 0.3, 1.0), k4.w * (1.0 - 0.6 * disabled))
|
|
// The ring's contact shade, close round the wire and as strong as
|
|
// the wire lies low over what is under it (the skirt or the ground).
|
|
let gap = max(zc - wr - zk, 0.0)
|
|
let ao_r = exp(-max(dr, 0.0) / max(wr * 0.7, px)) * exp(-gap / max(wr, px)) * (1.0 - cov_r * vis) * smoothstep(rad * 0.30, rad * 0.40, ql) * k4.w * (1.0 - 0.6 * disabled)
|
|
under = clamp((s_skirt + s_nub) * k4.z * live + ct + s_ring + ao_r * 0.9, 0.0, 1.0) * (1.0 - cov_k)
|
|
|
|
// The knob, lit as the stud is, from an eye nearer over it than over
|
|
// the stud (3.6 times as near), as his close photographs were taken:
|
|
// the level nub and skirt then mirror the studio round them, the dark
|
|
// camera only in their middle, where a flat mirror shows it.
|
|
let near = k2.w * 3.6
|
|
let vk = normalize(vec3(-(q / rad) * near, 1.0))
|
|
// The skirt is the knob's face, not its foot: its occlusion counts
|
|
// the height from the case up to the skirt's level, so only the
|
|
// rim's fall to the case lies in it.
|
|
let ho = clamp(pr.x / 0.25, 0.0, 1.0)
|
|
let litk = Cap.shade(nk, l, vk, ho, body, 0.0, 0.0, 1.0, k1.x, k1.y, k1.z, k1.w, k2.x, k2.y, 0.0, k3.x, k3.w, body, light_ink, shadow_ink, sky)
|
|
let dullk = Cap.shade(nk, l, vk, ho, body, 0.0, 0.0, 1.0, 0.0, k1.y, 0.0, 0.0, k2.x, k2.y, 0.0, k3.x, k3.w, body, light_ink, shadow_ink, sky)
|
|
let occk = 1.0 - k2.x * pow(1.0 - ho, max(k2.y, 0.2))
|
|
let rvk = -vk - nk * (2.0 * dot(nk, -vk))
|
|
face_k = mix(dullk, lq_studio(rvk) * mix(vec3(1.0, 1.0, 1.0), body, k3.x) * mix(1.0, occk, 0.35), k2.z) + (litk - dullk)
|
|
// In the shadows of the nub and the ring, and dark in the hole round
|
|
// the wire where it goes into the nub.
|
|
face_k = face_k * mix(vec3(1.0, 1.0, 1.0), shadow_ink, clamp(s_nub * k4.z * live + s_ring, 0.0, 1.0))
|
|
// Where the ring lies low over the skirt, the skirt darkens close
|
|
// round the wire, so the ring reads on the knob's own silver.
|
|
face_k = face_k * mix(vec3(1.0, 1.0, 1.0), shadow_ink, clamp(ao_r * 0.9, 0.0, 1.0))
|
|
let hole = (1.0 - smoothstep(-px * 0.5, px * 0.5 + max(wr * 0.7, px), dr)) * (1.0 - vis) * smoothstep(rad * 0.24 - px, rad * 0.30, ql) * (1.0 - smoothstep(rad * 0.34, rad * 0.40 + px, ql))
|
|
face_k = mix(face_k, face_k * vec3(0.10, 0.10, 0.11), hole * 0.85)
|
|
|
|
// The ring, lit alike; its height share for the occlusion.
|
|
let qv = q / rad
|
|
let vr = normalize(vec3(-qv / max(length(qv) / 1.1, 1.0) * near, 1.0))
|
|
let h01 = clamp(zr / (hk * 0.25), 0.0, 1.0)
|
|
let litr = Cap.shade(nr, l, vr, h01, body, 0.0, 0.0, 1.0, k1.x, k1.y, k1.z, k1.w, k2.x, k2.y, 0.0, k3.x, k3.w, body, light_ink, shadow_ink, sky)
|
|
let dullr = Cap.shade(nr, l, vr, h01, body, 0.0, 0.0, 1.0, 0.0, k1.y, 0.0, 0.0, k2.x, k2.y, 0.0, k3.x, k3.w, body, light_ink, shadow_ink, sky)
|
|
let occr = 1.0 - k2.x * pow(1.0 - h01, max(k2.y, 0.2))
|
|
let rvr = -vr - nr * (2.0 * dot(nr, -vr))
|
|
face_r = mix(dullr, lq_studio(rvr) * mix(vec3(1.0, 1.0, 1.0), body, k3.x) * mix(1.0, occr, 0.35), k2.z) + (litr - dullr)
|
|
face_r = mix(face_r, face_r * shadow_ink, 0.7 * (1.0 - smoothstep(0.31, 0.45, r01)))
|
|
}
|
|
|
|
// Toned as the bench tones, lifted under the hand, dulled when disabled.
|
|
var fk = Cap.tone(face_k, ev, ro)
|
|
var fr = Cap.tone(face_r, ev, ro)
|
|
let lift = vec3(0.04, 0.04, 0.042) * hover * (1.0 - press) * live
|
|
fk = fk + lift
|
|
fr = fr + lift
|
|
let lk = Finish.lum(fk)
|
|
fk = mix(fk, mix(fk, vec3(lk, lk, lk), 0.45) * 0.8, disabled)
|
|
let lr = Finish.lum(fr)
|
|
fr = mix(fr, mix(fr, vec3(lr, lr, lr), 0.45) * 0.8, disabled)
|
|
// Laid over what lies under it, as the stud is.
|
|
let dim = mix(vec3(1.0, 1.0, 1.0), shadow_ink, under)
|
|
let kept = keep * (1.0 - under * (1.0 - (shadow_ink.x + shadow_ink.y + shadow_ink.z) / 3.0))
|
|
var low = under_ink * dim
|
|
if toned > 0.5 {
|
|
low = Cap.tone(low, ev, ro)
|
|
}
|
|
var res = vec4(low, 1.0 - kept)
|
|
res = mix(res, vec4(fk, 1.0), cov_k)
|
|
res = mix(res, vec4(fr, 1.0), cov_r * vis)
|
|
let ring = Finish.ring_out(dk, 0.0, px, px) * 0.9 + Finish.glow(dk - px, 0.7) * 0.35 * (1.0 - Finish.cover(dk, px))
|
|
return Finish.over(res, vec4(0.498, 0.706, 0.910, 1.0) * clamp(ring, 0.0, 1.0) * focus * live * (1.0 - cov_r * vis))
|
|
}
|
|
|
|
// The fader: the silver ring pull (`lq_pull`) running a slot, its skirt
|
|
// 18.75 points across whatever the fader's width (on his word, 'the whole
|
|
// cap and ring can be 50% bigger', half as big again as the 12.5 it was),
|
|
// its ring hanging down the screen. The skirt decides where the cap stops,
|
|
// not the ring (on his word: 'the skirt radius decides the top/bottom of
|
|
// the cap (no the ring)'), and at either stop it covers the slot's end:
|
|
// cap_size, what the widget's travel, drag and cap field count, is 13.5
|
|
// points, the post under the knob, so at a stop the knob's middle stands
|
|
// 6.75 points in from the slot's end and the 18.75 point skirt reaches
|
|
// 2.6 points past it, over the end's rounded corners too. The skirt is
|
|
// 25/18 of cap_size across, and the nub and the ring are measured on twice
|
|
// that, the bench knob the skirt was halved from, held to half the
|
|
// fader's width (20 points on a 40 point fader, so this one is not held). The slot is this sheet's: 9.33 points wide
|
|
// (fourteen device pixels at 1.5 dpi, a little wider than the sheet's other
|
|
// slots so the zipper's teeth have room) and running the whole control, its
|
|
// ends rounded by 0.3 of its half width, in the wine black of the sheet's
|
|
// wells, with its own shade and occlusion, and no scale and no well. Its
|
|
// filled part, from the value's zero up to the knob, is a closed metal
|
|
// zipper seen from above; past the knob the slot is open
|
|
// and plain. Hovered, the pull lifts as the
|
|
// sheet's chrome caps do; held, it sinks half a point and the lift goes;
|
|
// focused, the sheet's ice-blue ring; disabled, it dulls as the sheet's caps
|
|
// do and throws no shadow, the zipper's teeth dull with it, and the slot
|
|
// takes the sheet's disabled well colour.
|
|
mod.widgets.SliderFader.cap_size = 13.5
|
|
mod.widgets.SliderFader.track_inset = 0.0
|
|
mod.widgets.SliderFader.draw_bg.track_size = uniform(9.3333)
|
|
// The library's own cap's share across, left at what the 25 point stud
|
|
// was of a 40 point fader; the ring pull does not read it.
|
|
mod.widgets.SliderFader.draw_bg.cap_cross = uniform(0.625)
|
|
mod.widgets.SliderFader.clip_x = false
|
|
mod.widgets.SliderFader.clip_y = false
|
|
// The stud's silver for the ring pull, and the slot's inner shadow, which stays the sheet's.
|
|
lq_stud_material(mod.widgets.SliderFader.draw_bg)
|
|
// The ring pull's highlight is the knob bench's chrome, polished: tight
|
|
// (roughness 0.06) and with no broad sheen, so the knob's level nub and
|
|
// skirt mirror the studio instead of washing white.
|
|
mod.widgets.SliderFader.draw_bg.cap_roughness = uniform(0.06)
|
|
mod.widgets.SliderFader.draw_bg.cap_gloss = uniform(0.0)
|
|
mod.widgets.SliderFader.draw_bg.cap_inner = uniform(0.42)
|
|
// The slot's inks. The ground is the sheet's lacquer: the slot's floor is
|
|
// reckoned against it. The slot is the sheet's well colour, its disabled one
|
|
// when the fader is, under the sheet's own shade and occlusion.
|
|
mod.widgets.SliderFader.draw_bg.ground_color = uniform(theme.color_bg_app)
|
|
mod.widgets.SliderFader.draw_bg.track_color = uniform(theme.color_inset)
|
|
mod.widgets.SliderFader.draw_bg.track_color_disabled = uniform(theme.color_inset_disabled)
|
|
mod.widgets.SliderFader.draw_bg.slot_shadow_color = uniform(#5c6066)
|
|
mod.widgets.SliderFader.draw_bg.slot_ao = uniform(1.0)
|
|
|
|
mod.widgets.SliderFader.draw_bg.vertex = fn() {
|
|
let m = max(lq_stud_room(self.cap_light, self.cap_height, self.cap_shadow_blur, self.cap_glow, self.cap_px), self.cap_px * 1.46 + 4.0)
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
|
|
}
|
|
mod.widgets.SliderFader.draw_bg.pixel = fn() {
|
|
let m = max(lq_stud_room(self.cap_light, self.cap_height, self.cap_shadow_blur, self.cap_glow, self.cap_px), self.cap_px * 1.46 + 4.0)
|
|
let px = lq_px(self.draw_pass.dpi_factor)
|
|
let o = self.rect_pos
|
|
let size = self.rect_size
|
|
let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
let live = 1.0 - self.disabled
|
|
let vert = self.is_vertical
|
|
// Track space: `ab.x` along the travel from the value's zero (up the
|
|
// screen when the fader stands), `ab.y` across it; `ob` is where the
|
|
// across axis starts on screen, for snapping.
|
|
let ab = vec2(mix(p.x, size.y - p.y, vert), mix(p.y, p.x, vert))
|
|
let along = mix(size.x, size.y, vert)
|
|
let across = mix(size.y, size.x, vert)
|
|
let ob = mix(o.y, o.x, vert)
|
|
let travel = max(along - self.inset_px * 2.0 - self.cap_px, 1.0)
|
|
let foot = self.inset_px + self.cap_px * 0.5
|
|
let mid = lq_snap(across * 0.5, ob, px)
|
|
// The cap's half size, the post's: cap_size each way, held to the
|
|
// control.
|
|
let hs = vec2(self.cap_px * 0.5, min(self.cap_px, across) * 0.5)
|
|
// The cap's leading end (the left one lying, the top one standing) on
|
|
// the device pixel grid, so its ends stand crisp and it keeps one shape
|
|
// as it moves.
|
|
let at_free = foot + self.slide_pos * travel
|
|
let lead = Finish.snap(mix(o.x + at_free - hs.x, o.y + size.y - at_free - hs.x, vert), px)
|
|
let at = mix(lead - o.x + hs.x, o.y + size.y - lead - hs.x, vert)
|
|
// The key light, turned into track space.
|
|
let lt = Cap.light(normalize(self.cap_light), vert)
|
|
let ground = self.ground_color.rgb
|
|
let light_ink = self.light_color.rgb
|
|
let body = self.handle_color.rgb
|
|
let black = vec3(0.0, 0.0, 0.0)
|
|
|
|
// THE SLOT: track_size wide with its long edges on the pixel grid, as
|
|
// the sheet's slots are, from one track inset to the other, its ends
|
|
// rounded by 0.3 of its half width. It is drawn twice: on black, which
|
|
// is its own light (the floor, the walls, and the lip it lights outside
|
|
// itself, which fades when disabled as the sheet's lips do), and on the
|
|
// ground, which says how much of the ground it leaves showing. The fader
|
|
// is laid over whatever lies under it, and the story's canvas is not one
|
|
// flat colour.
|
|
let slot_shade = self.slot_shadow_color.rgb
|
|
let tw = min(self.track_size, across)
|
|
let s_lo = lq_snap(mid - tw * 0.5, ob, px)
|
|
let s_hi = lq_snap(mid + tw * 0.5, ob, px)
|
|
let tlen = max(along - self.inset_px * 2.0, 1.0)
|
|
let tbox = vec4(self.inset_px + tlen * 0.5, (s_lo + s_hi) * 0.5, tlen * 0.5, max(s_hi - s_lo, px) * 0.5)
|
|
let tr = 0.3 * tbox.w
|
|
let track_ink = mix(self.track_color.rgb, self.track_color_disabled.rgb, self.disabled)
|
|
let slot_light = light_ink * (1.0 - 0.6 * self.disabled)
|
|
var slot_own = Cap.slot(black, ab, tbox, tr, 0.0, foot, foot, at, hs.x, lt, px, 0.0, 0.0, track_ink, track_ink, slot_light, slot_shade, self.cap_inner, self.slot_ao)
|
|
let slot_over = Cap.slot(ground, ab, tbox, tr, 0.0, foot, foot, at, hs.x, lt, px, 0.0, 0.0, track_ink, track_ink, slot_light, slot_shade, self.cap_inner, self.slot_ao)
|
|
let shows = clamp(dot(slot_over - slot_own, vec3(1.0, 1.0, 1.0)) / max(dot(ground, vec3(1.0, 1.0, 1.0)), 0.001), 0.0, 1.0)
|
|
|
|
// THE ZIPPER. From the value's zero up to the stud (out of the default's
|
|
// place when the fader grows its fill from there; from the slot's own
|
|
// end when it fills from the stop, and on to the far end when the fill
|
|
// runs there) the slot is a closed metal zipper seen from above: its
|
|
// floor is the tape, and two rows of silver teeth come in from its long
|
|
// edges, one edge and then the other, and mesh down the middle, so the
|
|
// chain reads as one column of stacked links. A tooth every 2 points
|
|
// along the travel (three device pixels at 1.5 dpi), each as thick as
|
|
// its step: in the middle, where the two rows overlap, each tooth lies
|
|
// against the next, and out toward the edges each has a gap as thick as
|
|
// itself before the next from its own edge. A tooth is a block with
|
|
// rounded ends, standing one device pixel in from its edge and reaching
|
|
// two past the middle. It is polished silver, so it mirrors the room, a
|
|
// broad bright patch above the eye a little toward the light that falls
|
|
// dark away from it: round across its thickness, so a band near white
|
|
// runs along its middle and the side toward the light, and the far side
|
|
// is dark; and its top falling away from a ridge across its middle, so
|
|
// the half toward the light is the brighter and the other half greyer.
|
|
// Its two ends are a little darker and its four corners darker still,
|
|
// so the ends read round. Everything is counted in
|
|
// whole device pixels from the slot's zero end and its long edge, so
|
|
// every tooth lands on the pixel grid and keeps its place in the slot as
|
|
// the stud runs over it; only whole teeth are laid. The stud's shadow
|
|
// falls on the teeth as on the slot.
|
|
let z_end = mix(lq_snap(self.inset_px, o.x, px), size.y - lq_snap(size.y - self.inset_px, o.y, px), vert)
|
|
let z_far = mix(lq_snap(size.x - self.inset_px, o.x, px), size.y - lq_snap(self.inset_px, o.y, px), vert)
|
|
let z_lo = mix(foot + self.fill_lo * travel, z_end + 2.0 * px, step(self.fill_lo, 0.0))
|
|
let z_hi = mix(foot + self.fill_hi * travel, z_far - 2.0 * px, step(1.0, self.fill_hi))
|
|
let pitch = max(floor(2.0 / px + 0.5), 2.0)
|
|
let ia = floor((ab.x - z_end) / px)
|
|
let ic = floor((ab.y - s_lo) / px)
|
|
let nc = floor((s_hi - s_lo) / px + 0.5)
|
|
let k = floor(ia / pitch)
|
|
let ka = ia - k * pitch
|
|
let side = k - 2.0 * floor(k * 0.5)
|
|
let t_len = floor((nc - 2.0) * 0.5) + 2.0
|
|
let c0 = mix(1.0, nc - 1.0 - t_len, side)
|
|
let c1 = c0 + t_len
|
|
let t0 = z_end + k * pitch * px
|
|
let tooth = step(c0, ic) * step(ic, c1 - 1.0) * step(z_lo, t0) * step(t0 + pitch * px, z_hi)
|
|
// Where in the tooth this pixel's middle lies: `tt` along the travel
|
|
// and `ts` across it, each -1..1 from one side to the other.
|
|
let tt = (ka + 0.5) / pitch * 2.0 - 1.0
|
|
let ts = (ic - c0 + 0.5) / t_len * 2.0 - 1.0
|
|
// Its normal in track space, then on screen (the travel runs up the
|
|
// screen when the fader stands), and the ray it mirrors.
|
|
let n_a = tt * 0.45
|
|
let n_c = 0.30 * clamp(ts * 2.0, -1.0, 1.0)
|
|
let z_n = normalize(vec3(mix(n_a, n_c, vert), mix(n_c, -n_a, vert), sqrt(max(1.0 - n_a * n_a - n_c * n_c, 0.04))))
|
|
let z_r = vec3(2.0 * z_n.z * z_n.x, 2.0 * z_n.z * z_n.y, 2.0 * z_n.z * z_n.z - 1.0)
|
|
// The room's bright patch: about 20 degrees off the eye toward the key
|
|
// light, falling off over some 45 degrees.
|
|
let z_l = normalize(self.cap_light)
|
|
let z_a = acos(clamp(dot(z_r, normalize(vec3(z_l.x * 0.55, z_l.y * 0.55, 1.0))), -1.0, 1.0)) * 57.2958
|
|
// The patch's light runs from a tenth of the handle's grey where the
|
|
// tooth faces away from it up to near white where it faces it.
|
|
let z_e = pow(clamp(exp(-(z_a * z_a) / 2025.0) / 0.86, 0.0, 1.0), 1.4)
|
|
var tooth_ink = mix(body * 0.10, vec3(0.95, 0.955, 0.965), z_e)
|
|
let t_end = 1.0 - step(c0 + 0.5, ic) * (1.0 - step(c1 - 1.5, ic))
|
|
let t_edge = 1.0 - step(0.5, ka) * (1.0 - step(pitch - 1.5, ka))
|
|
tooth_ink = tooth_ink * (1.0 - 0.15 * t_end - 0.40 * t_end * t_edge)
|
|
let tooth_lum = Finish.lum(tooth_ink)
|
|
tooth_ink = mix(tooth_ink, mix(tooth_ink, vec3(tooth_lum, tooth_lum, tooth_lum), 0.45) * 0.8, self.disabled)
|
|
slot_own = mix(slot_own, tooth_ink, tooth)
|
|
|
|
// THE CAP, the ring pull, its knob at its place on the track, drawn on
|
|
// screen so its ring hangs down whichever way the fader lies.
|
|
let cs = vec2(mix(at, mid, vert), mix(mid, size.y - at, vert))
|
|
return lq_pull(slot_own, shows, 1.0, p - cs, min(self.cap_px * 1.3889, across * 0.5), self.drag * live, m, px, self.hover, self.focus, self.disabled, self.cap_light, vec4(self.cap_specular, self.cap_roughness, self.cap_gloss, self.cap_rim), vec4(self.cap_ao, self.cap_ao_reach, self.cap_reflection, self.cap_reflection_perspective), vec4(self.cap_metal, self.cap_spun, self.cap_glow, self.cap_light_intensity), vec4(self.cap_exposure, self.cap_rolloff, self.cap_shadow, self.cap_contact), vec2(self.cap_shadow_blur, self.cap_height), body, light_ink, self.shadow_color.rgb, self.sky_ground_color.rgb)
|
|
}
|
|
|
|
// ---------------------------------------------------------------- knobs
|
|
|
|
// A knob: a dark well ring with the value lit ice blue in it, round a cap
|
|
// of the same red lacquer as the window's ground, pressed into a shallow
|
|
// cone: from a thin edge its flanks run straight up to a soft rounded nub in
|
|
// the middle that rises out of the same lacquer, its top round and its foot
|
|
// running into the cone in a gentle fillet all round. A face of the cap
|
|
// turned to the eye has the ground's own red; its form shows in its shading
|
|
// and its sheen, not in a brighter red. The cap is a true solid of
|
|
// revolution, so as it turns its light and its reflections stand still and
|
|
// only the bar moves. The lacquer is lit from above and to the left and
|
|
// mirrors a broad soft box there, so a wide sheen runs down the lit flank
|
|
// and a broad soft patch lies on the nub. Through the nub runs a short
|
|
// polished steel bar with a chrome ball at either end, close to the nub; the
|
|
// bar turns with the value, and the ball at the value's end carries a small
|
|
// pip of the value's ice blue in its middle, so the bar is the pointer. A
|
|
// press lights the pip white-hot with a halo.
|
|
// Angles run from straight down, clockwise, the gap at the bottom.
|
|
|
|
// The window's ground without its reflections, at `uv` (shares of the
|
|
// window across and down): bright red at the top falling to near-black wine
|
|
// at the foot, the sides a little darker. The window draws it, and a knob
|
|
// takes it at its own middle for its lacquer.
|
|
let lq_ground_base = fn(uv: vec2) -> vec3 {
|
|
var col = mix(vec3(0.660, 0.070, 0.090), vec3(0.340, 0.048, 0.058), smoothstep(0.0, 0.55, uv.y))
|
|
col = mix(col, vec3(0.170, 0.020, 0.028), smoothstep(0.45, 1.0, uv.y))
|
|
let vx = uv.x * 2.0 - 1.0
|
|
return col * (1.0 - 0.20 * vx * vx)
|
|
}
|
|
// The ground's red under the point `w` of the window: `a` and `b` are the
|
|
// pass's projection of the window's origin and of the point one point down
|
|
// and to the right of it, which give the window's size.
|
|
let lq_ground_at = fn(w: vec2, a: vec4, b: vec4) -> vec3 {
|
|
let ws = vec2(2.0 / max(abs(b.x - a.x), 0.000001), 2.0 / max(abs(b.y - a.y), 0.000001))
|
|
return lq_ground_base(clamp(w / ws, vec2(0.0, 0.0), vec2(1.0, 1.0)))
|
|
}
|
|
|
|
// The cap's height at `v` (screen offset from the middle, in shares of the
|
|
// cap's radius), and how deep in the fillet round the nub's foot it lies
|
|
// (0..1). It depends on the distance from the middle alone, so the cap is
|
|
// a perfect revolve.
|
|
let lq_knob_h = fn(v: vec2) -> vec2 {
|
|
let rn = length(v)
|
|
// The cone: straight from the thin edge up toward the middle, its top
|
|
// rounded off under the nub; the very edge rounds down a little.
|
|
let ee = smoothstep(0.96, 1.0, rn)
|
|
var hc = 0.42 * (1.05 - sqrt(rn * rn + 0.0144)) - 0.012 * ee * ee
|
|
// The nub: a rounded bump 0.36 in radius and 0.10 high on the cone's
|
|
// top, round over its top, steepest a little past half way out and
|
|
// flattening into the cone at its foot, so it meets the cone in a
|
|
// fillet with no edge.
|
|
let s = min(rn / 0.36, 1.0)
|
|
let t = 1.0 - s * s
|
|
return vec2(hc + 0.10 * t * t, smoothstep(0.45, 0.75, s) * (1.0 - smoothstep(0.80, 1.0, s)))
|
|
}
|
|
|
|
// The sheet's chrome as a bar mirrors it, `w` across the bar from -1 to 1
|
|
// and `per` the screen direction that runs: the bright sky above, the red
|
|
// lacquer below, a hot line where it faces the light, darker toward its
|
|
// sides.
|
|
let lq_bar_ink = fn(per: vec2, w: float, hover: float, disabled: float) -> vec3 {
|
|
let n = per * w
|
|
let nz = sqrt(max(1.0 - w * w, 0.0))
|
|
let fac = dot(vec3(n.x, n.y, nz), normalize(vec3(-0.30, -0.70, 0.65)))
|
|
var col = mix(vec3(0.46, 0.30, 0.32), vec3(0.94, 0.94, 0.96), smoothstep(0.45, -0.50, n.y))
|
|
col = col + vec3(1.0, 1.0, 1.0) * pow(max(fac, 0.0), 20.0) * 0.65
|
|
col = mix(col, vec3(0.12, 0.08, 0.09), smoothstep(0.70, 1.0, abs(w)) * 0.6)
|
|
col = col * (1.0 + 0.16 * hover)
|
|
let fl = Finish.lum(col)
|
|
return mix(col, mix(vec3(fl, fl, fl), vec3(0.45, 0.25, 0.27), 0.4) * 0.75, disabled)
|
|
}
|
|
|
|
let lq_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, ground: vec3) -> vec4 {
|
|
// The ring scales with a small knob and stops at a 1.5 point gap and a
|
|
// 4 point width, so a large dial stays slim instead of a heavy donut.
|
|
let rc = max(rr / 1.32, rr - 5.5)
|
|
let ring_in = rc + min(0.08 * rc, 1.5)
|
|
let ring_out = ring_in + min(0.24 * rc, 4.0)
|
|
let ring_mid = (ring_in + ring_out) * 0.5
|
|
let ring_w = ring_out - ring_in
|
|
let dr = Finish.sd_ring(p, c, ring_mid, ring_w)
|
|
let q = p - c
|
|
let rl = length(q)
|
|
// The ring well: lip on the ground under its lower outer edge, the
|
|
// black outline both sides, a dark fill.
|
|
var res = vec4(0.70, 0.52, 0.54, 1.0) * 0.6 * Finish.ring_out(rl - ring_out, 0.0, px, px) * step(c.y + ring_out * 0.3, p.y)
|
|
var col = vec3(0.125, 0.043, 0.051)
|
|
col = mix(col, vec3(0.110, 0.016, 0.020), Finish.band(dr, 0.0, px, px))
|
|
let ra = Finish.cover(dr, px)
|
|
res = Finish.over(res, vec4(col * ra, ra))
|
|
// The value arc, lit like the lamp: 1.5 points on the middle of the
|
|
// ring, a pale core, the accent at its edges and a soft halo on the
|
|
// ring's floor round it. Angles from straight down, clockwise.
|
|
let one = PI / 180.0
|
|
let sweep = 2.0 * PI - gap * one
|
|
let start = gap * one * 0.5
|
|
let ve = start + sweep * slide
|
|
let oe = start + sweep * origin
|
|
let a0 = mix(start, min(oe, ve), arc_origin)
|
|
let a1 = mix(ve, max(oe, ve), arc_origin)
|
|
var ang = atan2(-q.x, q.y)
|
|
if ang < 0.0 {
|
|
ang = ang + 2.0 * PI
|
|
}
|
|
let ac = clamp(ang, a0, a1)
|
|
let ap = c + vec2(-sin(ac), cos(ac)) * ring_mid
|
|
let da = length(p - ap)
|
|
let live = step(0.002, a1 - a0)
|
|
let ink = mix(vec3(0.227, 0.655, 0.965), vec3(0.227, 0.29, 0.388), disabled)
|
|
let floor_in = Finish.cover(dr + px, px)
|
|
let xa = max(da - 0.75, 0.0)
|
|
let ga = (0.30 * exp(-(xa * xa) / (2.0 * 0.55 * 0.55)) + 0.14 * exp(-(xa * xa) / (2.0 * 1.5 * 1.5))) * floor_in * (1.0 - Finish.cover(da - 0.75, px)) * (1.0 - disabled) * live
|
|
res = Finish.over(res, vec4(0.227, 0.655, 0.965, 1.0) * ga)
|
|
let acol = mix(mix(vec3(0.227, 0.655, 1.0), vec3(1.0, 1.0, 1.0), 0.72 * (1.0 - disabled)), ink, smoothstep(0.1, 0.7, da))
|
|
res = Finish.over(res, vec4(acol, 1.0) * Finish.cover(da - 0.75, px) * live)
|
|
// The cap throws a soft shadow down across the ring.
|
|
let shc = Finish.drop(p, c, vec2(rc, rc) * 0.9, rc * 0.9, vec2(0.0, rc * 0.16), rc * 0.14) * 0.7 * (1.0 - 0.5 * disabled)
|
|
res = Finish.over(res, vec4(0.02, 0.0, 0.0, 1.0) * shc * (1.0 - Finish.cover(rl - rc, px)))
|
|
|
|
// THE CAP. Its height, read at this pixel and half a device pixel to
|
|
// either side, gives its normal.
|
|
let dc = rl - rc
|
|
let rcap = max(rc, 0.5)
|
|
let rn = rl / rcap
|
|
let vn = q / rcap
|
|
let u = q / max(rl, 0.001)
|
|
let he = 0.5 * px / rcap
|
|
let h0 = lq_knob_h(vn)
|
|
let hx = lq_knob_h(vn + vec2(he, 0.0)).x - lq_knob_h(vn - vec2(he, 0.0)).x
|
|
let hy = lq_knob_h(vn + vec2(0.0, he)).x - lq_knob_h(vn - vec2(0.0, he)).x
|
|
let n = normalize(vec3(-hx / (2.0 * he), -hy / (2.0 * he), 1.0))
|
|
// The key light comes from above and a little to the left, as on the
|
|
// sheet's other caps. The lacquer is lit by it and mirrors a broad soft
|
|
// box above the light, the room's faint sky over the top and a little of
|
|
// the floor along the foot.
|
|
let kl = normalize(vec3(-0.30, -0.70, 0.65))
|
|
let diff = pow(clamp((dot(n, kl) + 0.25) / 1.25, 0.0, 1.0), 1.3)
|
|
let rv = vec3(2.0 * n.z * n.x, 2.0 * n.z * n.y, 2.0 * n.z * n.z - 1.0)
|
|
// The soft box: a patch of sky some 45 degrees out from the eye toward
|
|
// eleven o'clock, the angle the cone's flank mirrors, so its sheen runs
|
|
// the length of the flank toward the light as a broad streak. The nub
|
|
// curves too tightly to mirror the box's edges: it carries a broad soft
|
|
// round patch of it over its lit side and top, with no point.
|
|
let el = acos(clamp(rv.z, -1.0, 1.0))
|
|
let az = atan2(rv.y, rv.x)
|
|
let daz = abs(az + 1.97)
|
|
let dm = 1.0 - smoothstep(0.18, 0.36, rn)
|
|
let hk = normalize(kl + vec3(0.0, 0.0, 1.0))
|
|
let lobe = pow(max(dot(n, hk), 0.0), 6.0)
|
|
let box = mix((1.0 - smoothstep(0.10, 0.45, abs(el - 0.80))) * (1.0 - smoothstep(0.30, 0.95, min(daz, 6.2832 - daz))) * 0.16, lobe * 0.16, dm)
|
|
let sky = smoothstep(0.25, -0.85, rv.y) * 0.06
|
|
let flr = smoothstep(0.35, 0.85, rv.y) * 0.04
|
|
// The key light's own reflection, soft, on the cone only.
|
|
let spec = pow(max(dot(n, hk), 0.0), 30.0) * 0.10 * (1.0 - dm)
|
|
// The red: the ground's own red where the cap faces the eye, brighter
|
|
// where it turns to the light, deeper where it turns away, a little
|
|
// darker toward the thin edge and softly in the fillet round the nub.
|
|
var face = ground * (0.15 + 1.30 * diff)
|
|
face = face * mix(1.0, 0.92, smoothstep(0.6, 1.0, rn)) * (1.0 - 0.15 * h0.y)
|
|
// The gloss: the soft box's sheen, the sky and the floor, the key
|
|
// light's soft reflection, all in step with the room's light where the
|
|
// knob stands (as the ground is: brighter near the top of the window,
|
|
// dimmer at its foot), so the sheen keeps the same share over the red
|
|
// there and a knob low in the window is not washed pale; tinted by the
|
|
// lacquer; under the hand the sheen lifts.
|
|
let up = 1.0 - disabled
|
|
let room = clamp(ground.r / 0.35, 0.4, 1.2)
|
|
let gl = (box + sky + flr) * room * (1.0 + 0.45 * hover * up) * (1.0 - 0.5 * h0.y)
|
|
face = face + vec3(1.0, 0.62, 0.65) * gl
|
|
face = face + vec3(1.0, 0.86, 0.86) * spec * room * (1.0 + 0.3 * hover) * up
|
|
// Where the sheen would carry the red past full, it rolls off instead,
|
|
// so the lit flank keeps its gradation.
|
|
face = Cap.tone(face, 1.0, 0.7)
|
|
// THE CLEAR COAT over the red, glass smooth, so it mirrors the room
|
|
// crisply on top of the colour without changing it; it follows the
|
|
// cap's own surface, a revolve, so nothing it mirrors moves as the
|
|
// knob turns. The cone mirrors
|
|
// a ring of the room some 45 degrees out from the eye: the sheet's
|
|
// studio as its chrome mirrors it, faintly, only its bright parts (the
|
|
// soft box across the top; the grey room and the dark wall to the right
|
|
// sink into the red), kept off the lower side so the shaded flank stays
|
|
// as deep; the key light's soft box toward eleven o'clock, a narrow
|
|
// window mirrored down the lit flank as a bright band, level across,
|
|
// its edges under two device pixels soft at any size; and down the
|
|
// middle of that the key light itself, a hot line some two and a half
|
|
// device pixels wide at any size in a slightly wider glow, brightest on
|
|
// the middle of the flank and tapering toward either end. On the cone
|
|
// the mirrored azimuth turns with the point's own, so the distance
|
|
// across the band in device pixels is the azimuth's distance times the
|
|
// radius. The fillet round the nub's foot sweeps through every angle as
|
|
// it rolls into the nub, so the window, the studio and the hot line
|
|
// fade in only past it, on the flank, and leave no crease there. The
|
|
// nub keeps its broad soft patch, a little brighter, with no point.
|
|
let nc = n
|
|
let rvc = vec3(2.0 * nc.z * nc.x, 2.0 * nc.z * nc.y, 2.0 * nc.z * nc.z - 1.0)
|
|
let rxy = max(length(rvc.xy), 0.001)
|
|
let elc = acos(clamp(rvc.z, -1.0, 1.0))
|
|
let dzc = abs(atan2(rvc.y, rvc.x) + 1.97)
|
|
let wd = min(dzc, 6.2832 - dzc)
|
|
let wpx = wd * rl / px
|
|
let fil = smoothstep(0.34, 0.46, rn)
|
|
let elw = 1.0 - smoothstep(0.40, 0.50, abs(elc - 0.80))
|
|
let ring = max(lq_studio(vec3(rvc.x / rxy, rvc.y / rxy, 0.0)).x - 0.66, 0.0) * 2.4
|
|
let studio = ring * (1.0 - smoothstep(-0.30, 0.20, rvc.y / rxy)) * (1.0 - smoothstep(0.45, 0.90, abs(elc - 0.80)))
|
|
let win = (1.0 - smoothstep(-0.9, 0.9, (wd - 0.16) * rl / px)) * elw
|
|
let gs = 1.5 + 0.035 * rc / px
|
|
let along = elw * fil * (0.15 + 0.85 * exp(-(rn - 0.62) * (rn - 0.62) / 0.018)) * (1.0 - smoothstep(0.86, 0.97, rn))
|
|
let core = exp(-wpx * wpx / 2.25) * along
|
|
let glow = exp(-wpx * wpx / (gs * gs)) * along
|
|
// The coat is laid over the toned red as a screen, so it only lightens,
|
|
// and together it never takes more than four fifths of the way to
|
|
// white: the hot line stays a pale glint, never a blown patch, at rest,
|
|
// under the hand or held. The soft box and the studio follow the room's
|
|
// light, as the red's own sheen does, and so does the hot line's glow;
|
|
// its core only partly, so a knob at the foot of the window still shows
|
|
// a small bright glint without a pale smear round it. Under the hand
|
|
// the coat lifts a little with the sheen beneath it.
|
|
let coat = ((studio * 0.05 + win * 0.18) * fil + lobe * 0.16 * dm) * room * (1.0 + 0.20 * hover * up) * (1.0 - 0.5 * h0.y) * (1.0 - 0.6 * disabled)
|
|
let glint = min((0.7 * core * mix(1.0, room, 0.25) + 0.3 * glow * room) * 0.9, 0.85) * up
|
|
let keep = max((vec3(1.0, 1.0, 1.0) - vec3(1.0, 0.90, 0.90) * min(coat, 0.6)) * (1.0 - glint), vec3(0.2, 0.2, 0.2))
|
|
face = vec3(1.0, 1.0, 1.0) - (vec3(1.0, 1.0, 1.0) - face) * keep
|
|
// The thin edge: a bright line of the coat where it faces the light,
|
|
// the dark outline.
|
|
let ld = dot(u, vec2(-0.40, -0.92))
|
|
face = mix(face, vec3(1.0, 0.88, 0.88), Finish.band(dc, px, px * 2.0, px) * smoothstep(0.2, 0.85, ld) * 0.92 * up)
|
|
// Disabled, the lacquer greys toward its own lightness without growing
|
|
// darker, so the cap stays the ground's lacquer, dulled, not a hole.
|
|
let fl = Finish.lum(face)
|
|
face = mix(face, mix(face, vec3(fl, fl, fl), 0.45), disabled)
|
|
face = mix(face, mix(vec3(0.110, 0.016, 0.020), vec3(0.20, 0.10, 0.11), disabled), Finish.band(dc, 0.0, px, px))
|
|
|
|
// THE BAR: polished steel through the nub along the value's angle, a
|
|
// ball at either end lying on the cone close to the nub, with a short
|
|
// stub of bar, about a ball's radius, showing between: the balls 0.08 of
|
|
// the cap's radius, their middles 0.42 out. On a small knob the balls
|
|
// keep 1.6 points of radius and the stubs a point, so they read, and sit
|
|
// that much further out; on a knob too small to hold them they fade out.
|
|
let dir = vec2(-sin(ve), cos(ve))
|
|
let per = vec2(-dir.y, dir.x)
|
|
let lp = vec2(dot(q, dir), dot(q, per))
|
|
let vis = smoothstep(4.0, 7.0, rc)
|
|
let br = max(0.08 * rc, min(1.6, 0.2 * rc))
|
|
let bb = min(0.27 * rc + max(0.074 * rc, 1.0) + br, 0.92 * rc - br)
|
|
let bw = max(0.03 * rc, 0.6)
|
|
// The bar's shadow on the lacquer, down and to the right of it, where
|
|
// the bar is out of the nub.
|
|
let sh_o = vec2(0.30, 0.70) * bw * 1.4
|
|
let qs = q - sh_o
|
|
let ls = vec2(dot(qs, dir), dot(qs, per))
|
|
let ds = length(vec2(ls.x - clamp(ls.x, -bb, bb), ls.y)) - bw
|
|
let bar_sh = (1.0 - smoothstep(-bw * 0.6, bw * 1.2, ds)) * smoothstep(0.21, 0.28, abs(ls.x) / rcap) * vis
|
|
face = face * (1.0 - 0.45 * bar_sh * (1.0 - 0.5 * disabled))
|
|
let ca = Finish.cover(dc, px)
|
|
res = Finish.over(res, vec4(face * ca, ca))
|
|
// The bar: round, its axis half its radius over the cone 0.27 out from
|
|
// the middle, so it comes out of the nub's side; it shows where its top
|
|
// stands above the cap's height there.
|
|
let w = clamp(lp.y / bw, -1.0, 1.0)
|
|
let bz = 0.3169 + (bw / rcap) * (0.5 + sqrt(max(1.0 - w * w, 0.0)))
|
|
let shows = clamp((bz - h0.x) * rcap / px + 0.5, 0.0, 1.0)
|
|
let dbar = length(vec2(lp.x - clamp(lp.x, -bb, bb), lp.y)) - bw
|
|
let bcol = lq_bar_ink(per, w, hover, disabled)
|
|
let bcov = Finish.cover(dbar, px) * shows * vis
|
|
res = Finish.over(res, vec4(mix(bcol, vec3(0.10, 0.05, 0.06), Finish.band(dbar, 0.0, px * 0.7, px) * 0.6) * bcov, bcov))
|
|
// The balls: the sheet's polished chrome.
|
|
let tip = c + dir * bb
|
|
res = mix(res, lq_ball(res, p, c - dir * bb, br, px, hover, 0.0, disabled, 0.6), vis)
|
|
res = mix(res, lq_ball(res, p, tip, br, px, hover, 0.0, disabled, 0.6), vis)
|
|
// The pip in the middle of the ball at the value's end: a little over a
|
|
// quarter of the ball across, so the chrome shows all round it, its
|
|
// middle pale ice and its edge the value's blue. On a small knob it is
|
|
// never less than a device pixel in radius and all the value's blue, so
|
|
// the pointing ball tells from the other. Held, it lights white-hot and
|
|
// a halo of the blue spreads over the ball; disabled it is a dark lens.
|
|
let rp = max(0.27 * br, px)
|
|
let dp = length(p - tip)
|
|
let xp = max(dp - rp, 0.0)
|
|
let halo = exp(-(xp * xp) / max(2.0 * 0.16 * br * br, 0.001)) * 0.75 * held * up * vis
|
|
res = Finish.over(res, vec4(0.227, 0.655, 0.965, 1.0) * halo)
|
|
let pin = clamp(dp / rp, 0.0, 1.0)
|
|
let pale = mix(vec3(0.24, 0.68, 1.0), vec3(0.62, 0.86, 1.0), smoothstep(1.2 * px, 2.5 * px, rp))
|
|
var pcol = mix(mix(pale, vec3(0.97, 0.99, 1.0), held), mix(vec3(0.227, 0.655, 0.965), vec3(0.50, 0.78, 1.0), held), smoothstep(0.30, 1.0, pin))
|
|
pcol = mix(pcol, vec3(0.09, 0.10, 0.12), disabled)
|
|
res = Finish.over(res, vec4(pcol, 1.0) * Finish.cover(dp - rp, px) * vis)
|
|
// Focus: the ice ring round the well.
|
|
let fd = rl - ring_out
|
|
let ring = Finish.ring_out(fd, 0.0, px, px) * 0.9 + Finish.glow(fd - px, 0.7) * 0.35 * (1.0 - Finish.cover(fd, px))
|
|
return Finish.over(res, vec4(0.498, 0.706, 0.910, 1.0) * clamp(ring, 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 = 5.0
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
|
|
}
|
|
mod.widgets.RotaryKnob.draw_bg.pixel = fn() {
|
|
let m = 5.0
|
|
let px = lq_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 lq_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, lq_ground_at(self.world.xy - p + c, self.draw_pass.camera_projection * (self.draw_pass.camera_view * vec4(0.0, 0.0, 0.0, 1.0)), self.draw_pass.camera_projection * (self.draw_pass.camera_view * vec4(1.0, 1.0, 0.0, 1.0))))
|
|
}
|
|
mod.widgets.RotaryFlat.clip_x = false
|
|
mod.widgets.RotaryFlat.clip_y = false
|
|
mod.widgets.RotaryFlat.draw_bg.vertex = fn() {
|
|
let m = 5.0
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
|
|
}
|
|
mod.widgets.RotaryFlat.draw_bg.pixel = fn() {
|
|
let m = 5.0
|
|
let px = lq_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 lq_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, lq_ground_at(self.world.xy - p + c, self.draw_pass.camera_projection * (self.draw_pass.camera_view * vec4(0.0, 0.0, 0.0, 1.0)), self.draw_pass.camera_projection * (self.draw_pass.camera_view * vec4(1.0, 1.0, 0.0, 1.0))))
|
|
}
|
|
|
|
// ---------------------------------------------------------------- fields
|
|
|
|
// A field well that keeps inside its own box: a text field clips what it
|
|
// holds, so its right outline takes the box's last whole column and its lip
|
|
// the last whole row. Focus lights a lamp along the foot of the well: a
|
|
// pale line with the lamp's rim colour under it and a soft halo rising on
|
|
// the floor; the outline takes a little of the blue.
|
|
let lq_field = fn(p: vec2, size: vec2, o: vec2, px: float, fill: vec3, hover: float, focus: float, disabled: float) -> vec4 {
|
|
let lo = vec2(lq_snap(0.0, o.x, px), lq_snap(0.0, o.y, px))
|
|
// The box's far edges rounded DOWN to a whole device pixel; the bottom
|
|
// outline one pixel above the last row, the lip on it.
|
|
let hi = vec2(lq_snap(size.x - px * 0.49, o.x, px), lq_snap(size.y - px * 0.49, o.y, px) - px)
|
|
let c = (lo + hi) * 0.5
|
|
let d = Material.sd_box(p, c, (hi - lo) * 0.5, 2.0)
|
|
// Under the hand the floor lifts 0.025 and the outline takes a fifth of
|
|
// the lamp's blue.
|
|
var f = mix(fill + vec3(0.025, 0.025, 0.027) * hover, vec3(0.200, 0.090, 0.100), disabled)
|
|
var res = lq_well(p, lo, hi, 2.0, px, f, 0.0, disabled, 1.0)
|
|
let fo = focus * (1.0 - disabled)
|
|
let ho = hover * (1.0 - disabled) * (1.0 - focus)
|
|
res = Finish.over(res, vec4(0.40, 0.62, 0.84, 1.0) * Finish.band(d, 0.0, px, px) * (0.45 * fo + 0.2 * ho))
|
|
// The foot lamp: the two rows above the bottom outline, inset past the
|
|
// corners.
|
|
let y1 = hi.y - px
|
|
let infloor = Finish.cover(d + px, px) * step(lo.x + 3.0, p.x) * step(p.x, hi.x - 3.0)
|
|
let dy = max(y1 - px - p.y, 0.0)
|
|
let g = (0.34 * exp(-(dy * dy) / (2.0 * 0.7 * 0.7)) + 0.14 * exp(-(dy * dy) / (2.0 * 2.0 * 2.0))) * step(p.y, y1 - px) * infloor
|
|
res = Finish.over(res, vec4(0.40, 0.62, 0.84, 1.0) * g * fo)
|
|
let rim = step(y1 - px, p.y) * step(p.y, y1) * infloor
|
|
let core = step(y1 - px * 2.0, p.y) * step(p.y, y1 - px) * infloor
|
|
res = Finish.over(res, vec4(0.40, 0.62, 0.84, 1.0) * rim * fo)
|
|
res = Finish.over(res, vec4(0.84, 0.94, 1.0, 1.0) * core * fo)
|
|
return res
|
|
}
|
|
|
|
// Text fields: a flat well 24 points tall, -0.10 against the ground. 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 = lq_px(self.draw_pass.dpi_factor)
|
|
return lq_field(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, vec3(0.125, 0.043, 0.051), self.hover, self.focus, self.disabled)
|
|
}
|
|
mod.widgets.TextInputFlat.padding = mod.turtle.Inset{left: 8.0 right: 8.0 top: 3.0 bottom: 3.0}
|
|
mod.widgets.TextInputFlat.min_height = 24.0
|
|
mod.widgets.TextInputFlat.draw_cursor.color = #f4faff
|
|
mod.widgets.TextInputFlat.draw_text.color = #f6e8e6
|
|
mod.widgets.TextInputFlat.draw_text.color_empty = #a88486
|
|
mod.widgets.TextInputFlat.draw_text.color_disabled = #b07a7c
|
|
|
|
// The number field: the same well, its stepper a pair of small caps joined
|
|
// by an engraved line.
|
|
mod.widgets.NumberField.draw_bg.pixel = fn() {
|
|
let px = lq_px(self.draw_pass.dpi_factor)
|
|
return lq_field(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, vec3(0.125, 0.043, 0.051), self.hover, self.focus, self.disabled)
|
|
}
|
|
mod.widgets.NumberField.spin_width = 14.0
|
|
mod.widgets.NumberSpin.width = 14.0
|
|
// The widget's own filled triangles are hidden; the face strokes a 5 x 3
|
|
// point chevron in a 1 dp line of #e0c4c4 on each cap instead.
|
|
mod.widgets.NumberSpin.draw_text.color = #00000000
|
|
mod.widgets.NumberSpin.draw_text.color_disabled = #00000000
|
|
mod.widgets.NumberSpin.draw_bg.pixel = fn() {
|
|
let px = lq_px(self.draw_pass.dpi_factor)
|
|
let o = self.rect_pos
|
|
let p = self.pos * self.rect_size
|
|
// The stepper lies inside the field's well: its own floor is the
|
|
// field's, set off by an engraved divider (dark, then light) and split
|
|
// by an engraved line across the middle. The field's outline and lip
|
|
// stay the field's.
|
|
let x0 = lq_snap(0.0, o.x, px)
|
|
let xr = lq_snap(self.rect_size.x - px * 0.49, o.x, px) - px
|
|
let yt = lq_snap(0.0, o.y, px) + px
|
|
let yb = lq_snap(self.rect_size.y - px * 0.49, o.y, px) - px * 2.0
|
|
let mid = lq_snap(self.rect_size.y * 0.5, o.y, px)
|
|
let live = 1.0 - self.disabled
|
|
let inx = step(x0 + px * 2.0, p.x) * step(p.x, xr)
|
|
let upper = inx * step(yt, p.y) * step(p.y, mid - px)
|
|
let lower = inx * step(mid + px, p.y) * step(p.y, yb)
|
|
// Under the hand a half lifts a little; pressed, it sinks with a shade
|
|
// under its top edge.
|
|
var res = vec4(1.0, 1.0, 1.0, 1.0) * 0.035 * (upper * max(0.0, self.hot) + lower * max(0.0, -self.hot)) * live
|
|
let su = 0.45 * (1.0 - smoothstep(0.0, 3.0, p.y - yt)) * upper * max(0.0, self.down)
|
|
let sd = 0.45 * (1.0 - smoothstep(0.0, 3.0, p.y - mid - px)) * lower * max(0.0, -self.down)
|
|
res = Finish.over(res, vec4(0.0, 0.0, 0.0, 1.0) * (su + sd) * live)
|
|
let vin = step(yt + 2.0, p.y) * step(p.y, yb - 2.0)
|
|
let dark = step(x0, p.x) * step(p.x, x0 + px) * vin
|
|
let light = step(x0 + px, p.x) * step(p.x, x0 + px * 2.0) * vin
|
|
let hin = step(x0 + px * 2.0 + 2.0, p.x) * step(p.x, xr - 2.0)
|
|
let hdark = step(mid - px, p.y) * step(p.y, mid) * hin
|
|
let hlight = step(mid, p.y) * step(p.y, mid + px) * hin
|
|
res = Finish.over(res, vec4(0.110, 0.016, 0.020, 1.0) * (dark + hdark))
|
|
res = Finish.over(res, vec4(0.520, 0.360, 0.380, 1.0) * (light + hlight) * 0.8)
|
|
let ink = mix(vec3(0.880, 0.780, 0.790), vec3(0.55, 0.38, 0.40), self.disabled)
|
|
let cx = (x0 + px * 2.0 + xr) * 0.5
|
|
let cu = (yt + mid) * 0.5
|
|
let cd = (mid + yb) * 0.5
|
|
let du = min(lq_seg(p, vec2(cx - 2.5, cu + 1.5), vec2(cx, cu - 1.5)), lq_seg(p, vec2(cx, cu - 1.5), vec2(cx + 2.5, cu + 1.5)))
|
|
let dd = min(lq_seg(p, vec2(cx - 2.5, cd - 1.5), vec2(cx, cd + 1.5)), lq_seg(p, vec2(cx, cd + 1.5), vec2(cx + 2.5, cd - 1.5)))
|
|
let k = min(du, dd) - px * 0.5
|
|
return Finish.over(res, vec4(ink, 1.0) * Finish.cover(k, px))
|
|
}
|
|
|
|
// The drop-down: a button cap with an 18 point chevron zone at its right,
|
|
// behind an engraved divider.
|
|
mod.widgets.DropDownFlat.padding = mod.turtle.Inset{left: 8.0 right: 26.0 top: 3.0 bottom: 3.0}
|
|
mod.widgets.DropDownFlat.clip_x = false
|
|
mod.widgets.DropDownFlat.clip_y = false
|
|
mod.widgets.DropDownFlat.draw_bg.vertex = fn() {
|
|
let m = 7.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 engraved divider and the chevron, laid on a cap or a well.
|
|
let lq_chevron_zone = fn(dst: vec4, p: vec2, lo: vec2, hi: vec2, px: float, disabled: float) -> vec4 {
|
|
let dx = hi.x - 18.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(0.110, 0.040, 0.046, 1.0) * dark * 0.85)
|
|
res = Finish.over(res, vec4(0.96, 0.95, 0.97, 1.0) * light * 0.8)
|
|
// The chevron, engraved in the steel: 7 x 4 points, a 1.5 point dark
|
|
// stroke with a light line under it.
|
|
let cc = vec2(dx + 9.0, (lo.y + hi.y) * 0.5)
|
|
let sd = Sdf2d.viewport(p - vec2(0.0, px))
|
|
sd.move_to(cc.x - 3.5, cc.y - 2.0)
|
|
sd.line_to(cc.x, cc.y + 2.0)
|
|
sd.line_to(cc.x + 3.5, cc.y - 2.0)
|
|
sd.stroke(vec4(0.96, 0.95, 0.97, 0.55), 0.75)
|
|
res = Finish.over(res, sd.result)
|
|
let sdf = Sdf2d.viewport(p)
|
|
sdf.move_to(cc.x - 3.5, cc.y - 2.0)
|
|
sdf.line_to(cc.x, cc.y + 2.0)
|
|
sdf.line_to(cc.x + 3.5, cc.y - 2.0)
|
|
sdf.stroke(vec4(mix(vec3(0.118, 0.086, 0.094), vec3(0.20, 0.16, 0.17), disabled), 1.0), 0.75)
|
|
return Finish.over(res, sdf.result)
|
|
}
|
|
mod.widgets.DropDownFlat.draw_bg.pixel = fn() {
|
|
let m = 7.0
|
|
let px = lq_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(lq_snap(0.0, o.x, px), lq_snap(0.0, o.y, px))
|
|
let hi = vec2(lq_snap(self.rect_size.x, o.x, px), lq_snap(self.rect_size.y, o.y, px))
|
|
var res = lq_steel(p, lo, hi, 3.0, px, o, self.hover, max(self.down, self.active), 0.0, self.disabled, self.focus, 1.0, vec3(0.0, 0.0, 0.0))
|
|
return lq_chevron_zone(res, p, lo, hi, px, self.disabled)
|
|
}
|
|
mod.widgets.DropDownFlat.draw_text.color = #1e1618
|
|
mod.widgets.DropDownFlat.draw_text.color_hover = #120c0d
|
|
mod.widgets.DropDownFlat.draw_text.color_focus = #1e1618
|
|
mod.widgets.DropDownFlat.draw_text.color_down = #120c0d
|
|
mod.widgets.DropDownFlat.draw_text.color_disabled = #2e2426
|
|
|
|
// The combo box: the drop-down's steel cap, its words typed on it, its list
|
|
// opened from the same chevron zone.
|
|
mod.widgets.ComboBoxFlat.padding = mod.turtle.Inset{left: 8.0 right: 26.0 top: 3.0 bottom: 3.0}
|
|
mod.widgets.ComboBoxFlat.input.min_height = 0.0
|
|
mod.widgets.ComboBoxFlat.draw_bg.vertex = fn() {
|
|
let m = 7.0
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
|
|
}
|
|
mod.widgets.ComboBoxFlat.draw_bg.pixel = fn() {
|
|
let m = 7.0
|
|
let px = lq_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(lq_snap(0.0, o.x, px), lq_snap(0.0, o.y, px))
|
|
let hi = vec2(lq_snap(self.rect_size.x, o.x, px), lq_snap(self.rect_size.y, o.y, px))
|
|
var res = lq_steel(p, lo, hi, 3.0, px, o, self.hover, self.down, 0.0, self.disabled, self.focus, 1.0, vec3(0.0, 0.0, 0.0))
|
|
return lq_chevron_zone(res, p, lo, hi, px, self.disabled)
|
|
}
|
|
mod.widgets.ComboBoxFlat.input.draw_text.color = #1e1618
|
|
mod.widgets.ComboBoxFlat.input.draw_text.color_hover = #120c0d
|
|
mod.widgets.ComboBoxFlat.input.draw_text.color_focus = #120c0d
|
|
mod.widgets.ComboBoxFlat.input.draw_text.color_down = #120c0d
|
|
mod.widgets.ComboBoxFlat.input.draw_text.color_disabled = #2e2426
|
|
mod.widgets.ComboBoxFlat.input.draw_text.color_empty = #4a3e40
|
|
mod.widgets.ComboBoxFlat.input.draw_cursor.color = #120c0d
|
|
|
|
// Open lists: a flat dark face, a 1 dp top line, rows that lift 0.03 under
|
|
// the hand, and the chosen row filled with the accent -- the one fill in
|
|
// this family.
|
|
let lq_menu = fn(p: vec2, size: vec2, px: float) -> vec4 {
|
|
let lo = vec2(0.0, 0.0)
|
|
let hi = size
|
|
let c = (lo + hi) * 0.5
|
|
let d = Material.sd_box(p, c, (hi - lo) * 0.5, 3.0)
|
|
var col = mix(vec3(0.200, 0.066, 0.076), vec3(0.150, 0.046, 0.054), p.y / max(size.y, 1.0))
|
|
col = mix(col, vec3(0.50, 0.34, 0.36), Finish.band(d, px, px * 2.0, px) * step(p.y, c.y) * step(3.0, p.y))
|
|
col = mix(col, vec3(0.060, 0.010, 0.014), Finish.band(d, 0.0, px, px))
|
|
let a = Finish.cover(d, px)
|
|
return vec4(col * a, a)
|
|
}
|
|
let lq_row = fn(p: vec2, size: vec2, px: float, hover: float, active: float, disabled: float) -> vec4 {
|
|
let top = 1.0 - step(px, p.y)
|
|
var col = vec4(1.0, 1.0, 1.0, 1.0) * 0.035 * hover * (1.0 - disabled)
|
|
let sel = vec3(0.227, 0.498, 0.753)
|
|
let fill = vec4(mix(sel, vec3(0.45, 0.66, 0.86), top), 1.0)
|
|
return mix(col, fill, active)
|
|
}
|
|
// The open list is clipped to its own box by the popup that holds it, so
|
|
// its shadow cannot be drawn from here.
|
|
mod.widgets.PopupMenuFlat.draw_bg.pixel = fn() {
|
|
let px = lq_px(self.draw_pass.dpi_factor)
|
|
return lq_menu(self.pos * self.rect_size, self.rect_size, px)
|
|
}
|
|
mod.widgets.PopupMenuItem.draw_bg.pixel = fn() {
|
|
let px = lq_px(self.draw_pass.dpi_factor)
|
|
return lq_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 = lq_px(self.draw_pass.dpi_factor)
|
|
return lq_menu(self.pos * self.rect_size, self.rect_size, px)
|
|
}
|
|
mod.widgets.ComboBoxFlat.draw_item.pixel = fn() {
|
|
let px = lq_px(self.draw_pass.dpi_factor)
|
|
return lq_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 raised cap; the chosen answer
|
|
// sinks, flat and tinted toward the light, its legend lit and a lamp under
|
|
// it.
|
|
mod.widgets.SegmentedControlFlat.draw_bg.vertex = fn() {
|
|
let m = 7.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 = 7.0
|
|
let px = lq_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(lq_snap(0.0, o.x, px), lq_snap(0.0, o.y, px))
|
|
let hi = vec2(lq_snap(self.rect_size.x, o.x, px), lq_snap(self.rect_size.y, o.y, px))
|
|
return lq_steel(p, lo, hi, 3.0, px, o, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, vec3(0.0, 0.0, 0.0))
|
|
}
|
|
// The chosen answer is a push button pressed in: Day, Week and Month are
|
|
// three steel caps and the chosen one is sunk deep between the others, a
|
|
// well of darker, cooler brushed steel. The strip lays the answers 2 points
|
|
// in from its edge, so the well is the answer's box grown by those 2 points
|
|
// with the strip's own 3 point corners: at either end of the strip it IS the
|
|
// strip's end, outline and corners; between two answers it is a rounded key
|
|
// pressed in between raised neighbours. Black under the top edge, shaded down
|
|
// both walls, a cool lip caught along its foot, and a lamp under the word.
|
|
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 px = lq_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(lq_snap(-2.0, o.x, px), lq_snap(-2.0, o.y, px))
|
|
let hi = vec2(lq_snap(self.rect_size.x + 2.0, o.x, px), lq_snap(self.rect_size.y + 2.0, o.y, px))
|
|
let cc = (lo + hi) * 0.5
|
|
let d = Material.sd_box(p, cc, (hi - lo) * 0.5, 3.0)
|
|
let hh = hi.y - lo.y
|
|
let t = clamp((p.y - lo.y) / max(hh, 1.0), 0.0, 1.0)
|
|
// The floor: the strip's steel pushed in, deeper and cooler, its
|
|
// brushing still running through it.
|
|
let bq = p + o
|
|
let s1 = Finish.brushed(bq, vec2(1.0, 0.0), px * 1.2) - 0.5
|
|
let s2 = Finish.brushed(bq + vec2(31.0, 7.0), vec2(1.0, 0.0), px * 4.5) - 0.5
|
|
let fl = mix(0.345, 0.275, t) + s1 * 0.042 + s2 * 0.025
|
|
var col = vec3(fl, fl, fl) * vec3(0.90, 0.955, 1.04)
|
|
// The inner shadow: black right under the top edge, gone in about
|
|
// seven rows; along each wall a shade gone in five.
|
|
let top = exp(-max(p.y - lo.y - px, 0.0) / (3.5 * px))
|
|
let wl = 0.80 * exp(-max(p.x - lo.x - px, 0.0) / (2.5 * px))
|
|
let wr = 0.80 * exp(-max(hi.x - px - p.x, 0.0) / (2.5 * px))
|
|
let dk = 1.0 - (1.0 - top) * (1.0 - wl) * (1.0 - wr)
|
|
col = mix(col, vec3(0.0, 0.0, 0.0), dk)
|
|
// The cool lip the light catches along the well's foot, above the
|
|
// outline, then the outline itself.
|
|
let lip = Finish.band(d, px, px * 2.0, px) * step(hi.y - px * 2.5, p.y)
|
|
col = mix(col, vec3(0.50, 0.60, 0.70), lip * 0.8)
|
|
col = mix(col, vec3(0.070, 0.030, 0.036), Finish.band(d, 0.0, px, px))
|
|
let inside = Finish.cover(d, px)
|
|
var res = vec4(col, 1.0) * inside
|
|
// The lamp, centred under the word: the widget centres the word by its
|
|
// advance, which sets the ink about a point left of the middle, so the
|
|
// lamp stands a point left of it too. The core's top row 8.4 points
|
|
// under the segment's middle. Round it a broad soft pool on the floor.
|
|
let c = vec2(self.rect_size.x * 0.5 - 1.0, self.rect_size.y * 0.5)
|
|
let y0 = lq_snap(c.y + 8.4167, o.y, px)
|
|
let q = vec2((p.x - c.x) / max((hi.x - lo.x) * 0.3, 1.0), (p.y - (y0 + px)) / 3.2)
|
|
let pool = exp(-dot(q, q)) * 0.16 * inside
|
|
res = Finish.over(res, vec4(0.405, 0.593, 0.752, 1.0) * pool)
|
|
let xs = vec2(lq_snap(c.x - 5.3333, o.x, px), lq_snap(c.x + 5.3333, o.x, px))
|
|
let lamp = lq_lamp(res, p, xs, y0, px, 1.0, 1.0, vec3(0.910, 0.918, 0.922), vec3(0.405, 0.593, 0.752))
|
|
res = mix(res, lamp, Finish.cover(d + px, px))
|
|
return res
|
|
}
|
|
// Cap labels at the family's 8 points, the chosen one included.
|
|
mod.widgets.SegmentedControlFlat.draw_text.text_style.font_size = 8.0
|
|
mod.widgets.SegmentedControlFlat.draw_text_selected.text_style.font_size = 8.0
|
|
mod.widgets.SegmentedControlFlat.draw_text.color = #1e1618
|
|
mod.widgets.SegmentedControlFlat.draw_text_selected.color = #d4eaff
|
|
mod.widgets.SegmentedControlVertical.draw_pill.radius = -1.0
|
|
|
|
// Tabs: joined segments of one strip. Each quiet tab is a raised segment
|
|
// whose joint with the next is engraved, dark then light; the tab in use
|
|
// sinks like the chosen segment, its word lit and the lamp under it.
|
|
mod.widgets.Tabs.height = 26.0
|
|
mod.widgets.Tabs.clip_x = false
|
|
mod.widgets.Tabs.clip_y = false
|
|
let lq_tab = fn(pos: vec2, size: vec2, o: vec2, dpi: float, hover: float, active: float) -> vec4 {
|
|
let m = 7.0
|
|
let px = lq_px(dpi)
|
|
let p = pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
let lo = vec2(lq_snap(0.0, o.x, px), lq_snap(0.0, o.y, px))
|
|
let hi = vec2(lq_snap(size.x, o.x, px), lq_snap(size.y, o.y, px))
|
|
let inx = step(lo.x, p.x) * step(p.x, hi.x)
|
|
var res = lq_steel(p, lo - vec2(4.0, 0.0), hi + vec2(4.0, 0.0), 0.0, px, o, hover, active, 1.0, 0.0, 0.0, 1.0, vec3(0.0, 0.0, 0.0)) * inx
|
|
// The tab in use is a well cut between its neighbours: a shade down
|
|
// both walls of it.
|
|
let iny = step(lo.y, p.y) * step(p.y, hi.y)
|
|
let wall = exp(-max(min(p.x - lo.x, hi.x - p.x) - px, 0.0) / (2.5 * px))
|
|
res = Finish.over(res, vec4(0.0, 0.0, 0.0, 1.0) * 0.55 * wall * inx * iny * step(lo.y + px, p.y) * step(p.y, hi.y - px) * active)
|
|
// The joint: the black outline on the left, just outside the box, and a
|
|
// light line inside it; the dark line inside the right edge.
|
|
let left_out = step(lo.x - px, p.x) * (1.0 - step(lo.x, p.x))
|
|
let left_in = step(lo.x, p.x) * (1.0 - step(lo.x + px, p.x))
|
|
let right_in = step(hi.x - px, p.x) * (1.0 - step(hi.x, p.x))
|
|
res = Finish.over(res, vec4(0.070, 0.030, 0.036, 1.0) * (left_out + right_in) * iny)
|
|
res = Finish.over(res, vec4(0.94, 0.94, 0.96, 1.0) * left_in * iny * step(lo.y + px, p.y) * step(p.y, hi.y - px) * (1.0 - active))
|
|
// Under the sunk tab only, a 1 dp lip #474c56 on the ground.
|
|
let lip = step(hi.y, p.y) * (1.0 - step(hi.y + px, p.y)) * inx
|
|
res = Finish.over(res, vec4(0.70, 0.52, 0.54, 1.0) * lip * active)
|
|
// The tab in use carries the chosen segment's lamp under its word. The
|
|
// strip sets its words flush left 10 points in and the face cannot know
|
|
// how long a word is, so the lamp is centred on the first 27 points of
|
|
// it -- under a one-word name such as Mixer, Effects or Notes it stands
|
|
// in the middle; under a longer word it stays under its first letters.
|
|
// The core's top row 7.75 points under the tab's middle, as on a
|
|
// segment.
|
|
let lx = lo.x + 23.5
|
|
let xs = vec2(lq_snap(lx - 5.3333, o.x, px), lq_snap(lx + 5.3333, o.x, px))
|
|
return lq_lamp(res, p, xs, lq_snap((lo.y + hi.y) * 0.5 + 7.75, o.y, px), px, active, 1.0, vec3(0.910, 0.918, 0.922), vec3(0.405, 0.593, 0.752))
|
|
}
|
|
mod.widgets.Tabs.draw_tab.vertex = fn() {
|
|
let m = 7.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 = 7.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 = 7.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 lq_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 lq_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 lq_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 = #e0c4c4
|
|
// The close cross stands on steel of either depth (the chosen well, a
|
|
// raised tab under the hand): a pale blue-white stroke in a dark halo reads
|
|
// on both. The add plus beside the strip is drawn as before.
|
|
mod.widgets.Tabs.draw_mark.pixel = fn() {
|
|
let sdf = Sdf2d.viewport(self.pos * self.rect_size)
|
|
let cx2 = self.rect_size.x * 0.5
|
|
let cy2 = self.rect_size.y * 0.5
|
|
let r = min(self.rect_size.x, self.rect_size.y) * 0.26
|
|
let a = mix(0.7853981634, 0.0, self.plus)
|
|
let ax = cos(a) * r
|
|
let ay = sin(a) * r
|
|
let x = 1.0 - self.plus
|
|
sdf.move_to(cx2 - ax, cy2 - ay)
|
|
sdf.line_to(cx2 + ax, cy2 + ay)
|
|
sdf.stroke(vec4(0.070, 0.030, 0.036, 0.75 * x), 2.6)
|
|
sdf.move_to(cx2 + ay, cy2 - ax)
|
|
sdf.line_to(cx2 - ay, cy2 + ax)
|
|
sdf.stroke(vec4(0.070, 0.030, 0.036, 0.75 * x), 2.6)
|
|
let ink = mix(self.color, vec4(0.722, 0.816, 0.910, 1.0), x)
|
|
sdf.move_to(cx2 - ax, cy2 - ay)
|
|
sdf.line_to(cx2 + ax, cy2 + ay)
|
|
sdf.stroke(ink, 1.25)
|
|
sdf.move_to(cx2 + ay, cy2 - ax)
|
|
sdf.line_to(cx2 - ay, cy2 + ax)
|
|
sdf.stroke(ink, 1.25)
|
|
return sdf.result
|
|
}
|
|
mod.widgets.Tabs.draw_text.color = #1e1618
|
|
// The tab in use answers like the chosen segment: its ink #f6e8e6 in
|
|
// semibold, in the sunk blue well; quiet tabs grey regular.
|
|
mod.widgets.Tabs.draw_text_active.color = #d4eaff
|
|
mod.widgets.Tabs.draw_text.text_style.font_size = 8.0
|
|
mod.widgets.Tabs.draw_text_active.text_style = mod.theme.font_bold{font_size: 8.0}
|
|
|
|
// ---------------------------------------------------------------- feedback
|
|
|
|
// The progress bar: an 8 point well; the value is the lit bar of the
|
|
// sliders, a pale core row over a body row in the middle of the floor, its
|
|
// halo on the floor round it.
|
|
mod.widgets.ProgressBarFlat.height = 8.0
|
|
mod.widgets.ProgressBarFlat.draw_bg.pixel = fn() {
|
|
let px = lq_px(self.draw_pass.dpi_factor)
|
|
let o = self.rect_pos
|
|
let p = self.pos * self.rect_size
|
|
let th = min(self.thickness, self.rect_size.y)
|
|
let y0 = lq_snap((self.rect_size.y - th) * 0.5, o.y, px)
|
|
let x0 = lq_snap(self.rect_size.x * self.start, o.x, px)
|
|
let x1 = lq_snap(self.rect_size.x * self.end, o.x, px)
|
|
let lo = vec2(x0, y0)
|
|
let hi = vec2(x1, y0 + lq_snap(th, 0.0, px) - px)
|
|
var res = vec4(0.0, 0.0, 0.0, 0.0)
|
|
if self.track > 0.5 {
|
|
res = lq_well(p, lo, hi, 2.0, px, vec3(0.125, 0.043, 0.051), 0.0, 0.0, 1.0)
|
|
}
|
|
let fill = self.fill_color()
|
|
let ins = 2.0
|
|
let fl = vec2(lo.x + ins, lo.y + ins)
|
|
let span = hi.x - lo.x - ins * 2.0
|
|
var f0 = fl.x
|
|
var f1 = fl.x + span * clamp((self.value - self.start) / max(self.end - self.start, 0.001), 0.0, 1.0)
|
|
if self.value < 0.0 {
|
|
// Still: the sweep would need the pass clock, and a window at rest
|
|
// reads no clock, so the unknown is a segment standing in the
|
|
// middle with its ends fading.
|
|
f0 = fl.x + span * 0.3
|
|
f1 = fl.x + span * 0.7
|
|
}
|
|
// The fill is lit like the lamp: a pale core row over a row of the
|
|
// role colour in the middle of the well's floor, the halo on the floor
|
|
// round it and past its ends.
|
|
var fade = fill.a * step(0.001, f1 - f0)
|
|
if self.value < 0.0 {
|
|
fade = fade * smoothstep(f0, f0 + span * 0.12, p.x) * smoothstep(f1, f1 - span * 0.12, p.x)
|
|
}
|
|
let lb = lq_lit_bar(vec4(0.0, 0.0, 0.0, 0.0), p, vec2(f0, lo.y + px), vec2(f1, hi.y - px), vec2(lo.x + px, hi.x - px), px, fill.rgb, 0.0)
|
|
res = Finish.over(res, lb * fade)
|
|
return res * self.opacity
|
|
}
|
|
mod.widgets.ProgressBarFlat.draw_bg.color_primary = #8cc6ff
|
|
|
|
// The level meter: cells of 3 dp and a 1 dp black gap on black glass in a
|
|
// slim well; green, then yellow toward the top, red at the end; unlit cells
|
|
// stand at a fifth of the lit. The clip lamp is an LED. 8 points thick.
|
|
mod.widgets.LevelMeter.height = 8.0
|
|
mod.widgets.LevelMeterColumn.width = 8.0
|
|
mod.widgets.LevelMeter.draw_bg.pixel = fn() {
|
|
let px = lq_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)
|
|
// The window: black glass in a 1 dp outline with its lip.
|
|
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(lq_snap(wlo.x, o.x, px), lq_snap(wlo.y, o.y, px))
|
|
let hi = vec2(lq_snap(whi.x, o.x, px), lq_snap(whi.y, o.y, px) - px)
|
|
var res = lq_well(p, lo, hi, 1.0, px, vec3(0.040, 0.010, 0.014), 0.0, 0.0, 1.0)
|
|
let pad = 2.0 * px
|
|
let span = max(1.0, bar - pad * 2.0)
|
|
let pitch = px * 4.0
|
|
let cell = step(fract((a - pad) / pitch), 0.75)
|
|
let share = clamp((a - pad) / span, 0.0, 1.0)
|
|
let inb = step(pad, a) * step(a, bar - pad) * step(pad, b) * step(b, across - pad) * step(p.y, hi.y - px)
|
|
var lit_col = vec3(0.690, 0.820, 0.260)
|
|
lit_col = mix(lit_col, vec3(0.902, 0.788, 0.251), step(self.hot_at, share))
|
|
lit_col = mix(lit_col, vec3(0.941, 0.318, 0.227), step(self.hot_to, share))
|
|
// The first device pixel of each cell a touch lighter and paler.
|
|
let edge = step(fract((a - pad) / pitch), 0.25)
|
|
lit_col = mix(lit_col, mix(lit_col, vec3(0.85, 0.87, 0.8), 0.35), edge)
|
|
let on = step(share, self.level) * step(0.001, self.level)
|
|
let unlit = vec3(0.180, 0.080, 0.086)
|
|
let col = mix(unlit, lit_col, on)
|
|
res = Finish.over(res, vec4(col, 1.0) * cell * inb * self.opacity)
|
|
// The hold: one cell at full strength.
|
|
let hold_at = pad + span * self.hold
|
|
let hc = step(abs(a - hold_at), pitch * 0.5) * step(0.002, self.hold)
|
|
res = Finish.over(res, vec4(mix(vec3(0.690, 0.820, 0.260), vec3(0.941, 0.318, 0.227), step(self.hot_to, self.hold)), 1.0) * hc * cell * inb)
|
|
// The lamp.
|
|
if self.lamp > 0.5 {
|
|
let lc = mix(vec2(bar + 3.0 + across * 0.5, sz.y * 0.5), vec2(sz.x * 0.5, across * 0.5), v)
|
|
// A small red lamp, 3.5 points: a white-hot core, a red rim and a
|
|
// soft red halo when lit; a dark lens when out.
|
|
let rl = clamp(across * 0.5 - 1.5, 1.0, 1.75)
|
|
res = lq_led(res, p, lc, vec2(rl, rl), step(0.5, self.over), 0.45, px, vec3(1.0, 0.957, 0.941), vec3(0.941, 0.318, 0.227))
|
|
}
|
|
return res * self.opacity
|
|
}
|
|
|
|
// ---------------------------------------------------------------- surfaces
|
|
|
|
// The scroll bar: an 8 point well, the thumb a 6 point cap inset 1 point,
|
|
// no gloss, no glow.
|
|
mod.widgets.ScrollBar.bar_size = 8.0
|
|
mod.widgets.ScrollBar.bar_side_margin = 1.0
|
|
mod.widgets.ScrollBar.draw_bg.pixel = fn() {
|
|
let px = lq_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(lq_snap(0.0, o.x, px), lq_snap(0.0, o.y, px))
|
|
let hi = vec2(lq_snap(sz.x, o.x, px), lq_snap(sz.y, o.y, px))
|
|
var res = lq_well(p, lo, hi, 2.0, px, vec3(0.125, 0.043, 0.051), 0.0, 0.0, 0.0)
|
|
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), vec2(1.0, t0), v)
|
|
let thi = mix(vec2(t1, sz.y - 1.0), vec2(sz.x - 1.0, t1), v)
|
|
let slo = vec2(lq_snap(tlo.x, o.x, px), lq_snap(tlo.y, o.y, px))
|
|
let shi = vec2(lq_snap(thi.x, o.x, px), lq_snap(thi.y, o.y, px))
|
|
res = Finish.over(res, lq_smoke(p, slo, shi, 1.5, px, max(self.hover, self.drag), 0.0, 0.0, 0.0, 0.0))
|
|
return res * self.opacity
|
|
}
|
|
|
|
// The housing's grain: monochrome, one value per device pixel, sd 0.008.
|
|
// The library's hash nearly repeats every 97 device pixels (97 x 0.1031 is
|
|
// almost whole), which showed as a lattice over a wide ground; each pixel
|
|
// is first sent to a far cell picked by the hash of its own, and the grain
|
|
// is read there, so no shift lines the pattern up again.
|
|
let lq_grain = fn(p: vec2, o: vec2, px: float) -> float {
|
|
let i = floor((p + o) / 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))) * 0.014
|
|
}
|
|
|
|
// A raised lacquer slab: a soft drop that stays within the gap to its
|
|
// neighbours, the dark outline with a chrome bezel inside it, a deep red
|
|
// face falling toward the foot, a gloss over its upper part whose lower
|
|
// edge is a soft arc rising toward the sides, a bright streak along the top,
|
|
// and the lowest rows lifted by reflected light.
|
|
let lq_panel = fn(p: vec2, size: vec2, o: vec2, px: float, r: float, hover: float) -> vec4 {
|
|
let lo = vec2(lq_snap(0.0, o.x, px), lq_snap(0.0, o.y, px))
|
|
let hi = vec2(lq_snap(size.x, o.x, px), lq_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 sh1 = Finish.drop(p, c, h - vec2(1.0, 0.5), r, vec2(0.0, 2.0), 1.6) * 0.6
|
|
let sh2 = Finish.drop(p, c, h - vec2(3.5, 2.0), r, vec2(0.0, 4.5), 3.4) * 0.45
|
|
let sh = 1.0 - (1.0 - sh1) * (1.0 - sh2)
|
|
let contact = Finish.drop(p, c, h, r, vec2(0.0, px), px * 0.6) * 0.8
|
|
var res = vec4(0.03, 0.0, 0.0, 1.0) * (1.0 - (1.0 - sh) * (1.0 - contact))
|
|
let hh = max(hi.y - lo.y, 1.0)
|
|
let y = p.y - lo.y
|
|
let t = clamp(y / hh, 0.0, 1.0)
|
|
var col = mix(vec3(0.520, 0.082, 0.098), vec3(0.230, 0.034, 0.040), smoothstep(0.0, 1.0, t))
|
|
// The gloss band, its depth set in points so a tall slab and a short
|
|
// card carry the same reflection.
|
|
let sx = clamp((p.x - c.x) / max(h.x, 1.0), -1.0, 1.0)
|
|
let hz = min(24.0, hh * 0.42) - 7.0 * sx * sx
|
|
let band = 1.0 - smoothstep(hz - 4.0, hz + 1.0, y)
|
|
let gcol = mix(vec3(0.660, 0.235, 0.255), vec3(0.590, 0.140, 0.160), clamp(y / max(hz, 1.0), 0.0, 1.0))
|
|
col = mix(col, gcol, band * 0.9)
|
|
// The streak of a light held in the top of the gloss.
|
|
let wy = (y - 3.0) / 2.2
|
|
col = mix(col, vec3(0.96, 0.72, 0.74), 0.38 * exp(-wy * wy) * (1.0 - 0.55 * sx * sx))
|
|
// Reflected light low on the face.
|
|
col = mix(col, vec3(0.50, 0.22, 0.24), 0.30 * smoothstep(hh - 10.0, hh, y))
|
|
col = col + vec3(0.03, 0.012, 0.014) * hover
|
|
col = col + vec3(1.0, 1.0, 1.0) * lq_grain(p, o, px)
|
|
// The chrome bezel, bright at the top, dimmer down the sides.
|
|
let bez = mix(vec3(0.86, 0.66, 0.68), vec3(0.50, 0.36, 0.38), smoothstep(0.0, 0.8, t))
|
|
col = mix(col, bez, Finish.band(d, px, px * 2.5, px) * 0.9)
|
|
col = mix(col, vec3(0.060, 0.008, 0.012), Finish.band(d, 0.0, px, px))
|
|
let a = Finish.cover(d, px)
|
|
res = Finish.over(res, vec4(col * a, a))
|
|
return res
|
|
}
|
|
// A tray: a well cut into the lacquer. A darker red floor, the ground's rim
|
|
// shading its top, the chrome rim, and the gloss caught low on the far wall.
|
|
let lq_tray = fn(p: vec2, size: vec2, o: vec2, px: float, r: float) -> vec4 {
|
|
let lo = vec2(lq_snap(0.0, o.x, px), lq_snap(0.0, o.y, px))
|
|
let hi = vec2(lq_snap(size.x, o.x, px), lq_snap(size.y, o.y, px) - px)
|
|
let c = (lo + hi) * 0.5
|
|
let d = Material.sd_box(p, c, (hi - lo) * 0.5, r)
|
|
var res = lq_well(p, lo, hi, r, px, vec3(0.270, 0.040, 0.050), 0.0, 0.0, 1.0)
|
|
let a = Finish.cover(d + px * 2.0, px)
|
|
let y = p.y - lo.y
|
|
let hh = max(hi.y - lo.y, 1.0)
|
|
let shade = 0.55 * (1.0 - smoothstep(0.0, 8.0, y - px * 2.0)) * a
|
|
let low = 0.16 * smoothstep(hh - 16.0, hh - 3.0, y) * (1.0 - smoothstep(hh - 3.0, hh - 1.0, y)) * a
|
|
let g = lq_grain(p, o, px) * a
|
|
var col = res.rgb * (1.0 - shade) + vec3(0.62, 0.26, 0.28) * low * res.a + vec3(g, g, g) * res.a
|
|
return vec4(col, res.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 = 12.0
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
|
|
}
|
|
mod.widgets.PanelView.draw_bg.pixel = fn() {
|
|
let m = 12.0
|
|
let px = lq_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 {
|
|
return lq_tray(p, self.rect_size, self.rect_pos, px, 5.0)
|
|
}
|
|
return lq_panel(p, self.rect_size, self.rect_pos, px, 5.0, 0.0)
|
|
}
|
|
|
|
// The rounded view keeps the colour its caller gives it, square to the
|
|
// family: a true 2 point corner and, when it is opaque, a 1 dp outline.
|
|
mod.widgets.RoundedView.draw_bg.pixel = fn() {
|
|
let px = lq_px(self.draw_pass.dpi_factor)
|
|
let p = self.pos * self.rect_size
|
|
let lo = vec2(self.border_inset.x, self.border_inset.y)
|
|
let hi = self.rect_size - vec2(self.border_inset.z, self.border_inset.w)
|
|
let c = (lo + hi) * 0.5
|
|
let h = max((hi - lo) * 0.5, vec2(0.5, 0.5))
|
|
let r = min(2.0 * max(1.0, self.border_radius), min(h.x, h.y))
|
|
let d = Material.sd_box(p, c, h, r)
|
|
var fill = self.color
|
|
if self.color_2.x > -0.5 {
|
|
fill = mix(self.color, self.color_2, mix(self.pos.y, self.pos.x, self.gradient_fill_horizontal))
|
|
}
|
|
var ink = self.border_color
|
|
if self.border_size < 0.01 {
|
|
ink = vec4(0.110, 0.016, 0.020, fill.a)
|
|
}
|
|
let a = Finish.cover(d, px)
|
|
var col = mix(fill.rgb, ink.rgb, Finish.band(d, 0.0, max(self.border_size, px), px) * ink.a)
|
|
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)
|
|
}
|
|
|
|
// The card: a raised panel of its own; 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 = 12.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 = 12.0
|
|
let px = lq_px(self.draw_pass.dpi_factor)
|
|
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
return lq_panel(p, self.rect_size, self.rect_pos, px, 5.0, self.hover)
|
|
}
|
|
|
|
// List rows: 20 points, the text 10 points in; under the hand the row
|
|
// lifts 0.03. The chosen row is a shallow well -- the chosen segment's blue
|
|
// floor, a dark line and a short shade under its top edge, a light lip on
|
|
// its foot -- with a short lamp standing at its left.
|
|
mod.widgets.ListItem.height = 20.0
|
|
mod.widgets.ListItemOne.height = 20.0
|
|
mod.widgets.ListItem.padding = mod.turtle.Inset{left: 10.0 right: 8.0 top: 0.0 bottom: 0.0}
|
|
mod.widgets.ListItemOne.padding = mod.turtle.Inset{left: 10.0 right: 8.0 top: 0.0 bottom: 0.0}
|
|
mod.widgets.ListItem.draw_bg.pixel = fn() {
|
|
let px = lq_px(self.draw_pass.dpi_factor)
|
|
let o = self.rect_pos
|
|
let p = self.pos * self.rect_size
|
|
let live = 1.0 - self.disabled
|
|
var res = vec4(1.0, 1.0, 1.0, 1.0) * (0.03 * self.hover + 0.02 * self.down) * live
|
|
let y0 = lq_snap(0.0, o.y, px)
|
|
let y1 = lq_snap(self.rect_size.y, o.y, px)
|
|
let top = p.y - y0
|
|
let t = clamp(top / max(y1 - y0, 1.0), 0.0, 1.0)
|
|
var col = mix(vec3(0.150, 0.050, 0.060), vec3(0.110, 0.036, 0.044), t)
|
|
col = mix(col, vec3(0.0, 0.0, 0.0), 0.35 * (1.0 - smoothstep(0.0, 4.0, top - px)))
|
|
col = mix(col, vec3(0.110, 0.016, 0.020), 1.0 - step(px, top))
|
|
col = mix(col, vec3(0.62, 0.46, 0.48), step(y1 - px, p.y))
|
|
var sel = vec4(col, 1.0)
|
|
let lc = vec2(lq_snap(3.5, o.x, px) - px * 0.5, (y0 + y1) * 0.5)
|
|
sel = lq_led(sel, p, lc, vec2(px * 0.5 + 0.01, max((y1 - y0) * 0.22, 2.0)), live, 0.8, px, vec3(0.88, 0.96, 1.0), vec3(0.40, 0.62, 0.84))
|
|
res = mix(res, Finish.over(sel, res), self.selected)
|
|
return res
|
|
}
|
|
mod.widgets.ListItem.draw_text.color = #f6e8e6
|
|
mod.widgets.ListItem.draw_text_2.color = #d8b8b8
|
|
|
|
// A chip is a small seated steel cap; its role colour only tints the face.
|
|
// Its label is a cap label: 8 points, dark, stamped in whatever ink the
|
|
// palette hands it.
|
|
mod.widgets.ChipFlat.draw_text.text_style.font_size = 8.0
|
|
mod.widgets.ChipFlat.draw_text.pixel = fn() {
|
|
let raw = self.sample_text_pixel()
|
|
return vec4(vec3(0.118, 0.086, 0.094) * raw.a, raw.a)
|
|
}
|
|
mod.widgets.ChipFlat.draw_bg.pixel = fn() {
|
|
let px = lq_px(self.draw_pass.dpi_factor)
|
|
let o = self.rect_pos
|
|
let p = self.pos * self.rect_size
|
|
let lo = vec2(lq_snap(0.0, o.x, px), lq_snap(0.0, o.y, px))
|
|
let hi = vec2(lq_snap(self.rect_size.x, o.x, px), lq_snap(self.rect_size.y, o.y, px) - px)
|
|
let l = Finish.lum(self.color.rgb)
|
|
let tint = (self.color.rgb - vec3(l, l, l)) * 0.35 * self.color.a
|
|
var res = lq_steel(p, lo, hi, 2.0, px, o, self.hover, self.down, 0.0, 0.0, 0.0, 0.0, tint)
|
|
if self.keyed > 0.0 {
|
|
let d = Material.sd_box(p, (lo + hi) * 0.5, (hi - lo) * 0.5, 2.0)
|
|
res = Finish.over(res, vec4(0.40, 0.62, 0.84, 1.0) * Finish.band(d, 0.0, px, px) * self.keyed)
|
|
}
|
|
return res
|
|
}
|
|
|
|
// ---------------------------------------------------------------- ink
|
|
|
|
// Words sit embossed-dark: a 1 dp shadow of black at half strength directly
|
|
// under every glyph of a label. Read from the glyph one device pixel above,
|
|
// so it costs one more sample and nothing else.
|
|
// On a cap the label also sinks with it: held, the glyphs are read one
|
|
// device pixel higher in the atlas, so they stand one pixel lower in the
|
|
// well. Small labels come from the grayscale atlas, large ones from the
|
|
// multi-channel one; both paths carry the shadow and the nudge.
|
|
mod.widgets.ButtonFlat.draw_text.pixel = fn() {
|
|
let nudge = dFdy(self.t.xy) * 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 < 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))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (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))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
|
|
}
|
|
return self.sample_text_pixel()
|
|
}
|
|
// A template made from another before this sheet ran keeps its own copy
|
|
// of the label's draw, so each cap template takes the literal.
|
|
mod.widgets.Button.draw_text.pixel = fn() {
|
|
let nudge = dFdy(self.t.xy) * 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 < 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))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (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))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
|
|
}
|
|
return self.sample_text_pixel()
|
|
}
|
|
mod.widgets.ButtonPrimary.draw_text.pixel = fn() {
|
|
let nudge = dFdy(self.t.xy) * 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 < 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))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (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))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
|
|
}
|
|
return self.sample_text_pixel()
|
|
}
|
|
mod.widgets.ButtonSecondary.draw_text.pixel = fn() {
|
|
let nudge = dFdy(self.t.xy) * 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 < 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))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (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))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
|
|
}
|
|
return self.sample_text_pixel()
|
|
}
|
|
mod.widgets.ButtonTertiary.draw_text.pixel = fn() {
|
|
let nudge = dFdy(self.t.xy) * 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 < 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))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (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))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
|
|
}
|
|
return self.sample_text_pixel()
|
|
}
|
|
mod.widgets.ButtonDanger.draw_text.pixel = fn() {
|
|
let nudge = dFdy(self.t.xy) * 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 < 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))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (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))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
|
|
}
|
|
return self.sample_text_pixel()
|
|
}
|
|
mod.widgets.ButtonOutline.draw_text.pixel = fn() {
|
|
let nudge = dFdy(self.t.xy) * 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 < 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))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (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))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
|
|
}
|
|
return self.sample_text_pixel()
|
|
}
|
|
mod.widgets.ButtonFlatter.draw_text.pixel = fn() {
|
|
let nudge = dFdy(self.t.xy) * 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 < 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))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (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))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
|
|
}
|
|
return self.sample_text_pixel()
|
|
}
|
|
mod.widgets.ButtonXs.draw_text.pixel = fn() {
|
|
let nudge = dFdy(self.t.xy) * 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 < 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))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (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))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
|
|
}
|
|
return self.sample_text_pixel()
|
|
}
|
|
mod.widgets.ButtonSm.draw_text.pixel = fn() {
|
|
let nudge = dFdy(self.t.xy) * 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 < 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))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (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))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
|
|
}
|
|
return self.sample_text_pixel()
|
|
}
|
|
mod.widgets.ButtonLg.draw_text.pixel = fn() {
|
|
let nudge = dFdy(self.t.xy) * 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 < 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))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (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))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
|
|
}
|
|
return self.sample_text_pixel()
|
|
}
|
|
mod.widgets.ButtonXl.draw_text.pixel = fn() {
|
|
let nudge = dFdy(self.t.xy) * 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 < 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))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (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))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
|
|
}
|
|
return self.sample_text_pixel()
|
|
}
|
|
|
|
// The same shadow on every label that stands on the ground or a cap; one
|
|
// literal per template, as the handbook advises. A lit legend (the tab or
|
|
// segment in use) takes a 2 dp halo of the LED's glow instead.
|
|
mod.widgets.Label.draw_text.pixel = fn() {
|
|
let main = self.sample_text_pixel()
|
|
if self.texture_index > 1.5 && self.texture_index < 2.5 {
|
|
let dxt = length(dFdx(self.t))
|
|
let dyt = length(dFdy(self.t))
|
|
let tex_size = self.msdf_texture.size()
|
|
let scale = (dxt + dyt) * tex_size.x * 0.5
|
|
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
|
|
let ps = clamp(self.t.xy - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
|
|
let c = self.get_color()
|
|
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
|
|
}
|
|
return main
|
|
}
|
|
mod.widgets.CheckBoxFlat.draw_text.pixel = fn() {
|
|
let main = self.sample_text_pixel()
|
|
if self.texture_index > 1.5 && self.texture_index < 2.5 {
|
|
let dxt = length(dFdx(self.t))
|
|
let dyt = length(dFdy(self.t))
|
|
let tex_size = self.msdf_texture.size()
|
|
let scale = (dxt + dyt) * tex_size.x * 0.5
|
|
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
|
|
let ps = clamp(self.t.xy - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
|
|
let c = self.get_color()
|
|
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
|
|
}
|
|
return main
|
|
}
|
|
mod.widgets.RadioButtonFlat.draw_text.pixel = fn() {
|
|
let main = self.sample_text_pixel()
|
|
if self.texture_index > 1.5 && self.texture_index < 2.5 {
|
|
let dxt = length(dFdx(self.t))
|
|
let dyt = length(dFdy(self.t))
|
|
let tex_size = self.msdf_texture.size()
|
|
let scale = (dxt + dyt) * tex_size.x * 0.5
|
|
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
|
|
let ps = clamp(self.t.xy - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
|
|
let c = self.get_color()
|
|
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
|
|
}
|
|
return main
|
|
}
|
|
mod.widgets.DropDownFlat.draw_text.pixel = fn() {
|
|
let main = self.sample_text_pixel()
|
|
if self.texture_index > 1.5 && self.texture_index < 2.5 {
|
|
let dxt = length(dFdx(self.t))
|
|
let dyt = length(dFdy(self.t))
|
|
let tex_size = self.msdf_texture.size()
|
|
let scale = (dxt + dyt) * tex_size.x * 0.5
|
|
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
|
|
let ps = clamp(self.t.xy - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
|
|
let c = self.get_color()
|
|
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
|
|
// Stamped into steel: a light line under the glyph, not a shadow.
|
|
let e = s * 0.42 * c.a
|
|
return main + vec4(0.96 * e, 0.95 * e, 0.97 * e, e) * (1.0 - main.a)
|
|
}
|
|
return main
|
|
}
|
|
mod.widgets.SegmentedControlFlat.draw_text.pixel = fn() {
|
|
let main = self.sample_text_pixel()
|
|
if self.texture_index > 1.5 && self.texture_index < 2.5 {
|
|
let dxt = length(dFdx(self.t))
|
|
let dyt = length(dFdy(self.t))
|
|
let tex_size = self.msdf_texture.size()
|
|
let scale = (dxt + dyt) * tex_size.x * 0.5
|
|
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
|
|
let ps = clamp(self.t.xy - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
|
|
let c = self.get_color()
|
|
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
|
|
// Stamped into steel: a light line under the glyph, not a shadow.
|
|
let e = s * 0.42 * c.a
|
|
return main + vec4(0.96 * e, 0.95 * e, 0.97 * e, e) * (1.0 - main.a)
|
|
}
|
|
return main
|
|
}
|
|
mod.widgets.SegmentedControlFlat.draw_text_selected.pixel = fn() {
|
|
let raw = self.sample_text_pixel()
|
|
// Lit blue-white, whatever ink the palette hands the chosen answer.
|
|
let main = vec4(vec3(0.831, 0.918, 1.0) * raw.a, raw.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.4 * c.a
|
|
return main + vec4(0.22 * a, 0.38 * a, 0.56 * a, a) * (1.0 - main.a)
|
|
}
|
|
return main
|
|
}
|
|
mod.widgets.Tabs.draw_text.pixel = fn() {
|
|
let main = self.sample_text_pixel()
|
|
if self.texture_index > 1.5 && self.texture_index < 2.5 {
|
|
let dxt = length(dFdx(self.t))
|
|
let dyt = length(dFdy(self.t))
|
|
let tex_size = self.msdf_texture.size()
|
|
let scale = (dxt + dyt) * tex_size.x * 0.5
|
|
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
|
|
let ps = clamp(self.t.xy - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
|
|
let c = self.get_color()
|
|
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
|
|
// Stamped into steel: a light line under the glyph, not a shadow.
|
|
let e = s * 0.42 * c.a
|
|
return main + vec4(0.96 * e, 0.95 * e, 0.97 * e, e) * (1.0 - main.a)
|
|
}
|
|
return main
|
|
}
|
|
mod.widgets.Tabs.draw_text_active.pixel = fn() {
|
|
let main = self.sample_text_pixel()
|
|
if self.texture_index > 1.5 && self.texture_index < 2.5 {
|
|
let dxt = length(dFdx(self.t))
|
|
let dyt = length(dFdy(self.t))
|
|
let tex_size = self.msdf_texture.size()
|
|
let scale = (dxt + dyt) * tex_size.x * 0.5
|
|
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
|
|
let 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.4 * c.a
|
|
return main + vec4(0.22 * a, 0.38 * a, 0.56 * a, a) * (1.0 - main.a)
|
|
}
|
|
return main
|
|
}
|
|
mod.widgets.SliderMinimal.draw_text.pixel = fn() {
|
|
let main = self.sample_text_pixel()
|
|
if self.texture_index > 1.5 && self.texture_index < 2.5 {
|
|
let dxt = length(dFdx(self.t))
|
|
let dyt = length(dFdy(self.t))
|
|
let tex_size = self.msdf_texture.size()
|
|
let scale = (dxt + dyt) * tex_size.x * 0.5
|
|
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
|
|
let ps = clamp(self.t.xy - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
|
|
let c = self.get_color()
|
|
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
|
|
}
|
|
return main
|
|
}
|
|
|
|
// ---------------------------------------------------------------- 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 -- in the widget catalogue's toolbar the
|
|
// search box, the number beside it and the drop down after it stood at three
|
|
// different heights -- and a sheet that reshapes the text box alone widens
|
|
// that gap instead of closing it. So the box metrics are stated ONCE here and
|
|
// handed to each field below, and a field added to the library later joins
|
|
// the row by being named here rather than by having these numbers copied
|
|
// into it.
|
|
// Fields and buttons share the one 24 point row.
|
|
let field_height = 24.0
|
|
let field_pad_v = 3.0
|
|
let field_radius = 1.0
|
|
// The outline is one device pixel; the faces below draw it themselves.
|
|
let field_border = 0.34
|
|
|
|
// The room a Fit field takes. A minimum height rather than a height,
|
|
// because a line of text is shorter than a row, and the padding with it:
|
|
// where a field is Fit, the padding IS its height, and two fields padded
|
|
// differently part company again the moment the type grows.
|
|
let field_room = fn(w) {
|
|
w.min_height = field_height
|
|
w.padding.top = field_pad_v
|
|
w.padding.bottom = field_pad_v
|
|
}
|
|
// The box a field draws. Given the draw object and not the widget, because
|
|
// not every field draws its own box, and because writing a corner onto a
|
|
// draw struct that has no corner would add a slot its shader never reads.
|
|
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: a number
|
|
// with a stepper, a date, a time. The height is all these take. A padding
|
|
// here would NOT hold the line off the box -- each of them measures its
|
|
// parts against the turtle's whole rect and draws them from the content
|
|
// origin, so the padding displaces the line instead of insetting it, and the
|
|
// number came out sitting on the bottom edge with its arrows split around it.
|
|
let field_frame = fn(w) {
|
|
w.min_height = field_height
|
|
}
|
|
|
|
field_room(mod.widgets.TextInput)
|
|
field_face(mod.widgets.TextInput.draw_bg)
|
|
field_room(mod.widgets.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)
|
|
// The number's box strokes its own hairline and declares no border size.
|
|
mod.widgets.NumberField.draw_bg.border_radius = field_radius
|
|
// The drag-number's corner is written into its own shader, so it takes the
|
|
// room and keeps the box it draws.
|
|
field_frame(mod.widgets.ValueInput)
|
|
field_frame(mod.widgets.DateField)
|
|
field_frame(mod.widgets.TimeField)
|
|
// A picker is a date field in an anchor: the field inside fills the frame.
|
|
field_frame(mod.widgets.DatePicker)
|
|
field_frame(mod.widgets.DateRangePicker)
|
|
// The chrome-less input a well holds must impose no height of its own. It is
|
|
// a TextInput, so it inherits the minimum above, and a minimum as tall as the
|
|
// whole field, applied inside a well already that tall, pushed the line out
|
|
// through the bottom of the box.
|
|
mod.widgets.WellInput.min_height = 0.0
|
|
|
|
// ---------------------------------------------------------------- rules
|
|
|
|
// A rule is engraved: one device pixel of the outline's black over one of
|
|
// light, across the middle of its room, no shadow.
|
|
mod.widgets.Hr.draw_bg.pixel = fn() {
|
|
let px = lq_px(self.draw_pass.dpi_factor)
|
|
let p = self.pos * self.rect_size
|
|
let y0 = lq_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.110, 0.016, 0.020, 1.0) * dark + vec4(0.62, 0.36, 0.38, 1.0) * light
|
|
}
|
|
mod.widgets.Vr.draw_bg.pixel = fn() {
|
|
let px = lq_px(self.draw_pass.dpi_factor)
|
|
let p = self.pos * self.rect_size
|
|
let x0 = lq_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.110, 0.016, 0.020, 1.0) * dark + vec4(0.520, 0.360, 0.380, 1.0) * light
|
|
}
|
|
|
|
// ---------------------------------------------------------------- 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
|
|
mod.widgets.View.clip_y = false
|
|
mod.widgets.Window.pass.clear_color = #6a0c10
|
|
// The window's own ground: #6a0c10 with the family's grain, monochrome, one
|
|
// value per device pixel, sd 0.008, peaks inside +/-0.02. The panels carry
|
|
// the same grain in the same window space, so it runs on under them.
|
|
mod.widgets.Window.show_bg = true
|
|
// The window's ground: candy-red lacquer seen under studio light. Bright red
|
|
// at the top falling to near-black wine at the foot, the sides a little
|
|
// darker; a broad soft reflection of a light over the upper middle, a
|
|
// long softbox streak under it. Lacquer is smooth: the grain is a dither only.
|
|
mod.widgets.Window.draw_bg.pixel = fn() {
|
|
let px = lq_px(self.draw_pass.dpi_factor)
|
|
let sz = max(self.rect_size, vec2(1.0, 1.0))
|
|
let p = self.pos * sz
|
|
var col = lq_ground_base(self.pos)
|
|
// The broad reflection of a studio light, over the upper middle.
|
|
let q1 = (p - vec2(sz.x * 0.34, sz.y * 0.15)) / vec2(max(sz.x * 0.12, 90.0), max(sz.y * 0.07, 45.0))
|
|
col = mix(col, vec3(0.94, 0.62, 0.64), 0.42 * exp(-dot(q1, q1)))
|
|
// Its hot middle, smaller and paler.
|
|
let q3 = q1 * vec2(2.2, 2.6) + vec2(0.0, 0.5)
|
|
col = mix(col, vec3(1.0, 0.80, 0.81), 0.22 * exp(-dot(q3, q3)))
|
|
// A softbox caught as a long thin streak a little lower: flat along
|
|
// its length, falling off softly at its ends and edges.
|
|
let q2 = (p - vec2(sz.x * 0.37, sz.y * 0.225)) / vec2(max(sz.x * 0.11, 80.0), 5.0)
|
|
let qx = q2.x * q2.x
|
|
col = mix(col, vec3(0.97, 0.74, 0.76), 0.17 * exp(-qx * qx - q2.y * q2.y))
|
|
let g = lq_grain(p, self.rect_pos, px)
|
|
return vec4(col + vec3(g, g, g), 1.0)
|
|
}
|
|
|
|
// ---------------------------------------------------------------- badge
|
|
|
|
// A count is a small seated plate of the brushed steel: the face its colour
|
|
// (the neutral one steel) brushed and lit from above, the one top line, the
|
|
// dark outline, dark ink.
|
|
mod.widgets.BadgePalette.neutral = #b4b3b8
|
|
mod.widgets.BadgePalette.on_neutral = #1e1618
|
|
mod.widgets.BadgeFlat.shape = mod.widgets.BadgeShape.Rounded
|
|
mod.widgets.BadgeFlat.radius = 2.0
|
|
mod.widgets.BadgeFlat.draw_bg.pixel = fn() {
|
|
let px = lq_px(self.draw_pass.dpi_factor)
|
|
let o = self.rect_pos
|
|
let p = self.pos * self.rect_size
|
|
// The box's edges rounded DOWN to a device pixel, so the bottom and
|
|
// right outline rows stay inside the quad.
|
|
let lo = vec2(lq_snap(0.0, o.x, px), lq_snap(0.0, o.y, px))
|
|
let hi = vec2(lq_snap(self.rect_size.x - px * 0.49, o.x, px), lq_snap(self.rect_size.y - px * 0.49, o.y, px))
|
|
let c = (lo + hi) * 0.5
|
|
let h = (hi - lo) * 0.5
|
|
let r = min(2.0, min(h.x, h.y))
|
|
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 sx = (p.x - c.x) / max(h.x, 1.0)
|
|
let k = lq_steel_tone(t, sx, p + o, px) / 0.70
|
|
var col = self.color.rgb * k
|
|
col = mix(col, col * vec3(1.10, 0.84, 0.86), 0.5 * smoothstep(0.66, 1.0, t))
|
|
col = mix(col, vec3(0.985, 0.985, 1.0), 0.85 * Finish.band(d, px, px * 2.0, px) * step(p.y, lo.y + r + px * 2.0) * step(lo.x + r * 0.5, p.x) * step(p.x, hi.x - r * 0.5) * self.color.a)
|
|
col = mix(col, vec3(0.070, 0.030, 0.036), Finish.band(d, 0.0, px, px))
|
|
let fa = max(self.color.a, Finish.band(d, 0.0, px, px))
|
|
let a = Finish.cover(d, px) * fa
|
|
return vec4(col * a, a)
|
|
}
|
|
|
|
// ---------------------------------------------------------------- states
|
|
|
|
// Every held state is written as a plain value that cuts in at once (the
|
|
// library writes some as snap() values, which a state held as a default --
|
|
// the states page -- never applies). Live, the cut is the same as the snap.
|
|
mod.widgets.ButtonFlat.animator.hover.on.from = {all: Snap}
|
|
mod.widgets.ButtonFlat.animator.hover.on.apply = {draw_bg: {down: 0.0 hover: 1.0} draw_text: {down: 0.0 hover: 1.0}}
|
|
mod.widgets.ButtonFlat.animator.hover.down.from = {all: Snap}
|
|
mod.widgets.ButtonFlat.animator.hover.down.apply = {draw_bg: {down: 1.0 hover: 1.0} draw_text: {down: 1.0 hover: 1.0}}
|
|
mod.widgets.CheckBoxFlat.animator.hover.down.from = {all: Snap}
|
|
mod.widgets.CheckBoxFlat.animator.hover.down.apply = {draw_bg: {down: 1.0 hover: 1.0} draw_text: {down: 1.0 hover: 1.0}}
|
|
mod.widgets.RadioButtonFlat.animator.hover.down.from = {all: Snap}
|
|
mod.widgets.RadioButtonFlat.animator.hover.down.apply = {draw_bg: {down: 1.0 hover: 1.0} draw_text: {down: 1.0 hover: 1.0}}
|
|
mod.widgets.TextInputFlat.animator.hover.on.from = {all: Snap}
|
|
mod.widgets.TextInputFlat.animator.hover.on.apply = {draw_bg: {down: 0.0 hover: 1.0} draw_text: {down: 0.0 hover: 1.0}}
|
|
mod.widgets.TextInputFlat.animator.hover.down.from = {all: Snap}
|
|
mod.widgets.TextInputFlat.animator.hover.down.apply = {draw_bg: {down: 1.0 hover: 1.0} draw_text: {down: 1.0 hover: 1.0}}
|
|
mod.widgets.DropDownFlat.animator.hover.on.from = {all: Snap}
|
|
mod.widgets.DropDownFlat.animator.hover.on.apply = {draw_bg: {down: 0.0 hover: 1.0} draw_text: {down: 0.0 hover: 1.0}}
|
|
mod.widgets.DropDownFlat.animator.hover.down.from = {all: Snap}
|
|
mod.widgets.DropDownFlat.animator.hover.down.apply = {draw_bg: {down: 1.0 hover: 1.0} draw_text: {down: 1.0 hover: 1.0}}
|
|
|
|
|
|
// ---------------------------------------------------------------- switches and meter
|
|
|
|
// THE METER sits in the same window as the readouts and the screen: the
|
|
// library's display housing drawn round its face -- the bezel ring, a shade
|
|
// of whatever ground it is set in with a device pixel of dark at its edge;
|
|
// the face with the surround's shadow falling across it from the top and a
|
|
// device pixel of black where the glass meets the bezel; the glass's quiet
|
|
// diagonal band and the light line inside its top edge; the lip of light on
|
|
// the ground under it -- with the same corner, so the meter and the
|
|
// readouts read as one family. The scale, the red zone, the needle and its
|
|
// pivot are the library's own.
|
|
mod.widgets.NeedleMeter.draw_bg.border_radius = uniform(theme.corner_radius * 0.5)
|
|
mod.widgets.NeedleMeter.draw_bg.color_bezel = uniform(theme.color_d_05)
|
|
mod.widgets.NeedleMeter.draw_bg.color_lip = uniform(theme.color_u_15)
|
|
mod.widgets.NeedleMeter.draw_bg.pixel = fn() {
|
|
let p = self.pos * self.rect_size
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let lo = vec2(Finish.snap(self.rect_pos.x, px), Finish.snap(self.rect_pos.y, px)) - self.rect_pos
|
|
let hi = vec2(Finish.snap(self.rect_pos.x + self.rect_size.x, px), Finish.snap(self.rect_pos.y + self.rect_size.y, px) - px) - self.rect_pos
|
|
let c = (lo + hi) * 0.5
|
|
let ho = (hi - lo) * 0.5
|
|
let r_out = min(self.border_radius, min(ho.x, ho.y))
|
|
let outer = Material.sd_box(p, c, ho, r_out)
|
|
let b = Finish.snap(2.0, px)
|
|
let hf = max(ho - vec2(b, b), vec2(0.5, 0.5))
|
|
let rf = max(r_out - b, 0.0)
|
|
let face_d = Material.sd_box(p, c, hf, rf)
|
|
let pv = self.pivot()
|
|
let rr = self.scale_radius()
|
|
let q = p - pv
|
|
let r = length(q)
|
|
let ang = atan2(q.x, 0.0 - q.y)
|
|
let sweep = self.sweep * 0.017453293
|
|
let t = ang / sweep + 0.5
|
|
let on_scale = step(-0.0001, t) * step(t, 1.0001)
|
|
var face = self.color.rgb
|
|
// The scale: a hairline arc and a tick at every division.
|
|
let arc = Finish.cover(abs(r - rr) - px * 0.5, px) * on_scale
|
|
let n = max(floor(self.majors + 0.5), 1.0) * max(floor(self.minors + 0.5), 1.0)
|
|
let k = floor(clamp(t, 0.0, 1.0) * n + 0.5)
|
|
let ak = (k / n - 0.5) * sweep
|
|
let perp = abs(q.x * cos(ak) + q.y * sin(ak))
|
|
let mn = max(floor(self.minors + 0.5), 1.0)
|
|
let major = 1.0 - step(0.5, k - mn * floor(k / mn + 0.0001))
|
|
let len = mix(self.tick_minor, self.tick_major, major)
|
|
let radial = max(rr - len - r, r - rr)
|
|
let tick = Finish.cover(perp - px * mix(0.5, 1.0, major), px) * Finish.cover(radial, px) * step(0.0, 0.0 - q.y * cos(ak) + q.x * sin(ak))
|
|
face = mix(face, self.color_scale.rgb, max(arc, tick))
|
|
if self.zone < 0.999 {
|
|
let zone_t = Finish.cover((self.zone - t) * sweep * r, px) * Finish.cover((t - 1.0) * sweep * r, px)
|
|
let zone_r = Finish.cover(max(rr + px - r, r - rr - 2.5), px)
|
|
face = mix(face, self.color_zone.rgb, zone_t * zone_r)
|
|
}
|
|
// The needle, from under the pivot cover to just past the scale.
|
|
let na = self.needle_angle(self.value)
|
|
let nd = vec2(sin(na), 0.0 - cos(na))
|
|
let along = dot(q, nd)
|
|
let across = abs(q.x * nd.y - q.y * nd.x)
|
|
let needle = Finish.cover(across - px * 0.5, px) * Finish.cover(along - (rr + 3.0), px) * Finish.cover(0.0 - along - max(self.pivot_size - px, 0.0), px)
|
|
face = mix(face, self.color_needle.rgb, needle)
|
|
let pd = r - self.pivot_size
|
|
face = mix(face, mix(self.color_needle.rgb, self.color.rgb, 0.35), Finish.cover(pd, px))
|
|
face = mix(face, face * 0.55, Finish.band(pd, 0.0, px, px))
|
|
// The housing: the surround's shadow across the face from the top, the
|
|
// black device pixel at the glass's edge, and the glass over it all.
|
|
let insh = Finish.inset(p, c, hf, rf, vec2(0.0, 2.0), 1.8)
|
|
face = face * (1.0 - 0.55 * insh)
|
|
face = mix(face, face * 0.4, Finish.band(face_d, 0.0, px, px))
|
|
let u = (p - (c - hf)) / max(hf * 2.0, vec2(1.0, 1.0))
|
|
let diag = u.x + u.y * 0.6
|
|
let band = smoothstep(0.0, 0.12, diag) * (1.0 - smoothstep(0.28, 0.5, diag))
|
|
let top = Finish.cover(abs(p.y - (c.y - hf.y) - px * 1.5) - px * 0.5, px) * 0.6
|
|
let ga = clamp(0.03 * (band + top), 0.0, 1.0) * Finish.cover(face_d + px, px)
|
|
face = face * (1.0 - ga) + vec3(ga, ga, ga)
|
|
let bz = self.color_bezel
|
|
var col = vec4(bz.rgb * bz.a, bz.a)
|
|
col = Finish.over(col, vec4(0.0, 0.0, 0.0, 0.45 * Finish.band(outer, 0.0, px, px)))
|
|
col = mix(col, vec4(face, 1.0), Finish.cover(face_d, px))
|
|
col = col * Finish.cover(outer, px)
|
|
let lip = Finish.ring_out(outer, 0.0, px, px) * step(hi.y, p.y)
|
|
let la = self.color_lip.a * lip
|
|
return Finish.over(vec4(self.color_lip.rgb * la, la), col) * self.color.a * self.opacity
|
|
}
|
|
|
|
// ---------------------------------------------------------------- lamps
|
|
// LAMPS: an LED, as the hardware in his pictures has them. The emitter is
|
|
// the light: at full current its middle burns toward white and its rim is
|
|
// its most saturated colour; at lower current the middle is no longer
|
|
// white, the colour deeper, the glow smaller; out, it is dark tinted glass,
|
|
// its colour barely there. Round it a bezel: a raised ring lit from the
|
|
// upper left, or a socket cut into the surface, its upper wall in shadow
|
|
// and a catch light on its lower rim. Over the lens one crisp highlight of
|
|
// a dome. Its light is added, never painted: a tight hot halo and a short
|
|
// bloom in its own hue, screened over whatever is there and gone in about
|
|
// two radii; on a pale ground, where light can barely brighten, its hue is
|
|
// also laid round it as a tint that keeps the ground's luminance (the
|
|
// library's halo rule: at most 0.45 one device pixel out, halving every
|
|
// 0.4 of its reach, gone at the reach), so it never darkens or greys its
|
|
// ground. A bar is the same light drawn out into a light pipe.
|
|
let lq_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 lq_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 lq_led_lamp_hue = fn(ink: vec3, vivid: float) -> vec3 {
|
|
let f = lq_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 lq_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 = lq_led_lamp_full(ink)
|
|
let hue = lq_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 = lq_led_lamp_add(res, mix(hue, white, 0.15 * pw) * halo * pw * hwin * win, ground)
|
|
}
|
|
// THE BEAD: a glossy dome of coloured glass. Its colour deepens toward
|
|
// the top; light pools low inside, where it comes through the dome; lit,
|
|
// its middle burns toward white at full current and its rim is its most
|
|
// saturated colour.
|
|
let top = smoothstep(-1.0, 0.6, q.y)
|
|
let pool = exp(-(q.y - 0.55) * (q.y - 0.55) / 0.09 - q.x * q.x / 0.30)
|
|
var on = mix(hue * mix(0.55, 0.8, L), mix(b, hue, 0.5), top)
|
|
on = mix(on, min(mix(b, hue, 0.4) + vec3(0.35, 0.35, 0.35), white), pool * 0.5)
|
|
on = mix(on, vec3(1.0, 0.99, 0.975), exp(-t * t / 0.20) * smoothstep(0.5, 1.0, L) * 0.85)
|
|
on = mix(on, hue * (0.82 + 0.18 * L), smoothstep(0.62, 1.0, t))
|
|
on = on * (0.42 + 0.58 * L)
|
|
// Out: dark tinted glass on a dark ground, a grey bead on a pale one,
|
|
// its colour barely there, the same dome, a catch light along its lower
|
|
// rim.
|
|
let offc = mix(mix(vec3(0.085, 0.09, 0.115), ground * 0.62, pale), b * 0.4, 0.12)
|
|
var off = mix(offc * 0.62, offc * 1.08, top)
|
|
off = mix(off, min(offc + vec3(0.18, 0.18, 0.18), white), pool * 0.35)
|
|
off = off * mix(1.0, 0.75, smoothstep(0.75, 1.0, t))
|
|
let catchl = smoothstep(0.62, 0.9, t) * (1.0 - smoothstep(0.9, 1.0, t)) * smoothstep(0.2, 0.9, nrm.y)
|
|
off = off + vec3(0.30, 0.30, 0.32) * catchl * (1.0 - 0.5 * pale)
|
|
var col = mix(off, min(on, white), smoothstep(0.0, 0.35, L))
|
|
// One crisp highlight: a white oval across its upper third.
|
|
let e = length(vec2(q.x / 0.62, (q.y + 0.46) / 0.30))
|
|
let spec = (1.0 - smoothstep(0.55, 1.0, e)) * mix(1.0, 0.3, clamp((q.y + 0.76) / 0.6, 0.0, 1.0))
|
|
col = mix(col, white, spec * mix(0.8, 0.55, L) * smoothstep(0.9, 2.2, rt))
|
|
// Out, a faint seam where the dome meets its bezel; lit, none: the rim
|
|
// is the colour.
|
|
col = mix(col, col * 0.7, Finish.band(d, 0.0, px * 0.6, px) * 0.6 * (1.0 - smoothstep(0.0, 0.25, L)))
|
|
col = min(col, white)
|
|
let a_l = Finish.cover(d, px)
|
|
return Finish.over(res, vec4(col * a_l, a_l))
|
|
}
|
|
|
|
// The Lamp widget and the bar lamp: the lamp above, here in a ring of black lacquer.
|
|
// The halo's room is held in the lamp's own box (the library reserves a
|
|
// lens thickness either side once a halo is asked for), so the light is
|
|
// never cut at its edge.
|
|
mod.widgets.Lamp.halo = 0.3
|
|
mod.widgets.Lamp.halo_reach = 1.0
|
|
mod.widgets.Lamp.draw_bg.vertex = fn() {
|
|
let m = 2.0
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
|
|
}
|
|
mod.widgets.Lamp.draw_bg.pixel = fn() {
|
|
let m = 2.0
|
|
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let reach = clamp(self.reach, 0.0, 1.0)
|
|
let t = self.rect_size.y / (1.0 + 2.0 * reach)
|
|
let room = t * reach
|
|
let c = self.rect_size * 0.5
|
|
let half_len = max((self.rect_size.x - 2.0 * room) * 0.5, t * 0.5)
|
|
// The lens is the lamp's size; its socket stands round it in the room.
|
|
let hb = vec2(mix(t * 0.5, half_len, self.bar), t * 0.5)
|
|
let res = lq_led_lamp(vec4(0.0, 0.0, 0.0, 0.0), p, c, hb, self.lit, self.intent_color().rgb, 1.0 - step(3.5, self.intent), px, vec3(0.416, 0.047, 0.063), max(room - 0.5, 2.0), vec3(0.07, 0.045, 0.045), 1.0)
|
|
return res * self.opacity
|
|
}
|
|
true
|