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>
2808 lines
146 KiB
Text
2808 lines
146 KiB
Text
use mod.prelude.widgets_internal.*
|
|
use mod.sdf.Cap
|
|
|
|
// Futuristic brushed metal with lit circuit traces cut into it. Raised parts
|
|
// are gunmetal caps with cut corners, brushed along their length, lit on a
|
|
// narrow shoulder from above and edged in black, casting a short shadow on
|
|
// the steel. Wells are cut into the plate with a steel lip under them.
|
|
// Knobs are spun steel in a dark bezel whose groove carries the value as a
|
|
// glowing cyan ring; thumbs are chrome balls. The one cyan is light: LED
|
|
// dashes under the legends, lit traces, rings and values. Every rim is one
|
|
// device pixel, snapped.
|
|
mod.widgets.Button.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 2.0 bottom: 6.0}
|
|
mod.widgets.Button.draw_bg.border_radius = 2.0
|
|
mod.widgets.Button.draw_bg.border_size = 0.5
|
|
// A ROW OF FIELDS IS ONE HEIGHT: the box metrics are stated once here and
|
|
// handed to each field below. Buttons take the same 26.
|
|
let field_height = 26.0
|
|
let field_pad_v = mod.theme.mspace_1.top
|
|
let field_radius = 2.0
|
|
let field_border = 0.5
|
|
|
|
// The room a Fit field takes: where a field is Fit, the padding IS its
|
|
// height.
|
|
let field_room = fn(w) {
|
|
w.min_height = field_height
|
|
w.padding.top = field_pad_v
|
|
w.padding.bottom = field_pad_v
|
|
}
|
|
// The box a field draws. Given the draw object and not the widget, because
|
|
// not every field draws its own box.
|
|
let field_face = fn(d) {
|
|
d.border_radius = field_radius
|
|
d.border_size = field_border
|
|
}
|
|
// A field that states a frame and lays its own parts out inside it takes
|
|
// the height alone: a padding would displace its line.
|
|
let field_frame = fn(w) {
|
|
w.min_height = field_height
|
|
}
|
|
|
|
field_room(mod.widgets.TextInputFlat)
|
|
field_face(mod.widgets.TextInputFlat.draw_bg)
|
|
field_room(mod.widgets.TextInput)
|
|
field_face(mod.widgets.TextInput.draw_bg)
|
|
field_room(mod.widgets.ComboBox)
|
|
field_face(mod.widgets.ComboBox.draw_bg)
|
|
field_room(mod.widgets.DropDown)
|
|
field_face(mod.widgets.DropDown.draw_bg)
|
|
field_room(mod.widgets.DropDown2)
|
|
field_face(mod.widgets.DropDown2.draw_bg)
|
|
field_room(mod.widgets.FieldWell)
|
|
field_face(mod.widgets.FieldWell.draw_bg)
|
|
field_room(mod.widgets.TagField)
|
|
field_face(mod.widgets.TagField.draw_bg)
|
|
field_frame(mod.widgets.NumberField)
|
|
mod.widgets.NumberField.draw_bg.border_radius = field_radius
|
|
field_frame(mod.widgets.ValueInput)
|
|
field_frame(mod.widgets.DateField)
|
|
field_frame(mod.widgets.TimeField)
|
|
field_frame(mod.widgets.DatePicker)
|
|
field_frame(mod.widgets.DateRangePicker)
|
|
// The chrome-less input a well holds must impose no height of its own.
|
|
mod.widgets.WellInput.min_height = 0.0
|
|
mod.widgets.Window.pass.clear_color = #38424b
|
|
|
|
// Plain views stop clipping, so a part's shadow and light may fall into the
|
|
// gap beside it. Scroll views keep their own clip.
|
|
mod.widgets.View.clip_x = false
|
|
mod.widgets.View.clip_y = false
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// SHARED PARTS. Distances in points, negative inside, y down; `px` is one
|
|
// device pixel in points.
|
|
|
|
let g3 = fn(v: float) -> vec3 {
|
|
return vec3(v, v, v)
|
|
}
|
|
|
|
// A rounded box, true radius.
|
|
let fm_rbox = fn(p: vec2, c: vec2, h: vec2, r: float) -> float {
|
|
let rr = clamp(r, 0.0, min(h.x, h.y))
|
|
let q = abs(p - c) - h + vec2(rr, rr)
|
|
return min(max(q.x, q.y), 0.0) + length(max(q, vec2(0.0, 0.0))) - rr
|
|
}
|
|
|
|
// A box with its four corners cut at 45 degrees, `k` along each edge.
|
|
let fm_cbox = fn(p: vec2, c: vec2, h: vec2, k: float) -> float {
|
|
return Finish.sd_chamfer(p, c, h, clamp(k, 0.0, min(h.x, h.y)))
|
|
}
|
|
|
|
// The outward normal of the cut box, by central differences.
|
|
let fm_norm = fn(p: vec2, c: vec2, h: vec2, k: float) -> vec2 {
|
|
let e = 0.35
|
|
let gx = fm_cbox(p + vec2(e, 0.0), c, h, k) - fm_cbox(p - vec2(e, 0.0), c, h, k)
|
|
let gy = fm_cbox(p + vec2(0.0, e), c, h, k) - fm_cbox(p - vec2(0.0, e), c, h, k)
|
|
return vec2(gx, gy) / max(length(vec2(gx, gy)), 0.0001)
|
|
}
|
|
|
|
// A segment's distance.
|
|
let fm_seg = fn(p: vec2, a: vec2, b: vec2) -> float {
|
|
let pa = p - a
|
|
let ba = b - a
|
|
let k = clamp(dot(pa, ba) / max(dot(ba, ba), 0.0001), 0.0, 1.0)
|
|
return length(pa - ba * k)
|
|
}
|
|
|
|
// A stroked chevron's distance: `w` wide, `hgt` tall, pointing down
|
|
// (`flip` 1 points up).
|
|
let fm_chevron = fn(p: vec2, c: vec2, w: float, hgt: float, flip: float) -> float {
|
|
let s = mix(1.0, -1.0, flip)
|
|
let a = c + vec2(-w * 0.5, -hgt * 0.5 * s)
|
|
let b = c + vec2(0.0, hgt * 0.5 * s)
|
|
let e = c + vec2(w * 0.5, -hgt * 0.5 * s)
|
|
return min(fm_seg(p, a, b), fm_seg(p, b, e))
|
|
}
|
|
|
|
// A disc laid on the pixel grid: a whole number of device pixels across.
|
|
// `c` is quad-local; returns (centre.x, centre.y, radius).
|
|
let fm_fit = fn(c: vec2, o: vec2, rad: float, px: float) -> vec3 {
|
|
let dn = max(floor(2.0 * rad / px + 0.5), 2.0)
|
|
let r2 = dn * px * 0.5
|
|
let ex = Finish.snap(o.x + c.x - r2, px) - o.x
|
|
let ey = Finish.snap(o.y + c.y - r2, px) - o.y
|
|
return vec3(ex + r2, ey + r2, r2)
|
|
}
|
|
|
|
// The one accent: the white-hot core, the lit body, the dim glass.
|
|
let fm_core = fn() -> vec3 {
|
|
return vec3(0.447, 0.973, 1.0)
|
|
}
|
|
let fm_cyan = fn() -> vec3 {
|
|
return vec3(0.184, 0.776, 0.878)
|
|
}
|
|
let fm_dim = fn() -> vec3 {
|
|
return vec3(0.063, 0.180, 0.212)
|
|
}
|
|
let fm_red = fn() -> vec3 {
|
|
return vec3(1.0, 0.353, 0.392)
|
|
}
|
|
// The accent as disabled parts show it: unlit steel.
|
|
let fm_off = fn() -> vec3 {
|
|
return vec3(0.216, 0.243, 0.263)
|
|
}
|
|
// The black edge every part stands in.
|
|
let fm_edge = fn() -> vec3 {
|
|
return vec3(0.039, 0.055, 0.071)
|
|
}
|
|
let fm_ink = fn() -> vec3 {
|
|
return vec3(0.016, 0.024, 0.035)
|
|
}
|
|
// The light comes from above and a little left.
|
|
let fm_light = fn() -> vec2 {
|
|
return vec2(-0.26, -0.966)
|
|
}
|
|
|
|
// Signed grain, one value per device pixel (pass `p / px`); two hashes on
|
|
// displaced lattices, so no shift lines them up again.
|
|
let fm_grain = fn(q: vec2, amount: float) -> float {
|
|
let i = floor(q)
|
|
let h = Finish.hash(i)
|
|
let a = Finish.hash(i + vec2(h * 397.13, h * 211.71))
|
|
let b = Finish.hash(vec2(i.y * 0.7548 + h * 157.3, i.x * 1.1698 + h * 89.9))
|
|
return (a + b - 1.0) * amount
|
|
}
|
|
|
|
// Brushed streaks along x, window space: fine streaks over long slow ones,
|
|
// -1..1 roughly.
|
|
let fm_brush = fn(q: vec2, px: float) -> float {
|
|
let fine = Finish.brushed(q, vec2(1.0, 0.0), max(px, 0.7)) - 0.5
|
|
let rowh = Finish.hash(vec2(floor(q.y / px) * 1.371 + 3.1, 17.9))
|
|
let long = Finish.noise(vec2(q.x / 140.0 + rowh * 53.0, q.y / (px * 1.5))) - 0.5
|
|
return fine * 0.8 + long * 0.9
|
|
}
|
|
|
|
// Gunmetal by height t 0..1: #4f5f69 at the top, #3d4a54 at the waist,
|
|
// #2b353d at the foot.
|
|
let fm_gun = fn(t: float) -> vec3 {
|
|
let top = vec3(0.430, 0.488, 0.528)
|
|
let mid = vec3(0.322, 0.370, 0.406)
|
|
let foot = vec3(0.245, 0.285, 0.318)
|
|
return mix(mix(top, mid, clamp(t * 2.0, 0.0, 1.0)), foot, clamp(t * 2.0 - 1.0, 0.0, 1.0))
|
|
}
|
|
|
|
// Light steel for plates and pads: #56646e to #38444d.
|
|
let fm_steel = fn(t: float) -> vec3 {
|
|
return mix(vec3(0.420, 0.478, 0.518), vec3(0.290, 0.338, 0.375), t)
|
|
}
|
|
|
|
// A steel cap: the cut box lo..hi, gunmetal (tone 0) or light steel (1),
|
|
// brushed, lit on a narrow shoulder from above, a black edge. `press`
|
|
// sinks it: the fall turns over, the top wall shades the face and the
|
|
// shoulder takes its light from below.
|
|
let fm_cap = fn(p: vec2, o: vec2, lo: vec2, hi: vec2, k: float, px: float, hov: float, press: float, dis: float, tone: float) -> vec4 {
|
|
let c = (lo + hi) * 0.5
|
|
let h = (hi - lo) * 0.5
|
|
let d = fm_cbox(p, c, h, k)
|
|
let n = fm_norm(p, c, h, k)
|
|
let t = clamp((p.y - lo.y) / max(hi.y - lo.y, 1.0), 0.0, 1.0)
|
|
var face = mix(fm_gun(t), fm_steel(t), tone)
|
|
// a soft sheen high on the face
|
|
let st = (t - 0.08) / 0.09
|
|
face = face + g3(0.08 * exp(-st * st) * (1.0 - press))
|
|
face = face + g3(0.045 * hov)
|
|
// pressed: a darker face, lit a little at the foot
|
|
let sunk = mix(vec3(0.118, 0.145, 0.169), vec3(0.176, 0.212, 0.239), t)
|
|
face = mix(face, sunk, press)
|
|
let q = p + o
|
|
face = face + g3(fm_brush(q, px) * 0.045 + fm_grain(q / px, 0.010))
|
|
// the top wall's shade inside a pressed cap
|
|
let ish = Finish.inset(p, c, h, k * 0.6, vec2(0.0, 1.6), 1.4) * press
|
|
face = mix(face, fm_edge(), clamp(ish, 0.0, 1.0) * 0.8)
|
|
// disabled: a flat unlit plate
|
|
face = mix(face, vec3(0.200, 0.227, 0.251) + g3(fm_brush(q, px) * 0.012), dis * 0.9)
|
|
// the shoulder
|
|
let sh = Finish.bevel(d + px, n, 2.2, fm_light())
|
|
let lit = mix(sh * (1.0 + 0.3 * hov), -sh * 0.55, press) * mix(1.0, 0.3, dis)
|
|
face = Finish.lift(face, clamp(lit, -1.0, 1.0) * mix(0.85, 0.55, step(lit, 0.0)))
|
|
// the black edge
|
|
face = mix(face, fm_edge(), Finish.band(d, 0.0, px, px) * 0.95)
|
|
let a = Finish.cover(d, px)
|
|
return vec4(face * a, a)
|
|
}
|
|
|
|
// An LED dash: `lit` 0 is the dark lens in its slot, 1 the lit body with a
|
|
// white-hot core; returns the dash premultiplied.
|
|
let fm_dash = fn(p: vec2, c: vec2, hw: float, hh: float, px: float, ink: vec3, lit: float) -> vec4 {
|
|
let d = fm_rbox(p, c, vec2(hw, hh), hh)
|
|
let a = Finish.cover(d, px)
|
|
let across = abs(p.y - c.y) / max(hh, 0.001)
|
|
let along = abs(p.x - c.x) / max(hw, 0.001)
|
|
let hot = (1.0 - smoothstep(0.0, 1.1, across)) * (1.0 - smoothstep(0.5, 1.0, along))
|
|
let body = mix(mix(ink, vec3(1.0, 1.0, 1.0), 0.25), mix(ink, vec3(1.0, 1.0, 1.0), 0.75), hot * clamp(lit, 0.0, 1.0))
|
|
let off = mix(vec3(0.035, 0.051, 0.063), ink * 0.22, 0.6)
|
|
let col = mix(off, body, clamp(lit, 0.0, 1.0))
|
|
return vec4(col * a, a)
|
|
}
|
|
|
|
// A well cut into the plate: the floor, the plate's shadow falling in from
|
|
// the top wall, a dark mouth darkest on top.
|
|
let fm_well = fn(p: vec2, c: vec2, h: vec2, k: float, px: float, fill: vec3, depth: float) -> vec3 {
|
|
let d = fm_cbox(p, c, h, k)
|
|
let sh = Finish.inset(p, c, h, max(k * 0.6, 0.5), vec2(0.0, depth * 0.5), max(depth * 0.4, px * 0.6))
|
|
var col = mix(fill, fm_edge(), clamp(sh, 0.0, 1.0) * 0.85)
|
|
let up = 1.0 - smoothstep(-0.5, 0.5, p.y - c.y)
|
|
col = mix(col, fm_edge(), Finish.band(d, 0.0, px, px) * mix(0.55, 0.95, up))
|
|
return col
|
|
}
|
|
|
|
// The steel lip under a well's lower rim, outside it: premultiplied white.
|
|
let fm_lip = fn(p: vec2, c: vec2, h: vec2, k: float, px: float, a: float) -> vec4 {
|
|
let d = fm_cbox(p, c, h, k)
|
|
let foot = smoothstep(c.y + h.y - max(k, 1.0) - 1.5, c.y + h.y, p.y)
|
|
let l = Finish.ring_out(d, 0.0, px, px) * foot * a
|
|
return vec4(l, l, l, l)
|
|
}
|
|
|
|
// Focus: one device pixel of cyan two device pixels out, with a soft
|
|
// breath of light round it.
|
|
let fm_focus = fn(d: float, px: float, focus: float) -> vec4 {
|
|
let f = Finish.ring_out(d, 2.0 * px, 3.0 * px, px) * 0.85 * focus
|
|
let g = Finish.glow(max(d - 2.5 * px, 0.0), 1.0) * step(0.0, d) * 0.22 * focus
|
|
let k = fm_cyan()
|
|
let fa = clamp(f + g * (1.0 - f), 0.0, 1.0)
|
|
let col = mix(k, fm_core(), f)
|
|
return vec4(col * fa, fa)
|
|
}
|
|
|
|
// A chrome ball standing on the plate: its cast shadow, a sky above the
|
|
// horizon line, the steel below with a cyan bounce, a hot spot at the
|
|
// upper left, a dark rim.
|
|
let fm_ball = fn(p: vec2, c: vec2, rad: float, px: float, hover: float, press: float, dis: float) -> vec4 {
|
|
var out = vec4(0.0, 0.0, 0.0, 0.0)
|
|
let ink = fm_ink()
|
|
let cast = Finish.drop(p, c, vec2(rad, rad), rad, vec2(rad * 0.12, rad * 0.30), rad * 0.28) * 0.6 * mix(1.0, 0.4, dis)
|
|
out = Finish.over(out, vec4(ink * cast, cast))
|
|
let q = (p - c) / max(rad, 0.001)
|
|
let r2 = dot(q, q)
|
|
let z = sqrt(max(1.0 - r2, 0.0))
|
|
let ry = 2.0 * z * q.y
|
|
let sky = mix(vec3(0.94, 0.97, 0.99), vec3(0.60, 0.68, 0.73), smoothstep(-0.95, -0.05, ry))
|
|
let gnd = mix(vec3(0.24, 0.28, 0.32), vec3(0.52, 0.60, 0.65), smoothstep(0.1, 0.9, ry))
|
|
var col = mix(sky, gnd, smoothstep(-0.30, 0.20, ry))
|
|
col = col + fm_cyan() * 0.22 * smoothstep(0.35, 1.0, q.y) * z * (1.0 - dis)
|
|
col = col * mix(0.5, 1.0, smoothstep(0.0, 0.5, z))
|
|
let sq = q - vec2(-0.30, -0.42)
|
|
col = col + g3(0.55 * exp(-dot(sq, sq) / 0.025))
|
|
col = col + g3(0.12 * hover - 0.08 * press)
|
|
col = mix(col, g3(0.30) + 0.12 * col, dis * 0.8)
|
|
let d = length(p - c) - rad
|
|
col = mix(col, fm_edge(), Finish.band(d, 0.0, px, px) * 0.75)
|
|
let a = Finish.cover(d, px)
|
|
return Finish.over(out, vec4(col * a, a))
|
|
}
|
|
|
|
let grow_vertex = fn(rp: vec2, rs: vec2, m: float) -> vec4 {
|
|
return vec4(rp.x - m, rp.y - m, rs.x + 2.0 * m, rs.y + 2.0 * m)
|
|
}
|
|
|
|
// A line of light at `at` across v, `wd` wide.
|
|
let fm_hl = fn(v: float, at: float, wd: float, px: float) -> float {
|
|
return clamp((wd * 0.5 - abs(v - at)) / px + 0.5, 0.0, 1.0)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// THE CIRCUIT. A dark teal-black board with traces on it, built from cell
|
|
// grids at three scales. Every edge between two cells hashes whether a trace
|
|
// crosses it and where along it, so both cells agree and a trace runs on
|
|
// unbroken; inside a cell the traces meet at a hub, arriving straight and
|
|
// turning at 45 degrees. A slow density field makes busy clusters and quiet
|
|
// ground, a slow direction field lays them in bundles running mostly one way.
|
|
// A trace that ends stops in a ring via; an empty cell may hold a chip or a
|
|
// small part. Some traces are lit where a slow field says so, brightening and
|
|
// fading along their length.
|
|
|
|
// The port on one cell edge: `e` the edge's middle in cell units, `horiz` 1
|
|
// for an edge a horizontal trace crosses. Returns (1 if there is one, its
|
|
// place along the edge in points).
|
|
let fm_port = fn(e: vec2, horiz: float, s: float, seed: float, dens: float) -> vec2 {
|
|
let dn = Finish.noise(e / 4.3 + vec2(seed * 7.13, seed * 3.31))
|
|
let dir = Finish.noise(e / 7.1 + vec2(seed * 2.91 + 40.0, seed * 5.77 + 11.0))
|
|
let busy = smoothstep(0.40 - dens, 0.56 - dens, dn)
|
|
let pref = smoothstep(0.35, 0.65, mix(1.0 - dir, dir, horiz))
|
|
let pr = busy * mix(0.05, 0.96, pref)
|
|
// on or off by runs of about three edges along the trace, the runs of
|
|
// neighbouring rows staggered, with a rare single break
|
|
let along = mix(e.y, e.x, horiz)
|
|
let across = mix(e.x, e.y, horiz)
|
|
let rk = floor((along + Finish.hash(vec2(across * 1.37 + seed, horiz * 5.1 + 2.0)) * 3.0) / 3.0)
|
|
let h1 = Finish.hash(vec2(rk * 1.731 + seed * 13.1 + horiz * 71.3, across * 2.117 + seed * 5.9 + 0.7)) * step(0.05, Finish.hash(e * vec2(2.71, 1.61) + vec2(seed * 2.3, horiz * 9.7)))
|
|
let h2 = Finish.hash(e * vec2(3.173, 1.291) + vec2(seed * 3.7 + horiz * 19.1, 8.3))
|
|
let t = 0.5 + (h2 - 0.5) * 0.46
|
|
return vec2(step(1.0 - pr, h1), t * s)
|
|
}
|
|
|
|
// One cell of a layer: `l` the point in the cell (0..s). Returns (distance to
|
|
// the trace centre lines, distance to the centre of a via, signed distance
|
|
// to a part, kind of part: 0 none, 1 chip with pins, 2 small part; plus
|
|
// 0.5 where a chip's pin is).
|
|
let fm_cell = fn(l: vec2, cell: vec2, s: float, seed: float, dens: float) -> vec4 {
|
|
let pl = fm_port(vec2(cell.x, cell.y + 0.5), 1.0, s, seed, dens)
|
|
let pr = fm_port(vec2(cell.x + 1.0, cell.y + 0.5), 1.0, s, seed, dens)
|
|
let pt = fm_port(vec2(cell.x + 0.5, cell.y), 0.0, s, seed, dens)
|
|
let pb = fm_port(vec2(cell.x + 0.5, cell.y + 1.0), 0.0, s, seed, dens)
|
|
let n = pl.x + pr.x + pt.x + pb.x
|
|
let hc = Finish.hash(cell * vec2(1.93, 2.71) + vec2(seed * 9.1, 4.4))
|
|
let hd = Finish.hash(cell * vec2(2.37, 1.13) + vec2(seed * 3.3, 7.9))
|
|
let nv = pt.x + pb.x
|
|
let nh = pl.x + pr.x
|
|
// the hub: on the line of the ports, moved in toward the odd one so a
|
|
// corner is cut at 45 degrees
|
|
let k = s * (0.07 + 0.07 * hc)
|
|
let hx = mix(s * (0.3 + 0.4 * hc), (pt.x * pt.y + pb.x * pb.y) / max(nv, 1.0), step(0.5, nv)) + k * (pr.x - pl.x)
|
|
let hy = mix(s * (0.3 + 0.4 * hd), (pl.x * pl.y + pr.x * pr.y) / max(nh, 1.0), step(0.5, nh)) + k * (pb.x - pt.x)
|
|
let hub = vec2(hx, hy)
|
|
let big = 1000.0
|
|
let al = vec2(clamp(hx - abs(hy - pl.y), 0.0, s), pl.y)
|
|
let ar = vec2(clamp(hx + abs(hy - pr.y), 0.0, s), pr.y)
|
|
let at = vec2(pt.y, clamp(hy - abs(hx - pt.y), 0.0, s))
|
|
let ab = vec2(pb.y, clamp(hy + abs(hx - pb.y), 0.0, s))
|
|
var d = big
|
|
d = min(d, mix(big, min(fm_seg(l, vec2(0.0, pl.y), al), fm_seg(l, al, hub)), pl.x))
|
|
d = min(d, mix(big, min(fm_seg(l, vec2(s, pr.y), ar), fm_seg(l, ar, hub)), pr.x))
|
|
d = min(d, mix(big, min(fm_seg(l, vec2(pt.y, 0.0), at), fm_seg(l, at, hub)), pt.x))
|
|
d = min(d, mix(big, min(fm_seg(l, vec2(pb.y, s), ab), fm_seg(l, ab, hub)), pb.x))
|
|
// a via where a trace ends, now and then where traces meet
|
|
let viaon = max(step(0.5, n) * step(n, 1.5) * step(0.35, hd), step(2.5, n) * step(0.55, hd))
|
|
let dp = mix(big, length(l - hub), viaon)
|
|
// an empty cell may hold a chip (pins along two sides) or a small part
|
|
var dc = big
|
|
var kind = 0.0
|
|
if n < 0.5 {
|
|
let hk = Finish.hash(cell * vec2(3.71, 2.93) + vec2(seed * 1.7, 2.2))
|
|
let hr = step(0.5, Finish.hash(cell * vec2(1.17, 3.39) + vec2(seed * 4.1, 6.6)))
|
|
let cc = vec2(s * (0.42 + 0.16 * hc), s * (0.42 + 0.16 * hd))
|
|
let rel = l - cc
|
|
let rl = mix(rel, vec2(rel.y, rel.x), hr)
|
|
if hk > 0.45 {
|
|
let hh = vec2(s * 0.20, s * 0.11)
|
|
dc = fm_rbox(rl, vec2(0.0, 0.0), hh, s * 0.02)
|
|
kind = 1.0
|
|
let pitch = s * 0.065
|
|
let pin = step(hh.y, abs(rl.y)) * step(abs(rl.y), hh.y + s * 0.045) * step(abs(rl.x), hh.x - s * 0.03) * step(fract(rl.x / pitch + 0.25), 0.5)
|
|
kind = kind + 0.5 * pin
|
|
}
|
|
if hk > 0.15 && hk < 0.45 {
|
|
dc = fm_rbox(rl, vec2(0.0, 0.0), vec2(s * 0.08, s * 0.04), s * 0.01)
|
|
kind = 2.0 + 0.5 * step(s * 0.05, abs(rl.x))
|
|
}
|
|
}
|
|
return vec4(d, dp, dc, kind)
|
|
}
|
|
|
|
// A layer at cell size `s`: the cell under the point in full, and the
|
|
// traces and vias of the three neighbours toward it, so a glow runs on
|
|
// across a cell's edge.
|
|
let fm_layer = fn(q: vec2, s: float, seed: float, dens: float) -> vec4 {
|
|
let cell = floor(q / s)
|
|
let f = q / s - cell
|
|
let ox = mix(-1.0, 1.0, step(0.5, f.x))
|
|
let oy = mix(-1.0, 1.0, step(0.5, f.y))
|
|
let r = fm_cell(q - cell * s, cell, s, seed, dens)
|
|
let c1 = cell + vec2(ox, 0.0)
|
|
let c2 = cell + vec2(0.0, oy)
|
|
let c3 = cell + vec2(ox, oy)
|
|
let a = fm_cell(q - c1 * s, c1, s, seed, dens)
|
|
let b = fm_cell(q - c2 * s, c2, s, seed, dens)
|
|
let c = fm_cell(q - c3 * s, c3, s, seed, dens)
|
|
return vec4(min(r.x, min(a.x, min(b.x, c.x))), min(r.y, min(a.y, min(b.y, c.y))), r.z, r.w)
|
|
}
|
|
|
|
// How lit the traces of a layer are at q: a slow field, so a lit trace
|
|
// brightens and fades along its length; most of the board stays dim.
|
|
let fm_lit = fn(q: vec2, sc: float, seed: float, lo: float) -> float {
|
|
let v = Finish.noise(q / sc + vec2(seed * 17.3, seed * 9.1)) * 0.7 + Finish.noise(q / (sc * 0.37) + vec2(seed * 3.9, 21.0)) * 0.3
|
|
return smoothstep(lo, lo + 0.1, v)
|
|
}
|
|
|
|
// The board at q (points, anchored where the caller anchors it). `soft`
|
|
// widens every edge (the frost of the pane in front); `calm` 0..1 dims the
|
|
// lit traces (under text).
|
|
let fm_board = fn(q: vec2, px: float, soft: float, calm: float, frost: float) -> vec3 {
|
|
let aa = px * soft
|
|
// behind frost a lit trace spreads: a softer, wider glow, a paler core
|
|
let hot = 1.0 - 0.8 * frost
|
|
let bodyk = 1.0 - 0.45 * frost
|
|
let spread = 1.0 + 0.6 * frost
|
|
let gk = 1.0 - 0.3 * frost
|
|
let cl = Finish.noise(q / 110.0) * 0.6 + Finish.noise(q / 31.0 + vec2(7.0, 3.0)) * 0.4
|
|
var col = mix(vec3(0.031, 0.071, 0.094), vec3(0.071, 0.118, 0.141), cl)
|
|
col = col + g3(fm_grain(q / px, 0.012))
|
|
let quiet = 1.0 - calm
|
|
// the fine signal lines: many, thin, never lit
|
|
let qa = q + vec2(3.1, 5.7)
|
|
let la = fm_cell(qa - floor(qa / 5.5) * 5.5, floor(qa / 5.5), 5.5, 1.0, -0.04)
|
|
col = mix(col, vec3(0.106, 0.200, 0.235), Finish.cover(la.x - 0.33, aa) * 0.8)
|
|
// the wide flat buses: a pale fill in a brighter outline
|
|
let qc = q + vec2(17.3, 11.9)
|
|
let lc = fm_layer(qc, 46.0, 3.0, 0.16)
|
|
let litc = fm_lit(q, 240.0, 3.0, 0.52) * quiet * (1.0 - 0.3 * frost)
|
|
let wc = 7.0
|
|
let cfill = mix(vec3(0.120, 0.225, 0.262), vec3(0.110, 0.300, 0.350), litc * bodyk)
|
|
col = mix(col, cfill, Finish.cover(lc.x - wc * 0.5, aa) * 0.9)
|
|
col = col + fm_cyan() * Finish.glow(max(lc.x - wc * 0.5, 0.0), 4.0 * spread) * step(wc * 0.5, lc.x) * 0.40 * litc * gk
|
|
let crim = Finish.cover(abs(lc.x - wc * 0.5 + 0.5) - 0.5, aa)
|
|
col = mix(col, mix(vec3(0.380, 0.540, 0.590), mix(fm_cyan(), fm_core(), 0.55 * hot), litc * bodyk), crim * 0.95)
|
|
// a part in an empty bus cell: a chip with its pins, a small part
|
|
let kind = floor(lc.w)
|
|
let pin = step(0.25, fract(lc.w))
|
|
let body = Finish.cover(lc.z, aa)
|
|
col = mix(col, vec3(0.200, 0.260, 0.285), pin * step(0.5, kind) * step(kind, 1.5))
|
|
let chip = mix(vec3(0.028, 0.040, 0.048), vec3(0.070, 0.090, 0.100), smoothstep(-6.0, 0.0, lc.z))
|
|
col = mix(col, mix(chip, vec3(0.200, 0.290, 0.320), Finish.cover(abs(lc.z + 0.6) - 0.4, aa) * 0.7), body * step(0.5, kind) * step(kind, 1.5))
|
|
let smd = mix(vec3(0.060, 0.080, 0.090), vec3(0.330, 0.390, 0.410), step(0.25, fract(lc.w)))
|
|
col = mix(col, smd, body * step(1.5, kind))
|
|
// the signal bus: mid traces, some lit
|
|
let qb = q + vec2(7.7, 2.3)
|
|
let lb = fm_layer(qb, 19.0, 5.0, 0.0)
|
|
let litb = fm_lit(q, 150.0, 5.0, 0.54) * quiet * (1.0 - 0.2 * frost)
|
|
let wb = 2.4
|
|
col = col + fm_cyan() * Finish.glow(max(lb.x - wb * 0.5, 0.0), 3.2 * spread) * step(wb * 0.5, lb.x) * 0.55 * litb * gk
|
|
let tb = Finish.tube(lb.x, wb, aa)
|
|
col = mix(col, mix(vec3(0.190, 0.320, 0.370), fm_cyan(), litb * bodyk), Finish.cover(lb.x - wb * 0.5, aa))
|
|
col = mix(col, mix(fm_core(), vec3(1.0, 1.0, 1.0), 0.45), tb.x * litb * hot)
|
|
// ring vias of both layers
|
|
let rv = abs(lb.y - 2.5) - 0.8
|
|
col = mix(col, mix(vec3(0.300, 0.430, 0.470), mix(fm_cyan(), fm_core(), 0.6 * hot), litb * bodyk), Finish.cover(rv, aa))
|
|
col = mix(col, vec3(0.012, 0.024, 0.031), Finish.cover(lb.y - 1.6, aa))
|
|
let rc = abs(lc.y - 5.6) - 1.6
|
|
col = mix(col, mix(vec3(0.270, 0.390, 0.430), mix(fm_cyan(), fm_core(), 0.5 * hot), litc * bodyk), Finish.cover(rc, aa))
|
|
col = mix(col, vec3(0.012, 0.024, 0.031), Finish.cover(lc.y - 3.8, aa))
|
|
return col
|
|
}
|
|
|
|
// A pane of frosted blue-white plastic lying on the board: it lightens and
|
|
// softens what is behind it, tints it pale blue, and carries a soft diagonal
|
|
// reflection (`refl` 0..1 at this point).
|
|
let fm_pane = fn(board: vec3, q: vec2, px: float, refl: float, tint: float) -> vec3 {
|
|
// the frost is a little uneven, as a moulded pane is
|
|
let fz = Finish.noise(q / 70.0 + vec2(3.3, 8.1)) - 0.5
|
|
var c = board * 0.82 + vec3(0.420, 0.540, 0.600) * (tint + 0.06 * fz)
|
|
c = c + vec3(0.85, 0.93, 1.0) * refl
|
|
c = c + g3(fm_grain(q / px + vec2(311.0, 97.0), 0.010))
|
|
return c
|
|
}
|
|
|
|
// Union of two distances with the inner corner cut at 45 degrees, `r` deep.
|
|
let fm_uc = fn(a: float, b: float, r: float) -> float {
|
|
return min(min(a, b), (a + b - r) * 0.7071)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// BUTTONS. One face for the family; `kind` picks the dressing:
|
|
// 0 flat plate (no shadow, no dash), 1 raised with a standby dash,
|
|
// 2 raised with the dash lit and the face lit from its foot (primary),
|
|
// 3 raised with the dash half lit (secondary), 4 raised with a red dash
|
|
// (danger), 5 a steel frame round the ground (outline), 6 bare until
|
|
// touched, 7 a light steel pad (tertiary), 8 raised with no dash (chips).
|
|
// The quad is grown by `m` on every side for the shadow and the focus ring.
|
|
let fm_button = fn(pos: vec2, size: vec2, o: vec2, px: float, hover: float, down: float, focus: float, disabled: float, loading: float, anim: float, k: float, kind: float, m: float) -> vec4 {
|
|
let p = pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
let live = 1.0 - disabled
|
|
let press = down * live
|
|
let hov = hover * live * (1.0 - press)
|
|
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o
|
|
let hi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y, px)) - o
|
|
let c = (lo + hi) * 0.5
|
|
let h = (hi - lo) * 0.5
|
|
let d = fm_cbox(p, c, h, k)
|
|
let bare = step(5.5, kind) * step(kind, 6.5)
|
|
let outl = step(4.5, kind) * step(kind, 5.5)
|
|
let pad = step(6.5, kind) * step(kind, 7.5)
|
|
let flat = 1.0 - step(0.5, kind)
|
|
let solid = 1.0 - bare - outl
|
|
let raised = solid * (1.0 - flat)
|
|
|
|
var out = vec4(0.0, 0.0, 0.0, 0.0)
|
|
let ink = fm_ink()
|
|
// the soft layer: mostly below, faded within half the gap to a neighbour
|
|
let soft = Finish.drop(p, c, h, k * 0.6, vec2(0.0, 2.0), 1.8) * 0.55 * raised * (1.0 - press) * live
|
|
out = Finish.over(out, vec4(ink * soft, soft))
|
|
let con = Finish.drop(p, c, h, k * 0.6, vec2(0.0, px), px * 1.2) * 0.7 * solid * mix(1.0, 0.3, press) * mix(1.0, 0.5, disabled)
|
|
out = Finish.over(out, vec4(ink * con, con))
|
|
|
|
// the cap
|
|
var tone = pad
|
|
var cap = fm_cap(p, o, lo, hi, k, px, hov, press, disabled, tone)
|
|
if flat > 0.5 {
|
|
// the flat plate: a darker slab with a gentler shoulder
|
|
let t = clamp((p.y - lo.y) / max(2.0 * h.y, 1.0), 0.0, 1.0)
|
|
var face = mix(vec3(0.176, 0.212, 0.239), vec3(0.137, 0.165, 0.188), t)
|
|
face = face + g3(0.035 * hov - 0.04 * press + fm_brush(p + o, px) * 0.02)
|
|
face = mix(face, vec3(0.184, 0.208, 0.227), disabled * 0.8)
|
|
let n = fm_norm(p, c, h, k)
|
|
let sh = Finish.bevel(d + px, n, 1.0, fm_light()) * (1.0 - press) * mix(1.0, 0.3, disabled)
|
|
face = Finish.lift(face, sh * 0.30)
|
|
face = mix(face, fm_edge(), Finish.band(d, 0.0, px, px) * 0.9)
|
|
let fa = Finish.cover(d, px)
|
|
cap = vec4(face * fa, fa)
|
|
}
|
|
// the outline and the bare face: only a frame, or nothing, until touched
|
|
var fa2 = 1.0
|
|
if kind > 4.5 && kind < 6.5 {
|
|
fa2 = mix(0.0, 0.5, hov) + 0.6 * press
|
|
}
|
|
out = Finish.over(out, cap * fa2)
|
|
if outl > 0.5 {
|
|
// dark glass inside the frame, so the legend reads on any ground
|
|
let ga = Finish.cover(d, px) * 0.8 * (1.0 - fa2 * 0.5)
|
|
let gt = clamp((p.y - lo.y) / max(2.0 * h.y, 1.0), 0.0, 1.0)
|
|
let gc = mix(vec3(0.090, 0.115, 0.135), vec3(0.060, 0.078, 0.094), gt) + g3(0.05 * hov)
|
|
out = Finish.over(out, vec4(gc * ga, ga))
|
|
// a steel frame two points wide, lit on its upper run
|
|
let n = fm_norm(p, c, h, k)
|
|
let fr = Finish.band(d, 0.0, 2.0, px)
|
|
let lt = clamp(dot(n, fm_light()), -1.0, 1.0)
|
|
var fc = mix(vec3(0.290, 0.337, 0.373), vec3(0.62, 0.69, 0.73), clamp(lt, 0.0, 1.0))
|
|
fc = mix(fc, vec3(0.16, 0.19, 0.21), clamp(-lt, 0.0, 1.0))
|
|
fc = mix(fc, vec3(0.25, 0.28, 0.30), disabled)
|
|
fc = mix(fc, fm_edge(), max(Finish.band(d, 0.0, px, px), Finish.band(d, 2.0 - px, 2.0, px)) * 0.9)
|
|
out = Finish.over(out, vec4(fc * fr, fr))
|
|
}
|
|
|
|
// the dash under the legend
|
|
let has_dash = step(0.5, kind) * step(kind, 4.5) + pad + outl
|
|
if has_dash > 0.5 {
|
|
let hw = clamp(h.x * 0.26, 5.0, 11.0)
|
|
let dy = Finish.snap(o.y + hi.y - 5.0, px) - o.y
|
|
let dc = vec2(Finish.snap(o.x + c.x, px) - o.x, dy)
|
|
let is_red = step(3.5, kind) * step(kind, 4.5)
|
|
var base = 0.72
|
|
if kind > 1.5 && kind < 2.5 {
|
|
base = 1.0
|
|
}
|
|
if kind > 2.5 && kind < 3.5 {
|
|
base = 0.85
|
|
}
|
|
if is_red > 0.5 {
|
|
base = 0.85
|
|
}
|
|
if outl > 0.5 {
|
|
base = 0.3
|
|
}
|
|
let lit = clamp(base + 0.3 * hov + press, 0.0, 1.0) * live
|
|
let dink = mix(fm_cyan(), fm_red(), is_red)
|
|
let a_cap = Finish.cover(d, px)
|
|
// the light it throws on the metal round it, inside the cap
|
|
let dd = fm_rbox(p, dc, vec2(hw, px * 2.0), px * 2.0)
|
|
let gl = Finish.glow(max(dd, 0.0) + 0.6, 3.2 + 1.2 * hov) * step(0.0, dd) * lit * (0.62 + 0.3 * hov) * a_cap
|
|
out = vec4(out.rgb + dink * gl, out.a)
|
|
// the primary's face takes the light rising from its foot
|
|
if kind > 1.5 && kind < 2.5 {
|
|
let t = clamp((p.y - lo.y) / max(2.0 * h.y, 1.0), 0.0, 1.0)
|
|
let rise = smoothstep(0.35, 1.0, t) * 0.16 * lit * a_cap * (1.0 - Finish.band(d, 0.0, px, px))
|
|
out = vec4(out.rgb + fm_cyan() * rise, out.a)
|
|
}
|
|
let dash = fm_dash(p, dc, hw, px * 2.0, px, dink, lit)
|
|
out = Finish.over(out, dash)
|
|
}
|
|
|
|
out = Finish.over(out, fm_focus(d, px, focus * live))
|
|
|
|
// the wait spinner, which the stock face drew and this one must too
|
|
if loading > 0.0 {
|
|
let sdf = Sdf2d.viewport(p)
|
|
let rr = max(min(h.x, h.y) - 5.0, 2.0)
|
|
let a0 = anim * 2.0 * PI
|
|
sdf.arc_round_caps(c.x, c.y, rr, a0, a0 + 1.5 * PI, 1.2)
|
|
let kk = fm_cyan()
|
|
sdf.fill(vec4(kk * loading, loading))
|
|
out = Finish.over(out, sdf.result)
|
|
}
|
|
return out
|
|
}
|
|
|
|
// The drop-down face: the cap with an engraved split 22 points from its
|
|
// right end and a stroked chevron behind it; `held` is pressed.
|
|
let fm_dropdown = fn(pos: vec2, size: vec2, o: vec2, px: float, hover: float, held: float, focus: float, disabled: float, m: float) -> vec4 {
|
|
var out = fm_button(pos, size, o, px, hover, held, focus, disabled, 0.0, 0.0, 3.0, 8.0, m)
|
|
let p = pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
let live = 1.0 - disabled
|
|
let dx = Finish.snap(o.x + size.x - 22.0, px) - o.x
|
|
let in_y = step(size.y * 0.22, p.y) * step(p.y, size.y * 0.78)
|
|
let div_d = step(dx - px, p.x) * step(p.x, dx) * in_y * 0.9
|
|
let div_l = step(dx, p.x) * step(p.x, dx + px) * in_y * 0.5 * (1.0 - held)
|
|
let bz = fm_edge()
|
|
out = Finish.over(out, vec4(bz * div_d, div_d))
|
|
let hl = vec3(0.45, 0.52, 0.57)
|
|
out = Finish.over(out, vec4(hl * div_l, div_l))
|
|
let cc = vec2(size.x - 11.0, Finish.snap(o.y + size.y * 0.5, px) - o.y + held * live * px)
|
|
let ch = Finish.cover(fm_chevron(p, cc, 6.0, 3.5, 0.0) - 0.7, px)
|
|
let ink = mix(mix(vec3(0.75, 0.80, 0.84), fm_core(), hover * live * 0.6), vec3(0.36, 0.40, 0.43), disabled)
|
|
return Finish.over(out, vec4(ink * ch, ch))
|
|
}
|
|
|
|
// Button faces. The flat root and the bare face keep to their own box, so
|
|
// every template derived from them with a face of its own is left as it is;
|
|
// the raised faces grow their quad for the shadow and the focus ring.
|
|
mod.widgets.ButtonFlat.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
return self.host_over(fm_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 3.0, 0.0, 0.0), 0.0)
|
|
}
|
|
mod.widgets.ButtonFlatter.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
return self.host_over(fm_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 3.0, 6.0, 0.0), 0.0)
|
|
}
|
|
mod.widgets.Button.draw_bg.vertex = fn() {
|
|
let g = grow_vertex(self.rect_pos, self.rect_size, 8.0)
|
|
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
|
|
}
|
|
mod.widgets.Button.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
return self.host_over(fm_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 3.0, 1.0, 8.0), 8.0)
|
|
}
|
|
mod.widgets.ButtonPrimary.draw_bg.vertex = fn() {
|
|
let g = grow_vertex(self.rect_pos, self.rect_size, 8.0)
|
|
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
|
|
}
|
|
mod.widgets.ButtonPrimary.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
return self.host_over(fm_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 3.0, 2.0, 8.0), 8.0)
|
|
}
|
|
mod.widgets.ButtonSecondary.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
return self.host_over(fm_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 3.0, 3.0, 8.0), 8.0)
|
|
}
|
|
mod.widgets.ButtonTertiary.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
return self.host_over(fm_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 3.0, 7.0, 8.0), 8.0)
|
|
}
|
|
mod.widgets.ButtonDanger.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
return self.host_over(fm_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 3.0, 4.0, 8.0), 8.0)
|
|
}
|
|
mod.widgets.ButtonOutline.draw_bg.vertex = fn() {
|
|
let g = grow_vertex(self.rect_pos, self.rect_size, 8.0)
|
|
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
|
|
}
|
|
mod.widgets.ButtonOutline.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
return self.host_over(fm_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 3.0, 5.0, 8.0), 8.0)
|
|
}
|
|
mod.widgets.ButtonXs.draw_bg.vertex = fn() {
|
|
let g = grow_vertex(self.rect_pos, self.rect_size, 8.0)
|
|
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
|
|
}
|
|
mod.widgets.ButtonXs.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
return self.host_over(fm_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 2.0, 8.0, 8.0), 8.0)
|
|
}
|
|
mod.widgets.ButtonSm.draw_bg.vertex = fn() {
|
|
let g = grow_vertex(self.rect_pos, self.rect_size, 8.0)
|
|
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
|
|
}
|
|
mod.widgets.ButtonSm.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
return self.host_over(fm_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 2.5, 8.0, 8.0), 8.0)
|
|
}
|
|
mod.widgets.ButtonLg.draw_bg.vertex = fn() {
|
|
let g = grow_vertex(self.rect_pos, self.rect_size, 8.0)
|
|
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
|
|
}
|
|
mod.widgets.ButtonLg.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
return self.host_over(fm_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 3.5, 1.0, 8.0), 8.0)
|
|
}
|
|
mod.widgets.ButtonXl.draw_bg.vertex = fn() {
|
|
let g = grow_vertex(self.rect_pos, self.rect_size, 8.0)
|
|
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
|
|
}
|
|
mod.widgets.ButtonXl.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
return self.host_over(fm_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 4.0, 1.0, 8.0), 8.0)
|
|
}
|
|
mod.widgets.ButtonCompound.draw_bg.vertex = fn() {
|
|
let g = grow_vertex(self.rect_pos, self.rect_size, 8.0)
|
|
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
|
|
}
|
|
mod.widgets.ButtonCompound.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
return self.host_over(fm_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 3.0, 8.0, 8.0), 8.0)
|
|
}
|
|
mod.widgets.Button.clip_x = false
|
|
mod.widgets.Button.clip_y = false
|
|
mod.widgets.ButtonPrimary.clip_x = false
|
|
mod.widgets.ButtonPrimary.clip_y = false
|
|
mod.widgets.ButtonOutline.clip_x = false
|
|
mod.widgets.ButtonOutline.clip_y = false
|
|
mod.widgets.ButtonXs.clip_x = false
|
|
mod.widgets.ButtonXs.clip_y = false
|
|
mod.widgets.ButtonSm.clip_x = false
|
|
mod.widgets.ButtonSm.clip_y = false
|
|
mod.widgets.ButtonLg.clip_x = false
|
|
mod.widgets.ButtonLg.clip_y = false
|
|
mod.widgets.ButtonXl.clip_x = false
|
|
mod.widgets.ButtonXl.clip_y = false
|
|
mod.widgets.ButtonCompound.clip_x = false
|
|
mod.widgets.ButtonCompound.clip_y = false
|
|
|
|
// The Select and MultiSelect face was built from Button before this sheet
|
|
// ran, so it takes the drop-down's face by its own path: split and chevron.
|
|
mod.widgets.Select.face.draw_bg.vertex = fn() {
|
|
let g = grow_vertex(self.rect_pos, self.rect_size, 8.0)
|
|
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
|
|
}
|
|
mod.widgets.Select.face.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
return fm_dropdown(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, 8.0)
|
|
}
|
|
mod.widgets.Select.face.clip_x = false
|
|
mod.widgets.Select.face.clip_y = false
|
|
mod.widgets.Select.face.padding = mod.turtle.Inset{left: 10.0 right: 30.0 top: 3.0 bottom: 3.0}
|
|
mod.widgets.Select.face.min_height = 26.0
|
|
mod.widgets.MultiSelect.face.draw_bg.vertex = fn() {
|
|
let g = grow_vertex(self.rect_pos, self.rect_size, 8.0)
|
|
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
|
|
}
|
|
mod.widgets.MultiSelect.face.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
return fm_dropdown(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, 8.0)
|
|
}
|
|
mod.widgets.MultiSelect.face.clip_x = false
|
|
mod.widgets.MultiSelect.face.clip_y = false
|
|
mod.widgets.MultiSelect.face.padding = mod.turtle.Inset{left: 10.0 right: 30.0 top: 3.0 bottom: 3.0}
|
|
mod.widgets.MultiSelect.face.min_height = 26.0
|
|
|
|
// Button geometry: one 26 point row; the legend sits two points high so
|
|
// the dash has its own room under it.
|
|
mod.widgets.ButtonFlat.padding = mod.turtle.Inset{left: 10.0 right: 10.0 top: 3.0 bottom: 3.0}
|
|
mod.widgets.Button.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 2.0 bottom: 6.0}
|
|
mod.widgets.ButtonPrimary.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 2.0 bottom: 6.0}
|
|
mod.widgets.ButtonTertiary.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 2.0 bottom: 6.0}
|
|
mod.widgets.ButtonOutline.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 2.0 bottom: 6.0}
|
|
mod.widgets.ButtonIcon.padding = mod.turtle.Inset{left: 7.0 right: 7.0 top: 2.0 bottom: 6.0}
|
|
mod.widgets.Button.icon_walk = mod.turtle.Walk{width: 13.0 height: Fit}
|
|
mod.widgets.ButtonFlat.min_height = 26.0
|
|
mod.widgets.Button.min_height = 26.0
|
|
mod.widgets.ButtonXs.min_height = 18.0
|
|
mod.widgets.ButtonXs.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 1.0 bottom: 1.0}
|
|
mod.widgets.ButtonSm.min_height = 22.0
|
|
mod.widgets.ButtonSm.padding = mod.turtle.Inset{left: 9.0 right: 9.0 top: 2.0 bottom: 2.0}
|
|
mod.widgets.ButtonLg.min_height = 30.0
|
|
mod.widgets.ButtonLg.padding = mod.turtle.Inset{left: 14.0 right: 14.0 top: 3.0 bottom: 7.0}
|
|
|
|
// Legends: semibold pale steel; the primary legend is lit cyan, the danger
|
|
// legend a warm red.
|
|
mod.widgets.ButtonFlat.draw_text.text_style = mod.theme.font_regular{font_size: 8.5}
|
|
mod.widgets.ButtonFlat.draw_bg.loading_color = #30c8e2
|
|
mod.widgets.ButtonFlat.draw_icon_end.color = #dce4ea
|
|
mod.widgets.ButtonPrimary.draw_text.color = #bff5fc
|
|
mod.widgets.ButtonPrimary.draw_text.color_hover = #e4fcff
|
|
mod.widgets.ButtonPrimary.draw_text.color_down = #9fe6f0
|
|
mod.widgets.ButtonPrimary.draw_text.color_focus = #bff5fc
|
|
mod.widgets.ButtonPrimary.draw_text.color_disabled = #6b777f
|
|
mod.widgets.ButtonPrimary.draw_icon.color = #bff5fc
|
|
mod.widgets.ButtonPrimary.draw_icon_end.color = #bff5fc
|
|
mod.widgets.ButtonPrimary.draw_text.text_style = mod.theme.font_regular{font_size: 8.5}
|
|
mod.widgets.ButtonSecondary.draw_text.color = #f2f6f9
|
|
mod.widgets.ButtonSecondary.draw_text.color_hover = #f0f6fa
|
|
mod.widgets.ButtonSecondary.draw_text.color_down = #bcc8d0
|
|
mod.widgets.ButtonSecondary.draw_text.color_focus = #f2f6f9
|
|
mod.widgets.ButtonSecondary.draw_icon.color = #f2f6f9
|
|
mod.widgets.ButtonTertiary.draw_text.color = #f2f6f9
|
|
mod.widgets.ButtonTertiary.draw_text.color_hover = #f4f8fb
|
|
mod.widgets.ButtonTertiary.draw_text.color_down = #c3cdd4
|
|
mod.widgets.ButtonTertiary.draw_text.color_focus = #f2f6f9
|
|
mod.widgets.ButtonTertiary.draw_icon.color = #f2f6f9
|
|
mod.widgets.ButtonDanger.draw_text.color = #ffb3b8
|
|
mod.widgets.ButtonDanger.draw_text.color_hover = #ffd0d3
|
|
mod.widgets.ButtonDanger.draw_text.color_down = #e6989d
|
|
mod.widgets.ButtonDanger.draw_text.color_focus = #ffb3b8
|
|
mod.widgets.ButtonDanger.draw_icon.color = #ffb3b8
|
|
mod.widgets.ButtonOutline.draw_text.color = #f2f6f9
|
|
mod.widgets.ButtonOutline.draw_text.color_hover = #f0f6fa
|
|
mod.widgets.ButtonOutline.draw_text.color_down = #bcc8d0
|
|
mod.widgets.ButtonOutline.draw_text.color_focus = #f2f6f9
|
|
mod.widgets.ButtonOutline.draw_text.color_disabled = #6b777f
|
|
mod.widgets.ButtonOutline.draw_icon.color = #dce4ea
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// CHECK BOXES. An empty box is a 13 point raised steel key with cut
|
|
// corners; a set one is pushed in: a well with its steel lip, the tick or
|
|
// the mixed bar lit cyan with its light on the well's floor.
|
|
mod.widgets.CheckBoxFlat.padding = mod.turtle.Inset{left: 0.0 right: 3.0 top: 6.0 bottom: 6.0}
|
|
mod.widgets.CheckBoxFlat.label_walk.margin = mod.turtle.Inset{left: 21.0 right: 3.0 top: 0.0 bottom: 0.0}
|
|
mod.widgets.CheckBoxFlat.draw_bg.size = uniform(13.0)
|
|
mod.widgets.CheckBoxFlat.min_height = 26.0
|
|
mod.widgets.CheckBoxFlat.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let o = self.rect_pos
|
|
let sz = self.size
|
|
let x0 = mix(2.0, self.rect_size.x - sz - 2.0, self.mark_at_end)
|
|
let lo = vec2(Finish.snap(o.x + x0, px), Finish.snap(o.y + (self.rect_size.y - sz) * 0.5, px)) - o
|
|
let h = vec2(sz * 0.5, sz * 0.5)
|
|
let c = lo + h
|
|
// the round box keeps a round well
|
|
let round = step(20.0, self.border_radius)
|
|
let k = mix(2.5, 0.0, round)
|
|
var d = fm_cbox(p, c, h, k)
|
|
if round > 0.5 {
|
|
d = length(p - c) - h.x
|
|
}
|
|
let live = 1.0 - self.disabled
|
|
var fill = mix(vec3(0.078, 0.102, 0.122), vec3(0.157, 0.176, 0.192), self.disabled)
|
|
fill = fill + g3(0.085 * self.hover * live * (1.0 - self.down) - 0.03 * self.down * live)
|
|
var col = fm_well(p, c, h, k, px, fill, (2.0 + 1.4 * self.down) * (1.0 - 0.7 * self.disabled))
|
|
col = mix(col, vec3(1.0, 0.353, 0.392), Finish.band(d, 0.0, px, px) * self.error)
|
|
// the tick, or the mixed bar
|
|
let ks = sz / 13.0
|
|
let m0 = lo + vec2(3.2, 6.8) * ks
|
|
let m1 = lo + vec2(5.6, 9.2) * ks
|
|
let m2 = lo + vec2(9.9, 3.9) * ks
|
|
let ck = min(fm_seg(p, m0, m1), fm_seg(p, m1, m2)) - 0.85 * ks
|
|
let dash = fm_seg(p, lo + vec2(3.4, 6.5) * ks, lo + vec2(9.6, 6.5) * ks) - 0.9 * ks
|
|
let show_ck = self.active * (1.0 - self.mixed)
|
|
let md = mix(mix(1000.0, ck, show_ck), dash, self.mixed)
|
|
let on = max(show_ck, self.mixed)
|
|
// the light on the floor round the mark
|
|
let glow = Finish.glow(max(md, 0.0), 1.1) * step(0.0, md) * 0.55 * on * live
|
|
col = col + fm_cyan() * glow * Finish.cover(d, px)
|
|
let mk = Finish.cover(md, px)
|
|
let core = 1.0 - smoothstep(0.0, 0.9 * ks, -md)
|
|
var tick = mix(fm_core(), mix(fm_core(), vec3(1.0, 1.0, 1.0), 0.5), 1.0 - core)
|
|
tick = mix(tick, vec3(0.34, 0.40, 0.44), self.disabled)
|
|
col = mix(col, tick, mk)
|
|
let a = Finish.cover(d, px)
|
|
var out = fm_lip(p, c, h, k, px, 0.16)
|
|
out = Finish.over(out, vec4(col * a, a))
|
|
// an empty square box is a raised steel key; set, it is pushed in and
|
|
// lit from within
|
|
if on < 0.5 && round < 0.5 {
|
|
let ink = fm_ink()
|
|
let sd2 = Finish.drop(p, c, h, 1.5, vec2(0.0, 1.2), 1.0) * 0.5 * live
|
|
out = Finish.over(vec4(ink * sd2, sd2), fm_cap(p, o, lo, lo + 2.0 * h, k, px, self.hover * live, self.down * live, self.disabled, 0.0))
|
|
let er = Finish.band(d, 0.0, px, px) * self.error
|
|
out = Finish.over(out, vec4(vec3(1.0, 0.353, 0.392) * er, er))
|
|
}
|
|
return Finish.over(out, fm_focus(d, px, self.focus * live))
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// TOGGLES. A slot 36 by 16 points holding a gunmetal block as tall as the
|
|
// slot. The part of the slot the block leaves open is a light strip: lit
|
|
// cyan when on, a dark lens when off.
|
|
mod.widgets.ToggleFlat.label_walk.margin = mod.turtle.Inset{left: 44.0 right: 3.0 top: 0.0 bottom: 0.0}
|
|
mod.widgets.ToggleFlat.draw_bg.size = uniform(16.0)
|
|
mod.widgets.ToggleFlat.min_height = 26.0
|
|
mod.widgets.ToggleFlat.padding = mod.turtle.Inset{left: 0.0 right: 3.0 top: 6.0 bottom: 6.0}
|
|
mod.widgets.ToggleFlat.draw_bg.pill_aspect = uniform(2.25)
|
|
mod.widgets.ToggleFlat.draw_bg.knob_inset = uniform(0.0)
|
|
mod.widgets.ToggleFlat.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let o = self.rect_pos
|
|
let sz = vec2(self.size * self.pill_aspect, self.size)
|
|
let x0 = mix(2.0, self.rect_size.x - sz.x - 2.0, self.mark_at_end)
|
|
let lo = vec2(Finish.snap(o.x + x0, px), Finish.snap(o.y + (self.rect_size.y - sz.y) * 0.5, px)) - o
|
|
let h = sz * 0.5
|
|
let c = lo + h
|
|
let k = 2.5
|
|
let d = fm_cbox(p, c, h, k)
|
|
let live = 1.0 - self.disabled
|
|
let knob_t = mix(self.active, self.drag_pos, self.drag)
|
|
var fill = mix(vec3(0.071, 0.094, 0.114), vec3(0.137, 0.153, 0.169), self.disabled)
|
|
fill = fill + g3(0.07 * self.hover * live - 0.02 * self.down * live)
|
|
var col = fm_well(p, c, h, k, px, fill, 2.4 + self.down)
|
|
col = mix(col, vec3(1.0, 0.353, 0.392), Finish.band(d, 0.0, px, px) * self.error)
|
|
// the light strip in the open part of the slot
|
|
let bw = 15.0
|
|
let open_lo = mix(lo.x + bw, lo.x, knob_t)
|
|
let open_c = vec2(open_lo + (sz.x - bw) * 0.5, c.y)
|
|
let sh = vec2((sz.x - bw) * 0.5 - 2.5, h.y - 3.0)
|
|
let sd = fm_rbox(p, open_c, sh, 1.0)
|
|
let lit = self.active * live
|
|
let sa = Finish.cover(sd, px)
|
|
let across = clamp(abs(p.y - c.y) / max(sh.y, 0.5), 0.0, 1.0)
|
|
let body = mix(mix(fm_core(), vec3(1.0, 1.0, 1.0), 0.45 * (1.0 - across)), fm_cyan(), across * 0.7)
|
|
let off = mix(vec3(0.047, 0.067, 0.082), fm_dim(), 0.35)
|
|
col = mix(col, mix(off, body, lit), sa)
|
|
let sg = Finish.glow(max(sd, 0.0), 1.2) * step(0.0, sd) * 0.6 * lit * Finish.cover(d, px)
|
|
col = col + fm_cyan() * sg
|
|
let a = Finish.cover(d, px)
|
|
var out = fm_lip(p, c, h, k, px, 0.16)
|
|
out = Finish.over(out, vec4(col * a, a))
|
|
// the block, its left edge on a pixel boundary
|
|
let bx = Finish.snap(o.x + lo.x + knob_t * (sz.x - bw), px) - o.x
|
|
let blo = vec2(bx, lo.y)
|
|
let bhi = vec2(bx + bw, lo.y + sz.y)
|
|
let bc = (blo + bhi) * 0.5
|
|
let bh = (bhi - blo) * 0.5
|
|
let ink = fm_ink()
|
|
let sd2 = Finish.drop(p, bc, bh, 1.5, vec2(0.0, 1.4), 1.2) * 0.45 * live
|
|
out = Finish.over(out, vec4(ink * sd2, sd2))
|
|
out = Finish.over(out, fm_cap(p, o, blo, bhi, 2.0, px, self.hover * live, self.down * live * 0.6, self.disabled, 0.0))
|
|
// two grip lines engraved across the block
|
|
let gx = bc.x
|
|
let gy = step(abs(p.y - bc.y), bh.y - 3.5)
|
|
let g1 = step(gx - 2.0 * px, p.x) * step(p.x, gx - px) * gy
|
|
let g2 = step(gx + px, p.x) * step(p.x, gx + 2.0 * px) * gy
|
|
out = Finish.over(out, vec4(fm_edge() * (g1 + g2) * 0.6, (g1 + g2) * 0.6))
|
|
let g3l = step(gx - px, p.x) * step(p.x, gx) * gy * 0.25 * live
|
|
out = Finish.over(out, vec4(g3l, g3l, g3l, g3l))
|
|
return Finish.over(out, fm_focus(d, px, self.focus * live))
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// RADIOS. A chrome dome in a dark socket; the chosen one has a cyan lens
|
|
// lit in its crown with its light on the socket.
|
|
mod.widgets.RadioButtonFlat.padding = mod.turtle.Inset{left: 0.0 right: 3.0 top: 6.0 bottom: 6.0}
|
|
mod.widgets.RadioButtonFlat.label_walk.margin = mod.turtle.Inset{left: 21.0 right: 3.0 top: 0.0 bottom: 0.0}
|
|
mod.widgets.RadioButtonFlat.draw_bg.size = uniform(13.0)
|
|
mod.widgets.RadioButtonFlat.min_height = 26.0
|
|
mod.widgets.RadioButtonFlat.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let o = self.rect_pos
|
|
let sz = self.size
|
|
let kf = fm_fit(vec2(2.0 + sz * 0.5, self.rect_size.y * 0.5), o, sz * 0.5, px)
|
|
let c = kf.xy
|
|
let rad = kf.z
|
|
let h = vec2(rad, rad)
|
|
let live = 1.0 - self.disabled
|
|
let d = length(p - c) - rad
|
|
// the socket
|
|
var fill = mix(vec3(0.047, 0.063, 0.078), vec3(0.137, 0.153, 0.169), self.disabled)
|
|
fill = fill + g3(0.07 * self.hover * live)
|
|
let col = fm_well(p, c, h, rad, px, fill, 2.0)
|
|
let a = Finish.cover(d, px)
|
|
var out = fm_lip(p, c, h, rad, px, 0.16)
|
|
out = Finish.over(out, vec4(col * a, a))
|
|
// the dome, a little smaller than the socket, sunk when pressed
|
|
let dr = rad - 1.6 - 0.4 * self.down * live
|
|
out = Finish.over(out, fm_ball(p, c, dr, px, self.hover * live, self.down * live, self.disabled))
|
|
// the lens in its crown
|
|
let on = self.active * live
|
|
let ld = length(p - c) - dr * 0.52
|
|
let lg = Finish.glow(max(ld, 0.0), 1.0) * step(0.0, ld) * on * 0.8 * step(length(p - c), dr)
|
|
out = vec4(out.rgb + fm_cyan() * lg, out.a)
|
|
let la = Finish.cover(ld, px) * self.active
|
|
let lr = clamp(length(p - c) / max(dr * 0.52, 0.001), 0.0, 1.0)
|
|
var lens = mix(mix(vec3(1.0, 1.0, 1.0), fm_core(), smoothstep(0.0, 0.6, lr)), fm_cyan(), smoothstep(0.5, 1.0, lr))
|
|
lens = mix(lens, vec3(0.30, 0.36, 0.40), self.disabled)
|
|
out = Finish.over(out, vec4(lens * la, la))
|
|
return Finish.over(out, fm_focus(d, px, self.focus * live))
|
|
}
|
|
|
|
// Radio tabs: steel caps; the chosen one is pushed in, its legend lit and
|
|
// a lamp strip glowing under it.
|
|
let fm_segment = fn(p: vec2, size: vec2, o: vec2, px: float, hover: float, down: float, focus: float, dis: float, on: float, soft: float) -> vec4 {
|
|
let live = 1.0 - dis
|
|
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o
|
|
let hi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y, px)) - o
|
|
let h = (hi - lo) * 0.5
|
|
let c = lo + h
|
|
let k = 3.0
|
|
let d = fm_cbox(p, c, h, k)
|
|
let press = max(down * live, on)
|
|
var out = vec4(0.0, 0.0, 0.0, 0.0)
|
|
let ink = fm_ink()
|
|
let sft = Finish.drop(p, c, h, 2.0, vec2(0.0, 2.0), 1.8) * 0.55 * soft * (1.0 - press) * live
|
|
out = Finish.over(out, vec4(ink * sft, sft))
|
|
let con = Finish.drop(p, c, h, 2.0, vec2(0.0, px), px * 1.2) * 0.7 * (1.0 - 0.7 * press)
|
|
out = Finish.over(out, vec4(ink * con, con))
|
|
var cap = fm_cap(p, o, lo, hi, k, px, hover * live * (1.0 - press), press, dis, 0.0)
|
|
out = Finish.over(out, cap)
|
|
// the lamp under the legend of the chosen one
|
|
let lit = on * live
|
|
let dc = vec2(Finish.snap(o.x + c.x, px) - o.x, Finish.snap(o.y + hi.y - 4.5, px) - o.y)
|
|
let hw = clamp(h.x * 0.34, 5.0, 14.0)
|
|
let a_cap = Finish.cover(d, px)
|
|
let dd = fm_rbox(p, dc, vec2(hw, px * 1.5), px * 1.5)
|
|
let gl = Finish.glow(max(dd, 0.0), 1.6) * step(0.0, dd) * lit * 0.6 * a_cap
|
|
out = vec4(out.rgb + fm_cyan() * gl, out.a)
|
|
let dsh = fm_dash(p, dc, hw, px * 1.5, px, fm_cyan(), lit)
|
|
out = Finish.over(out, dsh * on)
|
|
// focus: a cyan row inside the edge, as a ring outside would be cut
|
|
let fr = Finish.band(d, px, 2.0 * px, px) * focus * live * 0.85
|
|
out = Finish.over(out, vec4(fm_cyan() * fr, fr))
|
|
return out
|
|
}
|
|
|
|
mod.widgets.RadioButtonTabFlat.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 5.0 bottom: 7.0}
|
|
mod.widgets.RadioButtonTabFlat.min_height = 26.0
|
|
mod.widgets.RadioButtonTabFlat.label_walk.margin = mod.turtle.Inset{left: 0.0 right: 0.0 top: 0.0 bottom: 0.0}
|
|
mod.widgets.RadioButtonTabFlat.clip_x = false
|
|
mod.widgets.RadioButtonTabFlat.clip_y = false
|
|
mod.widgets.RadioButtonTabFlat.draw_bg.vertex = fn() {
|
|
let g = grow_vertex(self.rect_pos, self.rect_size, 8.0)
|
|
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
|
|
}
|
|
mod.widgets.RadioButtonTabFlat.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let m = 8.0
|
|
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
return fm_segment(p, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.active, 1.0)
|
|
}
|
|
mod.widgets.RadioButtonTabFlat.draw_text.text_style = mod.theme.font_regular{font_size: 8.5}
|
|
mod.widgets.RadioButtonTabFlat.draw_text.color = #eef3f6
|
|
mod.widgets.RadioButtonTabFlat.draw_text.color_hover = #f0f6fa
|
|
mod.widgets.RadioButtonTabFlat.draw_text.color_active = #8ff0fb
|
|
mod.widgets.RadioButtonTabFlat.draw_text.color_disabled = #6b777f
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// SLIDERS. A groove cut into the plate with a steel lip; the value is a lit
|
|
// cyan line inside it with its light on the groove's floor; the thumb is a
|
|
// chrome ball whose crown lights while it is held or focused.
|
|
mod.widgets.SliderMinimal.draw_bg.vertex = fn() {
|
|
let g = grow_vertex(self.rect_pos, self.rect_size, 6.0)
|
|
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
|
|
}
|
|
mod.widgets.SliderMinimal.clip_x = false
|
|
mod.widgets.SliderMinimal.clip_y = false
|
|
|
|
// The groove and its lit line; `lo` its top left, `w` its length, `th` its
|
|
// depth, `fx` where the value ends.
|
|
let fm_track = fn(p: vec2, lo: vec2, w: float, th: float, px: float, fx: float, dis: float, hot: float) -> vec4 {
|
|
let h = vec2(w * 0.5, th * 0.5)
|
|
let c = lo + h
|
|
let r = h.y
|
|
let d = fm_rbox(p, c, h, r)
|
|
let fill = mix(vec3(0.051, 0.071, 0.086), vec3(0.137, 0.153, 0.169), dis)
|
|
let sh = Finish.inset(p, c, h, r, vec2(0.0, 0.8), max(th * 0.25, px))
|
|
var col = mix(fill, fm_edge(), clamp(sh, 0.0, 1.0) * 0.8)
|
|
col = mix(col, fm_edge(), Finish.band(d, 0.0, px, px) * 0.8)
|
|
// the lit line: a tube one third of the groove, from its start to fx
|
|
let lh = max(th * 0.22, px)
|
|
let ld = fm_rbox(p, vec2((lo.x + 2.0 * px + fx) * 0.5, c.y), vec2(max((fx - lo.x - 2.0 * px) * 0.5, 0.0), lh), lh)
|
|
let across = clamp(abs(p.y - c.y) / lh, 0.0, 1.0)
|
|
var body = mix(mix(vec3(1.0, 1.0, 1.0), fm_core(), 0.55), fm_cyan(), smoothstep(0.1, 1.0, across))
|
|
body = body * (1.0 + 0.12 * hot)
|
|
body = mix(body, fm_off() * 1.3, dis)
|
|
let la = Finish.cover(ld, px) * step(lo.x + 2.0 * px, fx)
|
|
let lg = Finish.glow(max(ld, 0.0), max(th * 0.3, 1.0)) * step(0.0, ld) * 0.8 * (1.0 - dis) * step(lo.x + 2.0 * px, fx)
|
|
col = col + fm_cyan() * lg * Finish.cover(d, px)
|
|
col = mix(col, body, la)
|
|
let a = Finish.cover(d, px)
|
|
let out = fm_lip(p, c, h, r, px, 0.16)
|
|
return Finish.over(out, vec4(col * a, a))
|
|
}
|
|
|
|
// The thumb's lit crown while held or focused.
|
|
let fm_crown = fn(p: vec2, c: vec2, rad: float, px: float, on: float) -> vec4 {
|
|
let d = length(p - c) - rad
|
|
let a = Finish.cover(d, px) * on
|
|
let rr = clamp(length(p - c) / max(rad, 0.001), 0.0, 1.0)
|
|
let col = mix(mix(vec3(1.0, 1.0, 1.0), fm_core(), smoothstep(0.0, 0.7, rr)), fm_cyan(), smoothstep(0.7, 1.0, rr))
|
|
return vec4(col * a, a)
|
|
}
|
|
|
|
mod.widgets.SliderFlat.height = 34.0
|
|
mod.widgets.SliderFlat.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let m = 6.0
|
|
let size = self.rect_size
|
|
let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
let o = self.rect_pos
|
|
let live = 1.0 - self.disabled
|
|
let tr = 7.5
|
|
let cy = size.y - tr - 1.0
|
|
let th = 6.0 * px
|
|
let top = Finish.snap(o.y + cy, px) - o.y - 3.0 * px
|
|
let x0 = Finish.snap(o.x + 1.0, px) - o.x
|
|
let w = Finish.snap(o.x + size.x - 1.0, px) - o.x - x0
|
|
let kx = x0 + tr + self.slide_pos * (w - 2.0 * tr)
|
|
let hot = max(self.hover, self.drag) * live
|
|
var out = fm_track(p, vec2(x0, top), w, th, px, kx, self.disabled, hot)
|
|
let kf = fm_fit(vec2(kx, top + 3.0 * px), o, tr, px)
|
|
out = Finish.over(out, fm_ball(p, kf.xy, kf.z, px, self.hover * live, self.drag * live, self.disabled))
|
|
out = Finish.over(out, fm_crown(p, kf.xy, kf.z * 0.34, px, max(self.focus, self.drag) * live))
|
|
return Finish.over(out, fm_focus(length(p - kf.xy) - kf.z, px, self.focus * live))
|
|
}
|
|
|
|
// The minimal slider: a hairline groove, the lit thread in it and a small
|
|
// chrome ball.
|
|
mod.widgets.SliderMinimal.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let m = 6.0
|
|
let size = self.rect_size
|
|
let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
let o = self.rect_pos
|
|
let live = 1.0 - self.disabled
|
|
let cy = Finish.snap(o.y + size.y - 5.0, px) - o.y
|
|
let tr = mix(4.5, 5.0, max(self.hover, self.drag))
|
|
let kx = tr + self.slide_pos * (size.x - 2.0 * tr)
|
|
let inx = step(0.0, p.x) * step(p.x, size.x)
|
|
let dark = step(cy - 2.0 * px, p.y) * step(p.y, cy) * inx
|
|
let light = step(cy, p.y) * step(p.y, cy + px) * inx * 0.16
|
|
let lit = step(p.x, kx) * dark
|
|
var out = vec4(light, light, light, light)
|
|
let g = mix(vec3(0.035, 0.047, 0.059), mix(fm_cyan(), fm_off(), self.disabled), lit)
|
|
out = Finish.over(out, vec4(g * dark, dark))
|
|
// the thread's light on the plate either side of it
|
|
let gd = abs(p.y - (cy - px)) - px
|
|
let gl = Finish.glow(max(gd, 0.0), 0.9) * step(0.0, gd) * step(0.0, p.x) * step(p.x, kx) * 0.35 * live
|
|
out = vec4(out.rgb + fm_cyan() * gl, out.a)
|
|
let kf = fm_fit(vec2(kx, cy - px), o, tr, px)
|
|
out = Finish.over(out, fm_ball(p, kf.xy, kf.z, px, self.hover * live, self.drag * live, self.disabled))
|
|
out = Finish.over(out, fm_crown(p, kf.xy, kf.z * 0.34, px, max(self.focus, self.drag) * live))
|
|
return Finish.over(out, fm_focus(length(p - kf.xy) - kf.z, px, self.focus * live))
|
|
}
|
|
mod.widgets.SliderMinimal.text_input.draw_text.text_style = mod.theme.font_regular{font_size: 7.5}
|
|
|
|
// The round slider: a pill groove beside its label, the lit line in it and
|
|
// a chrome ball riding in the groove.
|
|
mod.widgets.SliderRoundFlat.height = 14.0
|
|
mod.widgets.SliderRoundFlat.text_input.margin = mod.turtle.Inset{left: 0.0 right: 8.0 top: 0.0 bottom: 0.0}
|
|
mod.widgets.SliderRoundFlat.text_input.min_height = 0.0
|
|
mod.widgets.SliderRoundFlat.text_input.padding = mod.turtle.Inset{left: 0.0 right: 0.0 top: 0.0 bottom: 0.0}
|
|
mod.widgets.SliderRoundFlat.text_input.draw_text.text_style = mod.theme.font_regular{font_size: 7.5}
|
|
mod.widgets.SliderRoundFlat.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let m = 6.0
|
|
let size = self.rect_size
|
|
let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
let o = self.rect_pos
|
|
let live = 1.0 - self.disabled
|
|
let x0 = Finish.snap(o.x + self.label_size, px) - o.x
|
|
let y0 = Finish.snap(o.y, px) - o.y
|
|
let hi = vec2(Finish.snap(o.x + size.x - 36.0, px) - o.x, Finish.snap(o.y + size.y, px) - o.y - px)
|
|
let th = hi.y - y0
|
|
let cr = th * 0.5 + 0.5
|
|
let gth = 6.0 * px
|
|
let gy = Finish.snap(o.y + y0 + th * 0.5, px) - o.y - 3.0 * px
|
|
let kx = x0 + cr + self.slide_pos * (hi.x - x0 - 2.0 * cr)
|
|
let hot = max(self.hover, self.drag) * live
|
|
var out = fm_track(p, vec2(x0, gy), hi.x - x0, gth, px, kx, self.disabled, hot)
|
|
let kf = fm_fit(vec2(kx, y0 + th * 0.5), o, cr, px)
|
|
out = Finish.over(out, fm_ball(p, kf.xy, kf.z, px, self.hover * live, self.drag * live, self.disabled))
|
|
let d = fm_rbox(p, vec2((x0 + hi.x) * 0.5, y0 + th * 0.5), vec2((hi.x - x0) * 0.5, th * 0.5), th * 0.5)
|
|
return Finish.over(out, fm_focus(d, px, self.focus * live))
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// KNOBS. A spun steel cap in a dark bezel: a cast shadow, the bezel's lit
|
|
// outer rim, a bright chamfer ring round the spun face and an engraved
|
|
// pointer. The bezel's groove carries the value: a glowing cyan ring whose
|
|
// light falls on the groove and a little past it; the rest of the groove
|
|
// is dark glass. The dial's ring is a row of lit segments.
|
|
let fm_ring = fn(p: vec2, c: vec2, ar: float, aw: float, px: float, val: float, gap: float, dis: float, hot: float, segs: float) -> vec4 {
|
|
let start = gap * 0.5
|
|
let sweep = 2.0 * PI - gap
|
|
let q = p - c
|
|
var ang = atan2(-q.x, q.y)
|
|
if ang < 0.0 {
|
|
ang = ang + 2.0 * PI
|
|
}
|
|
let rl = length(q)
|
|
let rd = abs(rl - ar) - aw * 0.5
|
|
let in_arc = clamp((ang - start) * ar / px + 0.5, 0.0, 1.0) * clamp((start + sweep - ang) * ar / px + 0.5, 0.0, 1.0)
|
|
let end = start + sweep * clamp(val, 0.0, 1.0)
|
|
let lit_in = clamp((ang - start) * ar / px + 0.5, 0.0, 1.0) * clamp((end - ang) * ar / px + 0.5, 0.0, 1.0)
|
|
// segments: a lamp every 9 degrees, 70 % of the pitch
|
|
var segm = 1.0
|
|
if segs > 0.5 {
|
|
let pitch = 9.0 * PI / 180.0
|
|
let f = fract((ang - start) / pitch)
|
|
let sl = pitch * ar
|
|
segm = clamp(min(f, 0.72 - f) * sl / px + 0.5, 0.0, 1.0)
|
|
}
|
|
var out = vec4(0.0, 0.0, 0.0, 0.0)
|
|
let live = 1.0 - dis
|
|
// the dark glass of the unlit groove
|
|
let ga = Finish.cover(rd, px) * in_arc * segm
|
|
let gc = mix(fm_dim(), vec3(0.10, 0.12, 0.13), dis)
|
|
out = Finish.over(out, vec4(gc * ga, ga))
|
|
// the lit part: a tube, white-hot along its middle
|
|
let across = clamp(abs(rl - ar) / max(aw * 0.5, 0.001), 0.0, 1.0)
|
|
var body = mix(mix(vec3(1.0, 1.0, 1.0), fm_core(), 0.5), fm_cyan(), smoothstep(0.15, 1.0, across))
|
|
body = body * (1.0 + 0.1 * hot)
|
|
body = mix(body, fm_off() * 1.4, dis)
|
|
let la = Finish.cover(rd, px) * lit_in * segm
|
|
// its light: over the bezel and a little past it
|
|
let hg = Finish.glow(max(rd, 0.0), aw * 1.2) * step(0.0, rd) * lit_in * (0.75 + 0.2 * hot) * live
|
|
out = Finish.over(vec4(fm_cyan() * hg, hg), out)
|
|
out = Finish.over(out, vec4(body * la, la))
|
|
return out
|
|
}
|
|
|
|
let fm_knob = fn(p: vec2, c: vec2, rad: float, bez: float, px: float, val: float, gap: float, hover: float, press: float, dis: float) -> vec4 {
|
|
let dia = rad * 2.0
|
|
let live = 1.0 - dis
|
|
let ink = fm_ink()
|
|
var out = vec4(0.0, 0.0, 0.0, 0.0)
|
|
let outer = rad + bez
|
|
let cast = Finish.drop(p, c, vec2(outer, outer), outer, vec2(0.0, dia * 0.07), dia * 0.07) * 0.6 * mix(1.0, 0.4, dis)
|
|
out = Finish.over(out, vec4(ink * cast, cast))
|
|
let q = p - c
|
|
let rl = length(q)
|
|
// the bezel: dark gunmetal, a lit outer rim on its upper half
|
|
let bd = rl - outer
|
|
let up = clamp(-q.y / max(rl, 0.001), -1.0, 1.0)
|
|
var bc = mix(vec3(0.075, 0.094, 0.110), vec3(0.106, 0.129, 0.149), 0.5 + 0.5 * up)
|
|
bc = mix(bc, mix(vec3(0.20, 0.23, 0.25), vec3(0.58, 0.65, 0.70), clamp(up, 0.0, 1.0)), Finish.band(bd, px, 2.0 * px, px))
|
|
bc = mix(bc, fm_edge(), Finish.band(bd, 0.0, px, px))
|
|
let ba = Finish.cover(bd, px)
|
|
out = Finish.over(out, vec4(bc * ba, ba))
|
|
// the cap's own shadow on the bezel
|
|
let cs = Finish.drop(p, c, vec2(rad, rad), rad, vec2(0.0, 1.0), 1.0) * 0.5
|
|
out = Finish.over(out, vec4(ink * cs, cs))
|
|
// the spun face
|
|
let d = rl - rad
|
|
var face = vec3(0.560, 0.615, 0.655) * Finish.spun(p, c, vec2(-0.3, -0.9))
|
|
face = face + fm_cyan() * 0.05 * smoothstep(0.2, 1.0, q.y / max(rad, 0.001))
|
|
face = face + g3(0.05 * hover - 0.05 * press)
|
|
face = mix(face, g3(0.30) + face * 0.15, dis * 0.75)
|
|
// chamfer ring: bright toward the light, a dark groove inside it
|
|
let cw = max(floor(dia * 0.05 / px + 0.5), 2.0) * px
|
|
let chm = mix(vec3(0.30, 0.34, 0.37), vec3(0.93, 0.96, 0.98), smoothstep(-0.6, 0.8, up))
|
|
face = mix(face, vec3(0.22, 0.25, 0.28), Finish.band(d, cw, cw + px, px) * 0.8)
|
|
face = mix(face, mix(chm, g3(0.34), dis), Finish.band(d, px, cw, px))
|
|
face = mix(face, fm_edge(), Finish.band(d, 0.0, px, px))
|
|
// the pointer, engraved: a dark line with a light line on its lower side
|
|
let pang = gap * 0.5 + (2.0 * PI - gap) * val
|
|
let dir = vec2(-sin(pang), cos(pang))
|
|
let inner = rad - cw - px
|
|
let pa = c + dir * inner * 0.30
|
|
let pb = c + dir * inner * 0.92
|
|
let pw = max(dia * 0.022, 0.6)
|
|
let pl = fm_seg(p, pa, pb) - pw
|
|
let pl_lit = fm_seg(p - vec2(0.0, px), pa, pb) - pw
|
|
let on_face = step(rl, inner)
|
|
face = mix(face, g3(0.97), Finish.cover(pl_lit, px) * 0.5 * live * on_face)
|
|
face = mix(face, mix(vec3(0.078, 0.094, 0.106), g3(0.3), dis), Finish.cover(pl, px) * on_face)
|
|
let a = Finish.cover(d, px)
|
|
return Finish.over(out, vec4(face * a, a))
|
|
}
|
|
|
|
mod.widgets.RotaryKnob.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let m = 6.0
|
|
let size = self.rect_size
|
|
let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
let o = self.rect_pos
|
|
let live = 1.0 - self.disabled
|
|
let dia = min(size.x, size.y)
|
|
let rr = dia * 0.5
|
|
let c = vec2(Finish.snap(o.x + size.x * 0.5, px) - o.x, Finish.snap(o.y + size.y - rr, px) - o.y)
|
|
let gap = self.gap * PI / 180.0
|
|
let outer = floor(rr * 0.92 / px) * px
|
|
let bez = max(floor(outer * 0.24 / px + 0.5), 4.0) * px
|
|
let rad = outer - bez
|
|
let aw = max(floor(bez * 0.45 / px + 0.5), 2.0) * px
|
|
let ar = rad + bez * 0.5
|
|
let hot = max(self.hover, self.drag) * live
|
|
var out = fm_knob(p, c, rad, bez, px, self.slide_pos, gap, max(self.hover, self.focus) * live, self.drag * live, self.disabled)
|
|
out = Finish.over(out, fm_ring(p, c, ar, aw, px, self.slide_pos, gap, self.disabled, hot, 0.0))
|
|
return Finish.over(out, fm_focus(length(p - c) - outer, px, self.focus * live))
|
|
}
|
|
|
|
mod.widgets.RotaryFlat.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let m = 6.0
|
|
let size = self.rect_size
|
|
let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
let o = self.rect_pos
|
|
let live = 1.0 - self.disabled
|
|
let room = vec2(size.x, size.y - 20.0)
|
|
let dia = min(room.x, room.y)
|
|
let rr = dia * 0.5
|
|
let c = vec2(Finish.snap(o.x + size.x * 0.5, px) - o.x, Finish.snap(o.y + 20.0 + room.y * 0.5, px) - o.y)
|
|
let gap = self.gap * PI / 180.0
|
|
let outer = floor(rr * 0.94 / px) * px
|
|
let bez = max(floor(outer * 0.22 / px + 0.5), 5.0) * px
|
|
let rad = outer - bez
|
|
let aw = max(floor(bez * 0.5 / px + 0.5), 3.0) * px
|
|
let ar = rad + bez * 0.5
|
|
let hot = max(self.hover, self.drag) * live
|
|
var out = fm_knob(p, c, rad, bez, px, self.slide_pos, gap, max(self.hover, self.focus) * live, self.drag * live, self.disabled)
|
|
out = Finish.over(out, fm_ring(p, c, ar, aw, px, self.slide_pos, gap, self.disabled, hot, 1.0))
|
|
return Finish.over(out, fm_focus(length(p - c) - outer, px, self.focus * live))
|
|
}
|
|
|
|
// The dial's readout: dark glass with cyan digits.
|
|
mod.widgets.RotaryFlat.text_input.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 3.0 bottom: 3.0}
|
|
mod.widgets.RotaryFlat.text_input.min_height = 20.0
|
|
mod.widgets.RotaryFlat.text_input.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let o = self.rect_pos
|
|
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o
|
|
let hi = vec2(Finish.snap(o.x + self.rect_size.x, px), Finish.snap(o.y + self.rect_size.y, px)) - o - vec2(0.0, px)
|
|
let h = (hi - lo) * 0.5
|
|
let c = lo + h
|
|
let k = 2.5
|
|
let d = fm_cbox(p, c, h, k)
|
|
var col = fm_well(p, c, h, k, px, vec3(0.047, 0.071, 0.086), 2.4)
|
|
let t = clamp((p.y - lo.y) / max(2.0 * h.y, 1.0), 0.0, 1.0)
|
|
col = col + g3(0.025 * (1.0 - smoothstep(0.1, 0.4, t)) * step(2.0 * px, p.y - lo.y))
|
|
let a = Finish.cover(d, px)
|
|
var out = fm_lip(p, c, h, k, px, 0.16)
|
|
return Finish.over(out, vec4(col * a, a))
|
|
}
|
|
mod.widgets.RotaryFlat.text_input.draw_text.color = #72f0fb
|
|
mod.widgets.RotaryFlat.text_input.draw_text.color_hover = #72f0fb
|
|
mod.widgets.RotaryFlat.text_input.draw_text.color_focus = #a6f7ff
|
|
mod.widgets.RotaryFlat.text_input.draw_text.color_empty = #2b6873
|
|
mod.widgets.RotaryFlat.text_input.draw_text.text_style = mod.theme.font_code{font_size: 9.0}
|
|
// The knob's caption is a scale label.
|
|
mod.widgets.RotaryKnob.draw_text.text_style = mod.theme.font_regular{font_size: 7.0}
|
|
mod.widgets.RotaryKnob.draw_text.color = #eef3f6
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// FADERS. The desk cap in chrome, drawn by the cap engine (`mod.sdf.Cap`)
|
|
// to the slider bench's preset for this sheet: a block ribbed across its
|
|
// middle (ten ribs, 0.22 deep), split across its centre down to the base,
|
|
// and chrome because it mirrors the engine's plain sky at reflection 1 and
|
|
// metal 1, with no environment map. It runs a black slot five points wide
|
|
// whose value fill is lit sky blue: the fill glows round itself where it
|
|
// shows beside the cap and lights the cap's end walls. 27 ticks stand on
|
|
// the far side. The states are this sheet's: hover and focus lift the cap,
|
|
// a press sinks it half a point and lights its split cyan (the old block's
|
|
// centre line), focus rings it in cyan, and disabled turns the cap to dull
|
|
// steel and puts the fill out.
|
|
mod.widgets.SliderFader.cap_size = 28.0
|
|
mod.widgets.SliderFader.track_inset = 4.0
|
|
mod.widgets.SliderFader.draw_bg.track_size = uniform(5.0)
|
|
mod.widgets.SliderFader.draw_bg.cap_cross = uniform(0.32)
|
|
// The cap's shadow and the fill's halo fall on the plate beside the fader.
|
|
mod.widgets.SliderFader.clip_x = false
|
|
mod.widgets.SliderFader.clip_y = false
|
|
// The material as the bench tuned it, as uniforms so the tweaker moves them.
|
|
// The light is screen space: x right, y down, z toward the viewer. The
|
|
// exposure is in stops, as the bench's EV; the height is in points.
|
|
mod.widgets.SliderFader.draw_bg.cap_light = uniform(vec3(-0.22, -0.7, 0.56))
|
|
mod.widgets.SliderFader.draw_bg.cap_light_intensity = uniform(1.05)
|
|
mod.widgets.SliderFader.draw_bg.cap_specular = uniform(0.9)
|
|
mod.widgets.SliderFader.draw_bg.cap_roughness = uniform(0.66)
|
|
mod.widgets.SliderFader.draw_bg.cap_gloss = uniform(0.0)
|
|
mod.widgets.SliderFader.draw_bg.cap_rim = uniform(0.0)
|
|
mod.widgets.SliderFader.draw_bg.cap_ao = uniform(0.52)
|
|
mod.widgets.SliderFader.draw_bg.cap_ao_reach = uniform(1.9)
|
|
mod.widgets.SliderFader.draw_bg.cap_reflection = uniform(1.0)
|
|
mod.widgets.SliderFader.draw_bg.cap_reflection_perspective = uniform(0.42)
|
|
mod.widgets.SliderFader.draw_bg.cap_metal = uniform(1.0)
|
|
mod.widgets.SliderFader.draw_bg.cap_spun = uniform(0.0)
|
|
mod.widgets.SliderFader.draw_bg.cap_glow = uniform(0.0)
|
|
mod.widgets.SliderFader.draw_bg.cap_exposure = uniform(-0.45)
|
|
mod.widgets.SliderFader.draw_bg.cap_rolloff = uniform(0.48)
|
|
mod.widgets.SliderFader.draw_bg.cap_shadow = uniform(0.65)
|
|
mod.widgets.SliderFader.draw_bg.cap_shadow_blur = uniform(8.0)
|
|
mod.widgets.SliderFader.draw_bg.cap_contact = uniform(0.5)
|
|
mod.widgets.SliderFader.draw_bg.cap_inner = uniform(0.7)
|
|
mod.widgets.SliderFader.draw_bg.cap_height = uniform(13.0)
|
|
// The inks are the preset's own: the chrome's body, the slot, the lit fill,
|
|
// the ticks, and the light and the shade the bench lit them with.
|
|
mod.widgets.SliderFader.draw_bg.handle_color = uniform(#c2c6cc)
|
|
mod.widgets.SliderFader.draw_bg.track_color = uniform(#x0b0c0e)
|
|
mod.widgets.SliderFader.draw_bg.fill_color = uniform(#7fd4ff)
|
|
mod.widgets.SliderFader.draw_bg.tick_color = uniform(#c9ced6)
|
|
mod.widgets.SliderFader.draw_bg.light_color = uniform(#ffffff)
|
|
mod.widgets.SliderFader.draw_bg.shadow_color = uniform(#x2e3036)
|
|
// The ground under the horizon of the sky the chrome mirrors: the bench's
|
|
// own dark ground. It is what gives the chrome the dark bands the bench
|
|
// showed; this sheet's lighter plate there would dull them.
|
|
mod.widgets.SliderFader.draw_bg.sky_ground_color = uniform(#26272b)
|
|
|
|
// The room the fader's quad takes round its rect, in points: how far the
|
|
// cap's shadow is thrown from the light, with its blur, and the cap's glow
|
|
// if it has one; then a band over which the fill's halo and whatever else
|
|
// reaches that far fades out, so the quad's edge never shows.
|
|
let fm_fader_room = fn(l: vec3, height: float, blur: float, glow: float) -> float {
|
|
let n = normalize(l)
|
|
let reach = length(n.xy) / max(n.z, 0.25) * max(height, 0.0) * 0.7
|
|
return reach + blur + glow * 24.0 + 10.0
|
|
}
|
|
|
|
mod.widgets.SliderFader.draw_bg.vertex = fn() {
|
|
let m = fm_fader_room(self.cap_light, self.cap_height, self.cap_shadow_blur, self.cap_glow)
|
|
let g = grow_vertex(self.rect_pos, self.rect_size, m)
|
|
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
|
|
}
|
|
mod.widgets.SliderFader.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let m = fm_fader_room(self.cap_light, self.cap_height, self.cap_shadow_blur, self.cap_glow)
|
|
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 its zero (up the screen when
|
|
// the fader stands), `ab.y` across it.
|
|
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 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 = across * 0.5
|
|
let at = foot + self.slide_pos * travel
|
|
let hs = vec2(self.cap_px * 0.5, min(max(across * self.cap_cross, 4.0), across) * 0.5)
|
|
let l = normalize(self.cap_light)
|
|
let lt = Cap.light(l, vert)
|
|
let light_ink = self.light_color.rgb
|
|
let shadow_ink = self.shadow_color.rgb
|
|
let dark = vec3(0.0, 0.0, 0.0)
|
|
let ev = pow(2.0, self.cap_exposure)
|
|
let ro = self.cap_rolloff
|
|
|
|
// THE GROUND. The slot runs from one track inset to the other, its ends
|
|
// rounded by a fifth of its half width; disabled, it goes to the flat
|
|
// dull well this sheet dims its tracks to.
|
|
let tw = min(self.track_size, across)
|
|
let tlen = max(along - self.inset_px * 2.0, 1.0)
|
|
let tbox = vec4(self.inset_px + tlen * 0.5, mid, tlen * 0.5, tw * 0.5)
|
|
let tr = 0.2 * tw * 0.5
|
|
let track_ink = mix(self.track_color.rgb, vec3(0.13, 0.14, 0.15), self.disabled)
|
|
// The fill's two ends are the widget's (a gain fader's grows out of its
|
|
// default); an end at a stop runs on to the slot's own end. Lit, the fill
|
|
// is its own light; disabled, it is an unlit bar of the sheet's steel.
|
|
var lo = foot + self.fill_lo * travel
|
|
var hi = foot + self.fill_hi * travel
|
|
if self.fill_lo < 0.0005 {
|
|
lo = self.inset_px
|
|
}
|
|
if self.fill_hi > 0.9995 {
|
|
hi = along - self.inset_px
|
|
}
|
|
let fill = 1.0 + step(0.5, live)
|
|
let fill_ink = mix(self.fill_color.rgb, fm_off() * 1.3, self.disabled)
|
|
// The fader lies on this sheet's brushed plate, which is lighter than
|
|
// the bench's ground, so nothing here assumes a ground colour: the slot
|
|
// is drawn on black, once lit and once with its fill dark. Inside, the
|
|
// lit one is the slot as the bench draws it. Outside, the dark one is
|
|
// the light of the lip alone, and what the lit one adds to it is the
|
|
// fill's halo, read as a share of the fill's own light.
|
|
let lit_slot = Cap.slot(dark, ab, tbox, tr, fill, lo, hi, at, hs.x, lt, px, 0.0, 0.0, track_ink, fill_ink, light_ink, shadow_ink, self.cap_inner, self.cap_ao)
|
|
let dark_slot = Cap.slot(dark, ab, tbox, tr, fill, lo, hi, at, hs.x, lt, px, 0.0, 0.0, track_ink, dark, light_ink, shadow_ink, self.cap_inner, self.cap_ao)
|
|
let tcov = Cap.cover(fm_rbox(ab, tbox.xy, tbox.zw, tr), px)
|
|
let halo = max(lit_slot - dark_slot, dark) * (1.0 - tcov)
|
|
let fill_top = max(max(fill_ink.x, fill_ink.y), fill_ink.z) * 1.02
|
|
let halo_share = clamp(max(max(halo.x, halo.y), halo.z) / max(fill_top, 0.001), 0.0, 1.0)
|
|
let lip = dark_slot * (1.0 - tcov)
|
|
// The ticks, 27 on the far side, as the share they paint.
|
|
let tick = Cap.ticks(dark, ab, mid, foot, travel, 27.0, 1.0, 6.0, tw, 0.0, px, vec3(1.0, 1.0, 1.0)).x
|
|
|
|
// THE CAP: the bench's block. Shape: corner share, edge width share,
|
|
// blend, height, less the half point a press sinks it. Ribs: count,
|
|
// depth, band from, band to. The mark is a split (kind 3) across the
|
|
// cap at its middle, 1.2 points wide, over its whole width; the fourth
|
|
// number is the reach of the light round it while it is held. No cut.
|
|
let sink = 0.5 * self.drag * live
|
|
let shape = vec4(0.05, 0.08, 0.01, max(self.cap_height - sink, 0.5))
|
|
let rib = vec4(10.0, 0.22, 0.06, 0.94)
|
|
let mark = vec4(3.0, 1.2, 0.35, 1.5)
|
|
let mark_at = vec4(0.0, 0.5, 1.0, 0.0)
|
|
let cut = vec4(0.0, 0.0, 0.0, 0.0)
|
|
let q = ab - vec2(at, mid)
|
|
let r = shape.x * min(hs.x, hs.y)
|
|
// The bench's plan curve is flat at one: no pinch, the engine's none.
|
|
let fp = Cap.sd_cap(q, hs, r, vec2(1.0, 0.0))
|
|
let cov = Cap.cover(fp.x, px)
|
|
var cap = vec3(0.0, 0.0, 0.0)
|
|
var shade_amt = 0.0
|
|
var glow_light = vec3(0.0, 0.0, 0.0)
|
|
// Only a pixel within reach of the cap and what it throws runs the rest.
|
|
let far = abs(q) - hs
|
|
let reach = max(hs.x, hs.y) * (1.0 + 2.2 * self.cap_glow) + shape.w * 1.5 + self.cap_shadow_blur * 1.5 + 6.0
|
|
if max(far.x, far.y) < reach {
|
|
// The preset's curves, handles resolved by the bench: the along
|
|
// curve is symmetric, drawn from the centre to one end.
|
|
let c_along = Cap.mirror(Cap.curve(abs(q.x) / hs.x, 7.0, vec2(0.0, 0.72), vec2(0.1167, 0.0), vec2(-0.09, -0.0363), vec2(0.35, 0.8), vec2(0.09, 0.0363), vec2(-0.0433, -0.0217), vec2(0.62, 0.97), vec2(0.0433, 0.0217), vec2(-0.0333, 0.0), vec2(0.75, 1.0), vec2(0.0333, 0.0), vec2(-0.0333, 0.1083), vec2(0.85, 0.85), vec2(0.0333, -0.1083), vec2(0.0, 0.0), vec2(0.95, 0.35), 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)), q.x)
|
|
let c_across = Cap.curve(abs(q.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, 0.08), 3.0, vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(0.06, 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 c_tooth = Cap.curve(Cap.rib_x(q, hs, rib), 4.0, vec2(0.0, 1.0), vec2(0.1, 0.0), vec2(0.0, 0.0), vec2(0.6, 1.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(0.72, 0.0), vec2(0.0, 0.0), vec2(-0.0467, 0.0), vec2(1.0, 0.0), vec2(0.0467, 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 f = Cap.field(q, hs, fp, c_along, c_across, c_edge, c_tooth, shape, rib, mark, mark_at, cut)
|
|
// While the cap is held its split lights in this sheet's cyan.
|
|
let held = Cap.mark_line(q, hs, mark, mark_at, px) * self.drag * live
|
|
let body = self.handle_color.rgb
|
|
cap = Cap.shade(Cap.normal(f.yz, shape.w, vert), l, Cap.view(q, hs, self.cap_reflection_perspective, vert), f.x, body, 0.0, held, 1.0, self.cap_specular, self.cap_roughness, self.cap_gloss, self.cap_rim, self.cap_ao, self.cap_ao_reach, self.cap_reflection, self.cap_metal, self.cap_light_intensity, fm_core(), light_ink, shadow_ink, self.sky_ground_color.rgb)
|
|
let sp = Cap.spun(q, hs, lt.xy) * self.cap_spun
|
|
cap = Cap.in_shadow(Cap.in_light(cap, clamp(sp, 0.0, 1.0) * 0.55, light_ink), clamp(-sp, 0.0, 1.0) * 0.42, shadow_ink)
|
|
cap = Cap.emit(cap, f.x, body, light_ink, self.cap_glow)
|
|
cap = cap + fill_ink * Cap.fill_light(q, hs, f.yz * shape.w, lo, hi, at, tw) * step(1.5, fill)
|
|
cap = cap + fm_core() * Cap.mark_glow(q, hs, mark, mark_at) * (1.0 - held) * self.drag * live
|
|
// Under it, the cast shadow (thrown from the preset's top outline)
|
|
// and the contact shade; disabled, the shadow thins as the sheet's
|
|
// did.
|
|
let sh = Cap.shadow(q, hs, r, vec3(0.8745, 0.9373, 0.2626), lt, shape.w, self.cap_shadow_blur, self.cap_shadow * mix(1.0, 0.3, self.disabled), px)
|
|
let ct = Cap.contact(fp.x, max(shape.w * 0.3, 1.0), self.cap_contact)
|
|
shade_amt = clamp(sh + ct, 0.0, 1.0) * (1.0 - cov)
|
|
glow_light = body * Cap.glow(fp.x, hs, self.cap_glow) * 0.9 * (1.0 - cov)
|
|
}
|
|
|
|
// Toned as the bench tones. Hover and focus lift the cap and a press
|
|
// takes that back, by what the sheet's old block used; disabled, the
|
|
// cap goes to the sheet's dull steel.
|
|
var face = Cap.tone(cap, ev, ro)
|
|
face = max(face + g3(0.04 * max(self.hover, self.focus) * live - 0.04 * self.drag * live), dark)
|
|
face = mix(face, vec3(0.24, 0.27, 0.29), self.disabled * 0.8)
|
|
// Laid over whatever the fader lies on, in the bench's order. The halo
|
|
// tints the ground toward the fill's own colour, so it never outshines
|
|
// the fill on a light plate; the lip and a glowing cap add their light;
|
|
// the ticks are painted; the cap's shadow darkens all of that, fading
|
|
// out over the outer band of the quad. Then the slot, shaded by the cap
|
|
// too, and the cap itself are painted solid.
|
|
var out = vec4(Cap.tone(fill_ink * 1.02, ev, ro) * halo_share, halo_share)
|
|
out = vec4(out.rgb + Cap.tone(lip + glow_light, ev, ro), out.a)
|
|
out = Finish.over(out, vec4(Cap.tone(self.tick_color.rgb, ev, ro) * tick, tick))
|
|
out = Finish.over(out, vec4(0.0, 0.0, 0.0, shade_amt * (1.0 - (shadow_ink.x + shadow_ink.y + shadow_ink.z) / 3.0)))
|
|
let inside = min(min(p.x, size.x - p.x), min(p.y, size.y - p.y)) + m
|
|
out = out * smoothstep(0.0, 8.0, inside)
|
|
out = mix(out, vec4(Cap.tone(lit_slot * mix(vec3(1.0, 1.0, 1.0), shadow_ink, shade_amt), ev, ro), 1.0), tcov)
|
|
out = mix(out, vec4(face, 1.0), cov)
|
|
return Finish.over(out, fm_focus(fp.x, px, self.focus * live))
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// TEXT FIELDS. A dark well with cut corners in a steel frame: the frame is
|
|
// one device pixel, lit along the foot and shaded along the top; the floor
|
|
// darkest under the top wall. Focus lights the frame cyan and lays a line
|
|
// of light along the well's top and foot, inside the box.
|
|
let fm_field = fn(p: vec2, size: vec2, o: vec2, px: float, hover: float, down: float, focus: float, dis: float, k: float) -> vec4 {
|
|
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o
|
|
let hi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y, px)) - o
|
|
let h = (hi - lo) * 0.5
|
|
let c = lo + h
|
|
let d = fm_cbox(p, c, h, k)
|
|
let n = fm_norm(p, c, h, k)
|
|
let t = clamp((p.y - lo.y) / max(2.0 * h.y, 1.0), 0.0, 1.0)
|
|
let u = clamp(t * 2.0, 0.0, 1.0)
|
|
let rise = 1.0 - (1.0 - u) * (1.0 - u)
|
|
var fill = mix(vec3(0.050, 0.066, 0.086), vec3(0.090, 0.114, 0.140), rise)
|
|
fill = mix(fill, vec3(0.106, 0.132, 0.160), clamp(t * 2.0 - 1.0, 0.0, 1.0))
|
|
fill = fill + g3(0.035 * hover * (1.0 - dis) - 0.015 * down * (1.0 - dis))
|
|
fill = mix(fill, vec3(0.157, 0.176, 0.192), dis)
|
|
fill = fill + g3(fm_grain((p + o) / px, 0.006))
|
|
// the inner shadow under the top wall
|
|
let sh = Finish.inset(p, c, h, max(k * 0.6, 0.5), vec2(0.0, 1.2), 1.4)
|
|
var col = mix(fill, fm_edge(), clamp(sh, 0.0, 1.0) * 0.8)
|
|
// the steel frame: lit where it faces up... the lip catches light at the
|
|
// foot, the top run is in shade
|
|
let lt = clamp(dot(n, vec2(0.0, 1.0)), -1.0, 1.0)
|
|
var fr = mix(vec3(0.56, 0.62, 0.66), vec3(0.86, 0.91, 0.94), clamp(lt, 0.0, 1.0))
|
|
fr = mix(fr, vec3(0.30, 0.34, 0.37), clamp(-lt, 0.0, 1.0))
|
|
fr = fr + g3(0.14 * hover * (1.0 - dis))
|
|
fr = mix(fr, vec3(0.22, 0.25, 0.27), dis)
|
|
let k2 = fm_cyan()
|
|
fr = mix(fr, mix(k2, fm_core(), 0.5), focus)
|
|
col = mix(col, fr, Finish.band(d, 0.0, 2.0 * px, px))
|
|
col = mix(col, fm_edge(), Finish.band(d, 2.0 * px, 3.0 * px, px) * 0.85 * (1.0 - focus))
|
|
// the light of focus on the floor, just inside the frame
|
|
let fg = Finish.glow(max(-d - px, 0.0), 1.4) * focus * 0.55 * step(d, -px)
|
|
col = col + k2 * fg
|
|
let a = Finish.cover(d, px)
|
|
return vec4(col * a, a)
|
|
}
|
|
|
|
mod.widgets.TextInputFlat.padding = mod.turtle.Inset{left: 7.0 right: 7.0 top: 3.0 bottom: 3.0}
|
|
mod.widgets.TextInputFlat.draw_bg.pixel = fn() {
|
|
// The value field inside a slider or a well draws no box of its own.
|
|
if self.color.a < 0.01 {
|
|
return vec4(0.0, 0.0, 0.0, 0.0)
|
|
}
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
return fm_field(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, 3.0)
|
|
}
|
|
mod.widgets.TextInputFlat.draw_text.color = #f2f6f9
|
|
mod.widgets.TextInputFlat.draw_text.color_hover = #f0f6fa
|
|
mod.widgets.TextInputFlat.draw_text.color_focus = #f0f6fa
|
|
mod.widgets.TextInputFlat.draw_text.color_empty = #7f8c95
|
|
mod.widgets.TextInputFlat.draw_text.color_disabled = #6b777f
|
|
mod.widgets.TextInputFlat.draw_cursor.color = #30c8e2
|
|
|
|
mod.widgets.NumberField.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
return fm_field(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, self.hover, 0.0, self.focus, self.disabled, 3.0)
|
|
}
|
|
// The box a field's input sits in is the same well.
|
|
mod.widgets.FieldWell.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
return fm_field(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, self.hover, 0.0, self.focus, self.disabled, 3.0)
|
|
}
|
|
|
|
// The stepper: a gunmetal column set into the well's end, split by an
|
|
// engraved line, each half with a small chevron.
|
|
let fm_spin = fn(p: vec2, size: vec2, o: vec2, px: float, hot: float, down: float, dis: float) -> vec4 {
|
|
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o + vec2(0.0, 2.0 * px)
|
|
let hi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y, px)) - o - vec2(2.0 * px, 2.0 * px)
|
|
let h = (hi - lo) * 0.5
|
|
let c = lo + h
|
|
let d = fm_cbox(p, c, h, 2.0)
|
|
let live = 1.0 - dis
|
|
let t = clamp((p.y - lo.y) / max(2.0 * h.y, 1.0), 0.0, 1.0)
|
|
var face = fm_gun(t)
|
|
let upper = step(p.y, c.y)
|
|
let hh = mix(max(0.0, -hot), max(0.0, hot), upper) * live
|
|
let dn = mix(max(0.0, -down), max(0.0, down), upper) * live
|
|
face = face + g3(0.04 * hh * (1.0 - dn))
|
|
face = mix(face, vec3(0.118, 0.145, 0.169), dn)
|
|
face = mix(face, vec3(0.200, 0.227, 0.251), dis)
|
|
let mid = Finish.snap(o.y + c.y, px) - o.y
|
|
face = mix(face, fm_edge(), step(mid - px, p.y) * step(p.y, mid))
|
|
face = mix(face, vec3(0.45, 0.52, 0.57), step(mid, p.y) * step(p.y, mid + px) * live)
|
|
let n = fm_norm(p, c, h, 2.0)
|
|
face = Finish.lift(face, Finish.bevel(d + px, n, 1.0, fm_light()) * 0.4 * live)
|
|
face = mix(face, fm_edge(), Finish.band(d, 0.0, px, px))
|
|
let ink = mix(vec3(0.80, 0.85, 0.88), vec3(0.36, 0.40, 0.43), dis)
|
|
let cu = vec2(c.x, (lo.y + mid) * 0.5)
|
|
let cl = vec2(c.x, (mid + hi.y) * 0.5)
|
|
let chd = min(fm_chevron(p, cu, 5.0, 2.5, 1.0), fm_chevron(p, cl, 5.0, 2.5, 0.0)) - 0.65
|
|
face = mix(face, mix(ink, fm_core(), 0.6 * hh), Finish.cover(chd, px))
|
|
let a = Finish.cover(d, px)
|
|
return vec4(face * a, a)
|
|
}
|
|
mod.widgets.NumberSpin.width = 16.0
|
|
mod.widgets.NumberSpin.draw_text.color = #00000000
|
|
mod.widgets.NumberSpin.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
return fm_spin(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, self.hot, self.down, self.disabled)
|
|
}
|
|
// The number field's own stepper was built before this sheet ran.
|
|
mod.widgets.NumberField.spin_width = 16.0
|
|
mod.widgets.NumberField.spin.width = 16.0
|
|
mod.widgets.NumberField.spin.draw_text.color = #00000000
|
|
mod.widgets.NumberField.spin.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
return fm_spin(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, self.hot, self.down, self.disabled)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// DROP-DOWNS. The cap with an engraved split 22 points from its right end
|
|
// and a chevron behind it; open is pressed.
|
|
mod.widgets.DropDownFlat.padding = mod.turtle.Inset{left: 10.0 right: 30.0 top: 3.0 bottom: 3.0}
|
|
mod.widgets.DropDown.padding = mod.turtle.Inset{left: 10.0 right: 30.0 top: 3.0 bottom: 3.0}
|
|
mod.widgets.DropDownFlat.clip_x = false
|
|
mod.widgets.DropDownFlat.clip_y = false
|
|
mod.widgets.DropDownFlat.draw_bg.vertex = fn() {
|
|
let g = grow_vertex(self.rect_pos, self.rect_size, 8.0)
|
|
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
|
|
}
|
|
mod.widgets.DropDownFlat.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
return fm_dropdown(self.pos, self.rect_size, self.rect_pos, px, self.hover, max(self.down * self.hover, self.active), self.focus, self.disabled, 8.0)
|
|
}
|
|
mod.widgets.DropDownFlat.draw_text.text_style = mod.theme.font_regular{font_size: 8.5}
|
|
|
|
// The combo box: the well with a gunmetal key joined at its right end.
|
|
mod.widgets.ComboBox.padding = mod.turtle.Inset{left: 8.0 right: 26.0 top: 3.0 bottom: 3.0}
|
|
mod.widgets.ComboBox.input.min_height = 0.0
|
|
mod.widgets.ComboBox.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let size = self.rect_size
|
|
let o = self.rect_pos
|
|
let live = 1.0 - self.disabled
|
|
var out = fm_field(p, size, o, px, self.hover, 0.0, self.focus, self.disabled, 3.0)
|
|
let kx = Finish.snap(o.x + size.x - 21.0, px) - o.x
|
|
let hi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y, px)) - o - vec2(2.0 * px, 2.0 * px)
|
|
let klo = vec2(kx, Finish.snap(o.y, px) - o.y + 2.0 * px)
|
|
let held = self.down * self.hover * live
|
|
out = Finish.over(out, fm_cap(p, o, klo, hi, 2.0, px, self.hover * live * (1.0 - held), held, self.disabled, 0.0))
|
|
let kc = (klo + hi) * 0.5
|
|
let cc = vec2(kc.x, Finish.snap(o.y + kc.y, px) - o.y)
|
|
let ch = Finish.cover(fm_chevron(p, cc, 6.0, 3.5, 0.0) - 0.7, px)
|
|
let ink = mix(vec3(0.75, 0.80, 0.84), vec3(0.36, 0.40, 0.43), self.disabled)
|
|
return Finish.over(out, vec4(ink * ch, ch))
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// SEGMENTS AND TABS. The segmented control is a row of steel push buttons:
|
|
// the strip is one raised cap and the chosen answer is pushed in deep
|
|
// between its neighbours, its legend lit and a lamp strip glowing under it
|
|
// on the well's floor. The tab strip is engraved into the plate; the tab in
|
|
// use is a dark glass pane with a lit cyan line along its foot.
|
|
let fm_strip = fn(pos: vec2, size: vec2, o: vec2, px: float, m: float) -> vec4 {
|
|
return fm_button(pos, size, o, px, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 3.0, 8.0, m)
|
|
}
|
|
|
|
let fm_push_well = fn(p: vec2, size: vec2, o: vec2, px: float) -> vec4 {
|
|
let x0 = Finish.snap(o.x, px) - o.x
|
|
let x1 = Finish.snap(o.x + size.x, px) - o.x
|
|
let y0 = Finish.snap(o.y - 2.0, px) - o.y + px
|
|
let y1 = Finish.snap(o.y + size.y + 2.0, px) - o.y - px
|
|
let inside = step(x0, p.x) * (1.0 - step(x1, p.x)) * step(y0, p.y) * (1.0 - step(y1, p.y))
|
|
let t = clamp((p.y - y0) / max(y1 - y0, 1.0), 0.0, 1.0)
|
|
var col = mix(vec3(0.050, 0.170, 0.205), vec3(0.060, 0.215, 0.255), t)
|
|
col = col + g3(fm_grain((p + o) / px, 0.008))
|
|
// lit from within: the glass brightens toward its walls
|
|
let ew = min(min(p.x - x0, x1 - p.x), min(p.y - y0, y1 - p.y))
|
|
col = col + fm_cyan() * exp(-max(ew, 0.0) / 3.5) * 0.30
|
|
let top = exp(-max(p.y - y0, 0.0) / (2.4 * px))
|
|
let wl = 0.8 * exp(-max(p.x - x0, 0.0) / (1.5 * px))
|
|
let wr = 0.8 * exp(-max(x1 - px - p.x, 0.0) / (1.5 * px))
|
|
let foot = 0.45 * step(y1 - px, p.y)
|
|
let dk = 1.0 - (1.0 - top) * (1.0 - wl) * (1.0 - wr) * (1.0 - foot)
|
|
col = mix(col, vec3(0.008, 0.012, 0.020), clamp(dk, 0.0, 1.0))
|
|
// the lamp's light on the floor: a broad soft pool
|
|
let k = fm_cyan()
|
|
let lc = vec2(Finish.snap(o.x + (x0 + x1) * 0.5, px) - o.x, Finish.snap(o.y + size.y, px) - o.y - 4.0)
|
|
let qq = vec2((p.x - lc.x) / max((x1 - x0) * 0.30, 1.0), (p.y - lc.y) / 4.0)
|
|
let pool = exp(-dot(qq, qq)) * 0.45 * (1.0 - 0.6 * top)
|
|
col = col + k * pool
|
|
var res = vec4(col, 1.0) * inside
|
|
// the neighbours' ends over the well: one lit column each
|
|
let ey = step(y0, p.y) * (1.0 - step(y1 - px, p.y))
|
|
let el = step(x0 - px, p.x) * (1.0 - step(x0, p.x))
|
|
let er = step(x1, p.x) * (1.0 - step(x1 + px, p.x))
|
|
let lit = mix(vec3(0.52, 0.59, 0.64), vec3(0.28, 0.33, 0.36), t)
|
|
res = Finish.over(res, vec4(lit, 1.0) * (el + er) * ey)
|
|
// the lamp: a lit strip with a white-hot core
|
|
let hw = clamp((x1 - x0) * 0.32, 5.0, 14.0)
|
|
let ld = fm_rbox(p, lc, vec2(hw, px * 1.5), px * 1.5)
|
|
let gl = Finish.glow(max(ld, 0.0), 1.4) * step(0.0, ld) * 0.6
|
|
res = vec4(res.rgb + k * gl * inside, res.a)
|
|
res = Finish.over(res, fm_dash(p, lc, hw, px * 1.5, px, k, 1.0) * inside)
|
|
return res
|
|
}
|
|
|
|
mod.widgets.SegmentedControlFlat.draw_bg.vertex = fn() {
|
|
let g = grow_vertex(self.rect_pos, self.rect_size, 6.0)
|
|
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
|
|
}
|
|
mod.widgets.SegmentedControlFlat.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
return fm_strip(self.pos, self.rect_size, self.rect_pos, px, 6.0)
|
|
}
|
|
mod.widgets.SegmentedControlFlat.draw_pill.vertex = fn() {
|
|
let g = grow_vertex(self.rect_pos, self.rect_size, 6.0)
|
|
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
|
|
}
|
|
mod.widgets.SegmentedControlFlat.draw_pill.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let m = 6.0
|
|
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
return fm_push_well(p, self.rect_size, self.rect_pos, px)
|
|
}
|
|
mod.widgets.SegmentedControlFlat.draw_text.color = #eef3f6
|
|
mod.widgets.SegmentedControlFlat.draw_text_selected.color = #d6fbff
|
|
mod.widgets.SegmentedControlFlat.palette.on_neutral_container = #8ff0fb
|
|
|
|
// The tab strip.
|
|
mod.widgets.Tabs.height = 26.0
|
|
mod.widgets.Tabs.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let o = self.rect_pos
|
|
let yb = Finish.snap(o.y + self.rect_size.y, px) - o.y
|
|
let dk = step(yb - 2.0 * px, p.y) * step(p.y, yb - px) * 0.8
|
|
let lt = step(yb - px, p.y) * step(p.y, yb) * 0.14
|
|
let bz = fm_edge()
|
|
return Finish.over(vec4(bz * dk, dk), vec4(lt, lt, lt, lt))
|
|
}
|
|
mod.widgets.Tabs.draw_tab.pixel = fn() {
|
|
return vec4(0.0, 0.0, 0.0, 0.0)
|
|
}
|
|
mod.widgets.Tabs.draw_tab_hover.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let h = self.rect_size * 0.5 - vec2(px, 2.0 * px)
|
|
let d = fm_cbox(p, self.rect_size * 0.5 - vec2(0.0, px), h, 3.0)
|
|
let a = Finish.cover(d, px) * 0.07
|
|
return vec4(a, a, a, a)
|
|
}
|
|
mod.widgets.Tabs.draw_tab_active.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let o = self.rect_pos
|
|
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o
|
|
let hi = vec2(Finish.snap(o.x + self.rect_size.x, px), Finish.snap(o.y + self.rect_size.y, px)) - o - vec2(0.0, 2.0 * px)
|
|
let h = (hi - lo) * 0.5
|
|
let c = lo + h
|
|
let k = 3.0
|
|
let d = fm_cbox(p, c, h, k)
|
|
// dark glass sunk into the plate
|
|
let t = clamp((p.y - lo.y) / max(2.0 * h.y, 1.0), 0.0, 1.0)
|
|
var col = fm_well(p, c, h, k, px, mix(vec3(0.050, 0.165, 0.200), vec3(0.060, 0.215, 0.255), t), 2.2)
|
|
// lit from within: the glass brightens toward its walls and its foot
|
|
col = col + fm_cyan() * exp(-max(-d, 0.0) / 3.5) * 0.28 * Finish.cover(d, px)
|
|
let yb = Finish.snap(o.y + self.rect_size.y, px) - o.y
|
|
let rise = exp(-max(yb - 2.0 * px - p.y, 0.0) / 4.0) * 0.40
|
|
col = col + fm_cyan() * rise
|
|
let a = Finish.cover(d, px)
|
|
var out = vec4(col * a, a)
|
|
// the lit strip along the foot, two device pixels, white-hot in the middle
|
|
let inx = step(lo.x + k, p.x) * step(p.x, hi.x - k)
|
|
let ln = step(yb - 2.0 * px, p.y) * step(p.y, yb) * inx
|
|
let hot = mix(fm_core(), vec3(1.0, 1.0, 1.0), 0.4 * step(p.y, yb - px))
|
|
return Finish.over(out, vec4(hot * ln, ln))
|
|
}
|
|
mod.widgets.Tabs.draw_sep.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let x = self.pos.x * self.rect_size.x
|
|
let y = self.pos.y
|
|
let in_y = step(0.25, y) * step(y, 0.75)
|
|
let dk = step(x, px) * 0.9 * in_y
|
|
let lt = step(px, x) * step(x, 2.0 * px) * 0.14 * in_y
|
|
let bz = fm_edge()
|
|
return Finish.over(vec4(bz * dk, dk), vec4(lt, lt, lt, lt))
|
|
}
|
|
mod.widgets.Tabs.draw_mark.color = #e2e9ee
|
|
mod.widgets.Tabs.draw_mark.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let o = self.rect_pos
|
|
let c = vec2(Finish.snap(o.x + self.rect_size.x * 0.5, px), Finish.snap(o.y + self.rect_size.y * 0.5, px)) - o
|
|
let a = mix(0.7853981634, 0.0, self.plus)
|
|
let r = mix(3.6, 3.5, self.plus)
|
|
let u = vec2(cos(a), sin(a)) * r
|
|
let v = vec2(-sin(a), cos(a)) * r
|
|
let dd = min(fm_seg(p, c - u, c + u), fm_seg(p, c - v, c + v)) - 0.5
|
|
let k = Finish.cover(dd, px) * self.color.a
|
|
return vec4(self.color.rgb * k, k)
|
|
}
|
|
mod.widgets.Tabs.draw_text.text_style = mod.theme.font_bold{font_size: 8.25}
|
|
mod.widgets.Tabs.draw_text_active.text_style = mod.theme.font_bold{font_size: 8.25}
|
|
mod.widgets.Tabs.draw_text.color = #eef3f6
|
|
mod.widgets.Tabs.draw_text_active.color = #d6fbff
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// PROGRESS. The slider's groove, six points; the value is the lit cyan
|
|
// line with its light on the groove's floor. The unknown value cannot move
|
|
// (a face runs no clock), so it is shown as still dim dashes.
|
|
mod.widgets.ProgressBarFlat.height = 6.0
|
|
mod.widgets.ProgressBarFlat.draw_bg.thickness = 6.0
|
|
mod.widgets.ProgressBarFlat.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let o = self.rect_pos
|
|
let w = self.rect_size.x
|
|
let th = 9.0 * px
|
|
let top = Finish.snap(o.y + (self.rect_size.y - th) * 0.5, px) - o.y
|
|
let bx0 = Finish.snap(o.x, px) - o.x
|
|
let bx1 = Finish.snap(o.x + w, px) - o.x
|
|
let h = vec2((bx1 - bx0) * 0.5, th * 0.5)
|
|
let c = vec2(bx0, top) + h
|
|
let r = 1.5
|
|
let d = fm_cbox(p, c, h, r)
|
|
let dis = step(4.5, self.intent)
|
|
var ink = fm_cyan()
|
|
if self.intent > 0.5 {
|
|
ink = self.fill_color().rgb
|
|
}
|
|
ink = mix(ink, fm_off(), dis)
|
|
let sh = Finish.inset(p, c, h, r, vec2(0.0, 0.8), 1.0)
|
|
var col = mix(vec3(0.051, 0.071, 0.086), fm_edge(), clamp(sh, 0.0, 1.0) * 0.8)
|
|
col = mix(col, fm_edge(), Finish.band(d, 0.0, px, px) * 0.8)
|
|
let span = bx1 - bx0 - 4.0 * px
|
|
let lx0 = Finish.snap(o.x + bx0 + 2.0 * px + span * self.start, px) - o.x + px * step(0.001, self.start)
|
|
let lx1 = Finish.snap(o.x + bx0 + 2.0 * px + span * self.end, px) - o.x
|
|
let xv = Finish.snap(o.x + bx0 + 2.0 * px + span * clamp(self.value, self.start, self.end), px) - o.x
|
|
let lh = 1.5 * px
|
|
var ld = fm_rbox(p, vec2((lx0 + xv) * 0.5, c.y), vec2(max((xv - lx0) * 0.5, 0.0), lh), lh)
|
|
var on = step(0.0, self.value) * step(lx0 + 0.01, xv)
|
|
if self.value < 0.0 {
|
|
let sx = floor((p.x - lx0) / px)
|
|
let dashx = step(0.0, sx) * step(fract(sx / 16.0), 0.49) * step(p.x, lx1)
|
|
ld = mix(1000.0, abs(p.y - c.y) - lh, dashx)
|
|
on = 0.45
|
|
}
|
|
let across = clamp(abs(p.y - c.y) / lh, 0.0, 1.0)
|
|
let body = mix(mix(mix(vec3(1.0, 1.0, 1.0), ink, 0.45), ink, smoothstep(0.1, 1.0, across)), fm_off() * 1.3, dis)
|
|
let lg = Finish.glow(max(ld, 0.0), 1.2) * step(0.0, ld) * 0.8 * on * (1.0 - dis)
|
|
col = col + ink * lg
|
|
let la = Finish.cover(ld, px) * on
|
|
col = mix(col, body, la)
|
|
let a = Finish.cover(d, px) * self.track
|
|
var out = fm_lip(p, c, h, r, px, 0.16 * self.track)
|
|
out = Finish.over(out, vec4(col * a, a))
|
|
// a stacked segment (drawn with no track) shows only its line
|
|
let seg = (1.0 - self.track) * la
|
|
out = Finish.over(out, vec4(body * seg, seg))
|
|
return out * self.opacity
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// LEVEL METERS. A ladder of lamps in a dark well: cyan to -6 dB, amber to
|
|
// -2, red above, each lit lamp glowing on the well; unlit lamps keep a
|
|
// tenth of their light. The over lamp is dark until it latches.
|
|
mod.widgets.LevelMeter.height = 6.0
|
|
mod.widgets.LevelMeterColumn.width = 6.0
|
|
mod.widgets.LevelMeter.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let vert = self.vertical
|
|
let along = mix(self.rect_size.x, self.rect_size.y, vert)
|
|
let across = mix(self.rect_size.y, self.rect_size.x, vert)
|
|
let a = mix(p.x, self.rect_size.y - p.y, vert)
|
|
let b = mix(p.y, p.x, vert)
|
|
let q = vec2(a, b)
|
|
let lamp_room = self.lamp * (across + 3.0)
|
|
let bar = max(4.0, along - lamp_room)
|
|
let h = vec2(bar * 0.5, across * 0.5 - px * 0.5)
|
|
let c = h
|
|
let r = 1.5
|
|
let d = fm_rbox(q, c, h, r)
|
|
let pad = 1.5 * px
|
|
let span = max(1.0, bar - pad * 2.0)
|
|
let share = clamp((a - pad) / span, 0.0, 1.0)
|
|
let pitch = 4.0
|
|
let cell = fract((a - pad) / pitch)
|
|
let in_cell = step(cell, 3.0 / pitch)
|
|
let bt = b - (c.y - h.y)
|
|
let in_b = step(pad, bt) * step(bt, 2.0 * h.y - pad)
|
|
let cell_row = in_cell * in_b * step(pad, a) * step(a, pad + span)
|
|
let lit = step(a, pad + span * self.level) * cell_row
|
|
let zone = mix(mix(fm_cyan(), vec3(1.0, 0.690, 0.180), step(0.70, share)), vec3(1.0, 0.353, 0.392), step(0.88, share))
|
|
var col = mix(vec3(0.035, 0.047, 0.059), fm_edge(), 1.0 - smoothstep(0.0, 1.5, bt))
|
|
col = mix(col, fm_edge(), Finish.band(d, 0.0, px, px))
|
|
col = mix(col, mix(col, zone, 0.12), cell_row)
|
|
// the lit lamps' light between them
|
|
let lvl = step(a, pad + span * self.level + 0.5) * step(pad, a) * in_b
|
|
col = col + zone * lvl * 0.18
|
|
let hot = mix(zone, mix(zone, vec3(1.0, 1.0, 1.0), 0.45), 1.0 - smoothstep(0.0, 0.5, abs(bt - h.y) / max(h.y, 0.5)))
|
|
col = mix(col, hot, lit)
|
|
if self.hold > 0.002 {
|
|
let at = pad + span * self.hold
|
|
let hm = step(abs(a - at), 1.5) * in_b
|
|
col = mix(col, zone, hm)
|
|
}
|
|
let ca = Finish.cover(d, px)
|
|
var out = Finish.over(fm_lip(q, c, h, r, px, 0.16), vec4(col * ca, ca))
|
|
if self.lamp > 0.5 {
|
|
let la = bar + 3.0 + across * 0.5
|
|
let lb = across * 0.5
|
|
let lc = vec2(mix(la, lb, vert), mix(lb, self.rect_size.y - la, vert))
|
|
let lr = min(across * 0.5 - px, 3.0)
|
|
let ld = length(p - lc) - lr
|
|
let on = step(0.5, self.over)
|
|
let lcol = mix(vec3(0.20, 0.06, 0.08), mix(vec3(1.0, 0.353, 0.392), vec3(1.0, 0.85, 0.86), 0.3), on)
|
|
let laa = Finish.cover(ld, px)
|
|
out = Finish.over(out, vec4(lcol * laa, laa))
|
|
let ra = Finish.ring_out(ld, 0.0, px, px) * 0.8
|
|
out = Finish.over(out, vec4(fm_edge() * ra, ra))
|
|
}
|
|
return out * self.opacity
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// SCROLL BARS. An 8 point slot with a 6 point gunmetal thumb in it; a held
|
|
// thumb lights a cyan line.
|
|
mod.widgets.ScrollBar.bar_size = 8.0
|
|
mod.widgets.ScrollBar.bar_side_margin = 0.0
|
|
mod.widgets.ScrollBar.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let vert = self.is_vertical
|
|
let a = mix(p.x, p.y, vert)
|
|
let b = mix(p.y, p.x, vert)
|
|
let along = mix(self.rect_size.x, self.rect_size.y, vert)
|
|
let across = mix(self.rect_size.y, self.rect_size.x, vert)
|
|
let q = vec2(a, b)
|
|
let sh = vec2(along * 0.5, across * 0.5)
|
|
let sc = sh
|
|
let sd = fm_rbox(q, sc, sh, 2.0)
|
|
let wall = 1.0 - smoothstep(0.0, 2.0, b)
|
|
let col = mix(vec3(0.059, 0.078, 0.094), fm_edge(), wall * 0.8)
|
|
let sa = Finish.cover(sd, px)
|
|
var out = vec4(col * sa, sa)
|
|
let t0 = along * self.norm_scroll
|
|
let tl = along * self.norm_handle
|
|
let th = vec2(tl * 0.5 - 1.0, 3.0)
|
|
let tc = vec2(t0 + tl * 0.5, across * 0.5)
|
|
let td = fm_cbox(q, tc, th, 1.5)
|
|
let tt = clamp((b - (tc.y - th.y)) / 6.0, 0.0, 1.0)
|
|
var face = mix(vec3(0.360, 0.420, 0.460), vec3(0.200, 0.240, 0.270), tt)
|
|
face = face + g3(0.05 * self.hover)
|
|
face = mix(face, vec3(0.60, 0.67, 0.71), Finish.band(td, 0.0, px, px) * (1.0 - smoothstep(0.3, 0.6, tt)))
|
|
face = mix(face, fm_edge(), Finish.band(td, 0.0, px, px) * smoothstep(0.4, 0.7, tt))
|
|
let cl = (1.0 - smoothstep(px * 0.5 - 0.01, px * 0.5 + 0.01, abs(b - tc.y))) * step(abs(a - tc.x), th.x - 3.0)
|
|
face = mix(face, fm_core(), cl * self.drag * 0.8)
|
|
let ta = Finish.cover(td, px)
|
|
out = Finish.over(out, vec4(face * ta, ta))
|
|
return out * self.opacity
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// VIEWS AND PANELS. A raised plate is brushed steel with its top-left and
|
|
// bottom-right corners cut: a soft shadow outside it, a black edge, a lit
|
|
// shoulder, a gloss along its top, an engraved panel line four points in
|
|
// and a cyan trace lit in that groove along the top, turning down the cut
|
|
// corner and ending in a node. The sunken panel is a pocket cut the same
|
|
// shape: a dark floor, the plate's shadow falling in, a steel lip at its
|
|
// foot and a dim trace glowing along the groove at its foot.
|
|
let fm_plate = fn(p: vec2, size: vec2, o: vec2, px: float, cut: float, sunken: float, alpha: float) -> vec4 {
|
|
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o
|
|
let hi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y, px)) - o
|
|
let h = (hi - lo) * 0.5
|
|
let c = lo + h
|
|
let kk = clamp(cut, 0.0, min(h.x, h.y) * 0.6)
|
|
let cuts = vec4(kk, 2.0, kk, 2.0)
|
|
let d = Finish.sd_notch(p, c, h, cuts)
|
|
let e = 0.35
|
|
let gx = Finish.sd_notch(p + vec2(e, 0.0), c, h, cuts) - Finish.sd_notch(p - vec2(e, 0.0), c, h, cuts)
|
|
let gy = Finish.sd_notch(p + vec2(0.0, e), c, h, cuts) - Finish.sd_notch(p - vec2(0.0, e), c, h, cuts)
|
|
let n = vec2(gx, gy) / max(length(vec2(gx, gy)), 0.0001)
|
|
let ink = fm_ink()
|
|
var out = vec4(0.0, 0.0, 0.0, 0.0)
|
|
let raised = 1.0 - sunken
|
|
let soft = Finish.drop(p, c, h, 3.0, vec2(0.0, 3.0), 3.0) * 0.6 * raised
|
|
out = Finish.over(out, vec4(ink * soft, soft))
|
|
let con = Finish.drop(p, c, h, 3.0, vec2(0.0, px), px) * 0.7
|
|
out = Finish.over(out, vec4(ink * con, con))
|
|
// round a pocket: the plate's lit lip at its foot, its shade at its top
|
|
let lipo = Finish.ring_out(d, 0.0, 2.0 * px, px) * clamp(n.y, 0.0, 1.0) * 0.55 * sunken
|
|
out = Finish.over(out, vec4(vec3(0.85, 0.90, 0.93) * lipo, lipo))
|
|
let t = clamp((p.y - lo.y) / max(2.0 * h.y, 1.0), 0.0, 1.0)
|
|
let yi = p.y - lo.y
|
|
let xi = (p.x - lo.x) / max(2.0 * h.x, 1.0)
|
|
let q = p + o
|
|
// raised: polished steel. A bright gloss over the top eight points, the
|
|
// upper part mid steel brightening toward the right where no text sits,
|
|
// a sharp reflection line, the lower part dark steel lifting a little at
|
|
// the foot.
|
|
let split = floor((lo.y + min(33.0, 2.0 * h.y * 0.55)) / px) * px
|
|
let upper = step(p.y, split)
|
|
var face = mix(vec3(0.365, 0.414, 0.450), vec3(0.340, 0.388, 0.424), clamp(yi / max(split - lo.y, 1.0), 0.0, 1.0))
|
|
let gl = 1.0 - smoothstep(0.0, 5.5, yi)
|
|
face = face + vec3(0.92, 0.96, 1.0) * (0.36 * gl * gl)
|
|
let rt = smoothstep(0.32, 0.92, xi + 0.25 * (1.0 - yi / max(split - lo.y, 1.0)))
|
|
face = face + vec3(0.92, 0.96, 1.0) * 0.40 * rt * rt
|
|
var low = mix(vec3(0.215, 0.250, 0.278), vec3(0.160, 0.188, 0.212), smoothstep(split, hi.y - 6.0, p.y))
|
|
low = low + g3(0.05 * smoothstep(hi.y - 12.0, hi.y - 2.0, p.y))
|
|
low = low + g3(0.07 * exp(-max(p.y - split, 0.0) / 3.0))
|
|
face = mix(low, face, upper)
|
|
face = mix(face, vec3(0.88, 0.93, 0.96), fm_hl(p.y, split + px * 0.5, px, px) * 0.85)
|
|
face = face + g3(fm_brush(q, px) * 0.03 + fm_grain(q / px, 0.010))
|
|
// sunken: a dark pocket, the shade of its top wall, a bus of lit traces
|
|
// on its floor kept clear of the caption and body at its top left
|
|
var fl = mix(vec3(0.071, 0.090, 0.106), vec3(0.094, 0.118, 0.137), t)
|
|
fl = fl + g3(fm_brush(q, px) * 0.012 + fm_grain(q / px, 0.008))
|
|
if sunken > 0.5 {
|
|
// a circuit seen through a pane, anchored to the panel itself; its
|
|
// lit traces calm under the caption and body at the top left
|
|
let bq = p - lo
|
|
let calm = 1.0 - max(smoothstep(58.0, 70.0, yi), smoothstep(0.58, 0.72, xi))
|
|
let ru = (xi * 0.8 + t) / 1.8
|
|
let r1 = (ru - 0.62) / 0.05
|
|
let r2 = (ru - 0.70) / 0.012
|
|
var b = fm_board(bq + vec2(41.0, 23.0), px, 2.2, calm * 0.85, 0.3)
|
|
b = fm_pane(b, q, px, exp(-r1 * r1) * 0.040 + exp(-r2 * r2) * 0.030, 0.30)
|
|
fl = mix(b, b * 0.72, calm * 0.6)
|
|
// the pane's rim, three points in, bright along its top
|
|
fl = mix(fl, vec3(0.80, 0.88, 0.92), Finish.band(d, 3.0, 3.0 + px, px) * (0.2 + 0.5 * clamp(-n.y, 0.0, 1.0)))
|
|
}
|
|
let insh = Finish.inset(p, c, h, 3.0, vec2(0.0, 2.5), 3.0)
|
|
fl = mix(fl, vec3(0.012, 0.016, 0.024), clamp(insh, 0.0, 1.0) * 0.85)
|
|
face = mix(face, fl, sunken)
|
|
// the rim: a bevel three points wide, lit toward the light
|
|
let sh = Finish.bevel(d + px, n, 3.0, fm_light())
|
|
face = Finish.lift(face, clamp(sh, -1.0, 1.0) * mix(0.55, 0.45, step(sh, 0.0)) * raised)
|
|
face = mix(face, vec3(0.45, 0.51, 0.55), Finish.band(d, px, 2.0 * px, px) * clamp(n.y, 0.0, 1.0) * sunken * 0.6)
|
|
face = mix(face, fm_edge(), Finish.band(d, 0.0, px, px))
|
|
let a = Finish.cover(d, px) * alpha
|
|
return Finish.over(out, vec4(face * a, a))
|
|
}
|
|
|
|
mod.widgets.PanelView.clip_x = false
|
|
mod.widgets.PanelView.clip_y = false
|
|
mod.widgets.PanelView.draw_bg.vertex = fn() {
|
|
let g = grow_vertex(self.rect_pos, self.rect_size, 8.0)
|
|
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
|
|
}
|
|
mod.widgets.PanelView.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let m = 8.0
|
|
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
return fm_plate(p, self.rect_size, self.rect_pos, px, 9.0, self.sunken, 1.0)
|
|
}
|
|
|
|
// The card is the raised plate too.
|
|
mod.widgets.Card.radius = 4.0
|
|
mod.widgets.Card.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 9.0 bottom: 9.0}
|
|
mod.widgets.Card.draw_bg.vertex = fn() {
|
|
let g = grow_vertex(self.rect_pos, self.rect_size, 8.0)
|
|
return self.clip_and_transform_vertex(g.xy, g.zw)
|
|
}
|
|
mod.widgets.Card.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let m = 8.0
|
|
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
let out = fm_plate(p, self.rect_size, self.rect_pos, px, 9.0, 0.0, 1.0)
|
|
let d = fm_rbox(p, self.rect_size * 0.5, self.rect_size * 0.5, 2.0)
|
|
let l = Finish.cover(d, px) * (0.03 * self.hover - 0.04 * self.down)
|
|
return Finish.over(out, vec4(max(l, 0.0), max(l, 0.0), max(l, 0.0), abs(l)))
|
|
}
|
|
|
|
// The plain rounded view keeps the colours its caller gives it; the edge is
|
|
// a snapped hairline.
|
|
mod.widgets.RoundedView.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
var fill = self.color
|
|
if self.color_2.x > -0.5 {
|
|
fill = mix(self.color, self.color_2, mix(self.pos.y, self.pos.x, self.gradient_fill_horizontal))
|
|
}
|
|
let lo = vec2(self.border_inset.x, self.border_inset.y)
|
|
let hi = self.rect_size - vec2(self.border_inset.z, self.border_inset.w)
|
|
let h = (hi - lo) * 0.5
|
|
let c = lo + h
|
|
let d = fm_rbox(p, c, h, max(self.border_radius, 1.0))
|
|
var col = fill
|
|
if self.border_size > 0.0 {
|
|
col = mix(col, self.border_color, Finish.band(d, 0.0, max(self.border_size, px), px) * self.border_color.a)
|
|
}
|
|
let a = Finish.cover(d, px) * col.a
|
|
return vec4(col.rgb * a, a)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// LISTS. Rows of 22 points over a hairline; a hovered row lifts a little;
|
|
// the chosen row is a pane of dark glass with a lit cyan bar at its head
|
|
// and a small lamp at its end.
|
|
mod.widgets.ListItemOne.height = 22.0
|
|
mod.widgets.ListItem.padding = mod.turtle.Inset{left: 12.0 right: 8.0 top: 0.0 bottom: 0.0}
|
|
mod.widgets.ListItem.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let o = self.rect_pos
|
|
let live = 1.0 - self.disabled
|
|
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o
|
|
let hi = vec2(Finish.snap(o.x + self.rect_size.x, px), Finish.snap(o.y + self.rect_size.y, px)) - o
|
|
let lay = (self.hover * 0.035 - self.down * 0.04) * live
|
|
// every row lies on a pane of dark glass, so what runs in the ground
|
|
// under it stays quiet behind its text
|
|
let ga = 0.62
|
|
var out = vec4(vec3(0.035, 0.047, 0.059) * ga, ga)
|
|
out = Finish.over(out, vec4(max(lay, 0.0), max(lay, 0.0), max(lay, 0.0), abs(lay)))
|
|
if self.selected > 0.5 {
|
|
// it ends 10 points short of the row's right edge, clear of the
|
|
// 8 point scroll bar
|
|
let h = (hi - lo) * 0.5 - vec2(5.0, 1.0)
|
|
let c = lo + (hi - lo) * 0.5 - vec2(5.0, 0.0)
|
|
let d = fm_cbox(p, c, h, 2.5)
|
|
let t = clamp((p.y - (c.y - h.y)) / max(2.0 * h.y, 1.0), 0.0, 1.0)
|
|
var col = mix(vec3(0.071, 0.110, 0.133), vec3(0.059, 0.133, 0.161), t)
|
|
col = mix(col, fm_edge(), Finish.band(d, 0.0, px, px) * 0.8)
|
|
let up = 1.0 - smoothstep(-0.5, 0.5, p.y - c.y)
|
|
col = mix(col, vec3(0.30, 0.46, 0.52), Finish.band(d, px, 2.0 * px, px) * up * 0.5)
|
|
let bx = c.x - h.x + 2.0 * px
|
|
let bar = step(bx, p.x) * step(p.x, bx + 2.0 * px) * step(c.y - h.y + 4.0, p.y) * step(p.y, c.y + h.y - 4.0)
|
|
let bg = Finish.glow(max(abs(p.x - bx - px) - px, 0.0), 1.5) * step(abs(p.y - c.y), h.y - 3.0) * 0.5 * live
|
|
col = col + fm_cyan() * bg
|
|
col = mix(col, fm_core(), bar * live)
|
|
// the lamp at its end
|
|
let lc = vec2(c.x + h.x - 7.0, c.y)
|
|
let ld = fm_rbox(p, lc, vec2(1.8, 1.8), 0.4)
|
|
let lg = Finish.glow(max(ld, 0.0), 1.2) * step(0.0, ld) * 0.6 * live
|
|
col = col + fm_cyan() * lg
|
|
col = mix(col, mix(fm_core(), vec3(1.0, 1.0, 1.0), 0.3), Finish.cover(ld, px) * live)
|
|
let a = Finish.cover(d, px)
|
|
out = Finish.over(out, vec4(col * a, a))
|
|
}
|
|
let yb = hi.y
|
|
let dk = step(yb - 2.0 * px, p.y) * step(p.y, yb - px) * 0.18
|
|
let lt = step(yb - px, p.y) * step(p.y, yb) * 0.06
|
|
out = Finish.over(out, vec4(0.0, 0.0, 0.0, dk))
|
|
return Finish.over(out, vec4(lt, lt, lt, lt))
|
|
}
|
|
mod.widgets.ListItem.draw_text.color = #f2f6f9
|
|
|
|
// The open list of a drop-down: a raised steel sheet; the row under the
|
|
// pointer becomes dark glass with a cyan bar at its head, the chosen row
|
|
// carries a small lit dot.
|
|
mod.widgets.PopupMenuFlat.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let c = self.rect_size * 0.5
|
|
let d = fm_cbox(p, c, c, 3.0)
|
|
let t = clamp(self.pos.y, 0.0, 1.0)
|
|
var col = mix(vec3(0.200, 0.235, 0.263), vec3(0.157, 0.188, 0.212), t)
|
|
col = col + g3(fm_brush(p + self.rect_pos, px) * 0.02)
|
|
col = mix(col, vec3(0.45, 0.52, 0.57), Finish.band(d, px, 2.0 * px, px) * step(p.y, c.y) * 0.6)
|
|
col = mix(col, fm_edge(), Finish.band(d, 0.0, px, px))
|
|
let a = Finish.cover(d, px)
|
|
return vec4(col * a, a)
|
|
}
|
|
mod.widgets.PopupMenuItem.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let c = self.rect_size * 0.5
|
|
let h = c - vec2(1.0, 0.5)
|
|
let d = fm_cbox(p, c, h, 2.0)
|
|
let live = 1.0 - self.disabled
|
|
let hv = self.hover * live
|
|
let col = fm_well(p, c, h, 2.0, px, vec3(0.059, 0.098, 0.118), 2.0)
|
|
let a = Finish.cover(d, px) * hv
|
|
var out = vec4(col * a, a)
|
|
let bar = step(1.0 + px, p.x) * step(p.x, 1.0 + 3.0 * px) * step(4.0, p.y) * step(p.y, self.rect_size.y - 4.0) * hv
|
|
let k = fm_core()
|
|
out = Finish.over(out, vec4(k * bar, bar))
|
|
let dt = Finish.cover(fm_rbox(p, vec2(6.0, c.y), vec2(1.3, 1.3), 0.4), px) * self.active
|
|
return Finish.over(out, vec4(k * dt, dt))
|
|
}
|
|
|
|
// The dock's tabs: the bar is a darker strip with an engraved foot; the tab
|
|
// in use is the plate's face, cut at its top corners, lit along its top.
|
|
mod.widgets.Tab.padding = mod.turtle.Inset{left: 10.0 right: 10.0 top: 3.0 bottom: 3.0}
|
|
mod.widgets.Tab.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let o = self.rect_pos
|
|
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y + 2.0, px)) - o
|
|
let hi = vec2(Finish.snap(o.x + self.rect_size.x, px), Finish.snap(o.y + self.rect_size.y, px)) - o + vec2(0.0, 4.0)
|
|
let h = (hi - lo) * 0.5
|
|
let c = lo + h
|
|
let d = fm_cbox(p, c, h, 3.0)
|
|
var face = vec3(0.235, 0.278, 0.310) + g3(fm_brush(p + o, px) * 0.02)
|
|
face = mix(face, vec3(0.50, 0.57, 0.61), Finish.band(d, px, 2.0 * px, px) * step(p.y, lo.y + 3.0))
|
|
face = mix(face, fm_edge(), Finish.band(d, 0.0, px, px))
|
|
let tl = step(lo.y + px, p.y) * step(p.y, lo.y + 3.0 * px) * step(lo.x + 4.0, p.x) * step(p.x, hi.x - 4.0)
|
|
face = mix(face, fm_core(), tl * 0.9)
|
|
let a = Finish.cover(d, px) * self.active
|
|
let lift = Finish.cover(d, px) * 0.05 * self.hover * (1.0 - self.active)
|
|
return Finish.over(vec4(lift, lift, lift, lift), vec4(face * a, a))
|
|
}
|
|
mod.widgets.TabBar.draw_fill.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let yb = Finish.snap(self.rect_pos.y + self.rect_size.y, px) - self.rect_pos.y
|
|
var out = vec4(0.0, 0.0, 0.0, 0.22)
|
|
let dk = step(yb - 2.0 * px, p.y) * step(p.y, yb - px) * 0.7
|
|
let lt = step(yb - px, p.y) * step(p.y, yb) * 0.12
|
|
out = Finish.over(out, vec4(0.0, 0.0, 0.0, dk))
|
|
return Finish.over(out, vec4(lt, lt, lt, lt))
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// CHIPS AND BADGES. A chip is the 20 point steel cap; a chosen chip is lit
|
|
// in its colour. A badge is a small pane of dark glass with a lit count.
|
|
mod.widgets.ChipFlat.size = mod.widgets.ChipSize.Small
|
|
mod.widgets.Chip.size = mod.widgets.ChipSize.Small
|
|
mod.widgets.ChipFlat.draw_bg.vertex = fn() {
|
|
let g = grow_vertex(self.rect_pos, self.rect_size, 8.0)
|
|
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
|
|
}
|
|
mod.widgets.ChipFlat.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let m = 8.0
|
|
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
let o = self.rect_pos
|
|
let ch = min(22.0, self.rect_size.y)
|
|
let dy = (self.rect_size.y - ch) * 0.5
|
|
let sz = vec2(self.rect_size.x, ch)
|
|
let pp = (p - vec2(0.0, dy) + vec2(m, m)) / (sz + vec2(2.0 * m, 2.0 * m))
|
|
let chosen = step(0.08, max(max(self.color.r, self.color.g), self.color.b) - min(min(self.color.r, self.color.g), self.color.b)) * step(0.5, self.color.a)
|
|
let lw = vec3(0.2126, 0.7152, 0.0722)
|
|
let pale = step(dot(self.ink.rgb, lw), 0.35)
|
|
let held = step(0.45, dot(self.color.rgb, lw)) * step(0.5, self.color.a) * (1.0 - pale) * (1.0 - chosen)
|
|
if max(chosen, pale) < 0.5 {
|
|
return fm_button(pp, sz, o + vec2(0.0, dy), px, self.hover, max(self.down, held), self.keyed, 0.0, 0.0, 0.0, 2.5, 8.0, m)
|
|
}
|
|
let q = p - vec2(0.0, dy)
|
|
let oo = o + vec2(0.0, dy)
|
|
let lo = vec2(Finish.snap(oo.x, px), Finish.snap(oo.y, px)) - oo
|
|
let hi = vec2(Finish.snap(oo.x + sz.x, px), Finish.snap(oo.y + sz.y, px)) - oo
|
|
let h = (hi - lo) * 0.5
|
|
let c = lo + h
|
|
let d = fm_cbox(q, c, h, 2.5)
|
|
let t = clamp((q.y - lo.y) / max(2.0 * h.y, 1.0), 0.0, 1.0)
|
|
let yi = q.y - lo.y - 2.0 * px
|
|
let k = mix(self.color.rgb, vec3(0.62, 0.66, 0.70), pale * (1.0 - chosen))
|
|
var face = mix(k, mix(k, vec3(1.0, 1.0, 1.0), 0.3), smoothstep(0.45, 1.0, t))
|
|
face = mix(k * 0.25, face, smoothstep(0.0, 3.0, yi))
|
|
face = face * (1.0 + 0.08 * self.hover - 0.12 * self.down)
|
|
face = mix(face, k * 0.2, Finish.band(d, 0.0, 2.0 * px, px))
|
|
let ha = Finish.glow(max(d, 0.0), 1.2) * step(0.0, d) * 0.35 * chosen
|
|
var out = vec4(k * ha, ha)
|
|
let a = Finish.cover(d, px)
|
|
out = Finish.over(out, vec4(face * a, a))
|
|
return Finish.over(out, fm_focus(d, px, self.keyed))
|
|
}
|
|
mod.widgets.BadgePalette.neutral = #10171d
|
|
mod.widgets.BadgePalette.on_neutral = #72f0fb
|
|
mod.widgets.BadgeFlat.draw_text.text_style = mod.theme.font_bold{font_size: 6.75}
|
|
mod.widgets.Badge.draw_text.text_style = mod.theme.font_bold{font_size: 6.75}
|
|
mod.widgets.BadgeFlat.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let o = self.rect_pos
|
|
let bh = min(16.0, self.rect_size.y)
|
|
let y0 = Finish.snap(o.y + (self.rect_size.y - bh) * 0.5, px) - o.y
|
|
let lo = vec2(Finish.snap(o.x, px) - o.x, y0)
|
|
let hi = vec2(Finish.snap(o.x + self.rect_size.x, px) - o.x, Finish.snap(o.y + y0 + bh, px) - o.y - px)
|
|
let h = (hi - lo) * 0.5
|
|
let c = lo + h
|
|
// a dot (as wide as tall and small) is round; a count is a pane
|
|
let dotty = step(self.rect_size.x, self.rect_size.y + 0.5) * step(self.rect_size.y, 12.5)
|
|
var d = fm_cbox(p, c, h, 2.0)
|
|
if dotty > 0.5 {
|
|
d = length(p - c) - h.y
|
|
}
|
|
var col = fm_well(p, c, h, 2.0, px, vec3(0.039, 0.063, 0.078), 2.0)
|
|
col = mix(col, fm_dim(), Finish.band(d, px, 2.0 * px, px) * 0.8)
|
|
let a = Finish.cover(d, px) * self.color.a
|
|
var out = fm_lip(p, c, h, 2.0, px, 0.16)
|
|
return Finish.over(out, vec4(col * a, a))
|
|
}
|
|
|
|
// The spinner turns in the one cyan.
|
|
mod.widgets.SpinnerFlat.draw_bg.color = #30c8e2
|
|
|
|
// Rules between groups: one dark device row over one light one, engraved.
|
|
mod.widgets.Hr.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let y = Finish.snap(self.rect_pos.y + self.rect_size.y * 0.5, px) - self.rect_pos.y
|
|
let dk = step(y - px, p.y) * (1.0 - step(y, p.y)) * 0.55
|
|
let lt = step(y, p.y) * (1.0 - step(y + px, p.y)) * 0.14
|
|
return Finish.over(vec4(0.0, 0.0, 0.0, dk), vec4(lt, lt, lt, lt))
|
|
}
|
|
mod.widgets.Vr.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let x = Finish.snap(self.rect_pos.x + self.rect_size.x * 0.5, px) - self.rect_pos.x
|
|
let dk = step(x - px, p.x) * (1.0 - step(x, p.x)) * 0.55
|
|
let lt = step(x, p.x) * (1.0 - step(x + px, p.x)) * 0.14
|
|
return Finish.over(vec4(0.0, 0.0, 0.0, dk), vec4(lt, lt, lt, lt))
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// THE HULL under everything: the window's own ground, in window points and
|
|
// anchored to the window's edges only, so it fits any page. The middle is calm
|
|
// brushed steel. Along the left edge, the foot and the right edge a window is
|
|
// cut into the steel, stepped out at 45 degrees where it widens: a seam round
|
|
// it, a lit chamfer stepping down to a ledge, a dark wall, then a pane of
|
|
// frosted blue-white plastic with a bright bevelled rim, lying in the frame's
|
|
// shadow; through it the circuit board glows.
|
|
|
|
// The plate's own steel at window point q: a top-lit fall down the window,
|
|
// brushed streaks and grain.
|
|
let fm_plate_steel = fn(q: vec2, top: float, bot: float, tone: float, px: float) -> vec3 {
|
|
let t = clamp((q.y - top) / max(bot - top, 1.0), 0.0, 1.0)
|
|
var col = mix(vec3(0.352, 0.402, 0.440), vec3(0.318, 0.364, 0.398), t)
|
|
col = col + g3(tone)
|
|
// a broad soft highlight across the plate, low and wide
|
|
let w = q.x / 900.0 - t * 0.35 - 0.45
|
|
col = col + vec3(0.9, 0.95, 1.0) * exp(-(w * w) / 0.05) * 0.025
|
|
col = col + g3(fm_brush(q, px) * 0.05 + fm_grain(q / px, 0.012))
|
|
return col
|
|
}
|
|
|
|
// A box lo..hi with its corners cut (tl, tr, br, bl).
|
|
let fm_nb = fn(q: vec2, lo: vec2, hi: vec2, k: vec4) -> float {
|
|
return Finish.sd_notch(q, (lo + hi) * 0.5, (hi - lo) * 0.5, k)
|
|
}
|
|
|
|
// The window cut into the hull, negative inside: a narrow strip down the left
|
|
// edge that widens part way down, one down the right edge that widens lower,
|
|
// and a low band along the foot whose top steps down right of the middle. All
|
|
// keep to the window's outer margin, where the least text lies.
|
|
let fm_win_sd = fn(q: vec2, size: vec2) -> float {
|
|
let m = 8.0
|
|
let w = size.x
|
|
let h = size.y
|
|
let lw = 32.0
|
|
let rw = 32.0
|
|
let bh = 50.0
|
|
let yt = clamp(h * 0.14, 60.0, 140.0)
|
|
let yl = mix(yt, h - bh, 0.40)
|
|
let yr = mix(yt, h - bh, 0.64)
|
|
let l1 = fm_nb(q, vec2(m, yt), vec2(m + lw * 0.62, h - m), vec4(7.0, 5.0, 0.0, 12.0))
|
|
let l2 = fm_nb(q, vec2(m, yl), vec2(m + lw, h - m), vec4(0.0, 8.0, 0.0, 12.0))
|
|
let r1 = fm_nb(q, vec2(w - m - rw * 0.62, mix(yt, h - bh, 0.18)), vec2(w - m, h - m), vec4(5.0, 7.0, 12.0, 0.0))
|
|
let r2 = fm_nb(q, vec2(w - m - rw, yr), vec2(w - m, h - m), vec4(8.0, 0.0, 12.0, 0.0))
|
|
let b1 = fm_nb(q, vec2(m, h - bh), vec2(w * 0.42, h - m), vec4(0.0, 0.0, 0.0, 12.0))
|
|
let b2 = fm_nb(q, vec2(w * 0.30, h - bh + 12.0), vec2(w - m, h - m), vec4(0.0, 0.0, 12.0, 0.0))
|
|
let l = fm_uc(l1, l2, 12.0)
|
|
let r = fm_uc(r1, r2, 12.0)
|
|
let b = fm_uc(b1, b2, 18.0)
|
|
return fm_uc(fm_uc(l, b, 16.0), r, 16.0)
|
|
}
|
|
|
|
// Relative luminance of a shader colour (gamma 2.2 decode).
|
|
let fm_lum = fn(c: vec3) -> float {
|
|
return 0.2126 * pow(max(c.x, 0.0), 2.2) + 0.7152 * pow(max(c.y, 0.0), 2.2) + 0.0722 * pow(max(c.z, 0.0), 2.2)
|
|
}
|
|
|
|
// Smoked plastic: what shows through keeps its hue and its order of
|
|
// brightness, but its luminance rolls off softly toward `lm` and never
|
|
// passes it, so light text over the brightest trace keeps its contrast.
|
|
let fm_smoke = fn(c: vec3, lm: float) -> vec3 {
|
|
let l = max(fm_lum(c), 0.00001)
|
|
let kn = lm * 0.55
|
|
let lo = min(l, kn + (lm - kn) * (1.0 - exp(-max(l - kn, 0.0) / (lm - kn))))
|
|
return c * pow(lo / l, 1.0 / 2.2)
|
|
}
|
|
|
|
let fm_hull = fn(q: vec2, size: vec2, px: float) -> vec3 {
|
|
var col = fm_plate_steel(q, 0.0, size.y, 0.0, px)
|
|
let d = fm_win_sd(q, size)
|
|
if d > 24.0 {
|
|
return col
|
|
}
|
|
let e = 0.5
|
|
let gx = fm_win_sd(q + vec2(e, 0.0), size) - fm_win_sd(q - vec2(e, 0.0), size)
|
|
let gy = fm_win_sd(q + vec2(0.0, e), size) - fm_win_sd(q - vec2(0.0, e), size)
|
|
let n = vec2(gx, gy) / max(length(vec2(gx, gy)), 0.0001)
|
|
// light on a wall that falls toward the pane: the lower walls face it
|
|
let fw = clamp(dot(-n, fm_light()), -1.0, 1.0)
|
|
// an engraved seam 16 points out; the plate inside it sits a hair lower
|
|
col = mix(col, col * 0.93, step(d, 16.0))
|
|
col = mix(col, fm_edge(), fm_hl(d, 16.0 - px * 0.5, px, px) * 0.7)
|
|
col = mix(col, vec3(0.80, 0.86, 0.90), fm_hl(d, 16.0 + px * 0.5, px, px) * 0.45)
|
|
// the outer chamfer, 8..4 points out, lit where it faces the light
|
|
let ch = step(4.0, d) * step(d, 8.0)
|
|
// held at the steel's own brightness, so text crossing it keeps its
|
|
// contrast; the light is in the hairline along its lip
|
|
let chc = mix(vec3(0.150, 0.180, 0.200), vec3(0.470, 0.530, 0.570), clamp(fw * 0.6 + 0.45, 0.0, 1.0))
|
|
col = mix(col, chc + g3(fm_brush(q, px) * 0.03), ch)
|
|
col = mix(col, vec3(0.95, 0.98, 1.0), fm_hl(d, 8.0 - px * 0.5, px, px) * 0.6)
|
|
// the ledge, 4..1.8 out, darker steel, with a lit slot along stretches
|
|
let le = step(1.8, d) * step(d, 4.0)
|
|
col = mix(col, vec3(0.170, 0.205, 0.230) + g3(fm_brush(q, px) * 0.03), le)
|
|
let along = mix(q.y, q.x, step(0.7071, abs(n.y)))
|
|
let sg = floor(along / 150.0)
|
|
let sl = along - sg * 150.0
|
|
let lit = step(0.45, Finish.hash(vec2(sg, 7.7 + step(0.7071, abs(n.y)) * 3.0))) * step(24.0, sl) * step(sl, 104.0)
|
|
let slot = step(2.4, d) * step(d, 3.4)
|
|
col = col + fm_cyan() * Finish.glow(max(abs(d - 2.9) - 0.5, 0.0), 0.9) * lit * 0.45 * step(1.8, d) * step(d, 4.0) * (1.0 - slot)
|
|
col = mix(col, mix(vec3(0.020, 0.030, 0.040), mix(fm_cyan(), fm_core(), 0.6 * fm_hl(d, 2.9, 0.5, px)), lit), slot)
|
|
// the inner wall, 1.8..0 out, dark and shaded
|
|
let wl = step(0.0, d) * step(d, 1.8)
|
|
col = mix(col, mix(vec3(0.030, 0.040, 0.050), vec3(0.230, 0.270, 0.300), clamp(fw * 0.5 + 0.3, 0.0, 1.0)), wl)
|
|
// text runs over the frame as well: its lit chamfer, hairlines and slot
|
|
// are held to the steel's own brightness, except at the window's edge
|
|
let eo = min(min(q.x, size.x - q.x), size.y - q.y)
|
|
col = mix(col, fm_smoke(col, mix(0.30, 0.095, smoothstep(1.5, 3.0, eo))), step(0.0, d) * step(d, 16.0))
|
|
if d < 0.0 {
|
|
// the pane
|
|
let refu = (q.x * 0.8 + q.y) / max(size.x * 0.8 + size.y, 1.0)
|
|
let r1 = (refu - 0.30) / 0.06
|
|
let r2 = (refu - 0.52) / 0.05
|
|
let r3 = (refu - 0.565) / 0.008
|
|
let r4 = (refu - 0.80) / 0.05
|
|
let r5 = (refu - 0.842) / 0.007
|
|
let refl = exp(-r1 * r1) * 0.065 + exp(-r2 * r2) * 0.070 + exp(-r3 * r3) * 0.060 + exp(-r4 * r4) * 0.065 + exp(-r5 * r5) * 0.060
|
|
// smoked plastic: the board seen crisp and dark, a faint grey-blue
|
|
// body to the pane and its reflections
|
|
let fz = Finish.noise(q / 70.0 + vec2(3.3, 8.1)) - 0.5
|
|
// (the smoke passes a little over half the light, so unlit traces sink
|
|
// toward the board and the lit ones stand out; their colour is kept
|
|
// rich so they still read as lit when the peak is held down below)
|
|
let bd = fm_board(q, px, 1.4, 0.0, 0.35) * 0.66
|
|
var pc = vec3(pow(max(bd.x, 0.0), 1.3), pow(max(bd.y, 0.0), 1.3), pow(max(bd.z, 0.0), 1.3)) * 1.25
|
|
pc = pc + vec3(0.026, 0.032, 0.038) * (1.0 + 0.5 * fz) + vec3(0.50, 0.56, 0.62) * refl
|
|
pc = max(mix(vec3(dot(pc, vec3(0.2126, 0.7152, 0.0722))), pc, 1.5), vec3(0.0))
|
|
pc = pc + g3(fm_grain(q / px + vec2(311.0, 97.0), 0.008))
|
|
// the frame's shadow on the pane, from the walls toward the light
|
|
let ds = fm_win_sd(q + fm_light() * 5.0, size)
|
|
let sh = smoothstep(-9.0, 2.0, ds)
|
|
pc = pc * (1.0 - 0.60 * sh * sh)
|
|
pc = pc * (1.0 - 0.35 * (1.0 - smoothstep(0.0, 3.0, -d)))
|
|
// the pane's bevelled rim, smoked too, lighter where it faces the light
|
|
let fr = clamp(dot(n, fm_light()), -1.0, 1.0)
|
|
let rim = step(-2.2, d)
|
|
let rc = mix(vec3(0.100, 0.125, 0.140), vec3(0.330, 0.390, 0.420), clamp(fr * 0.55 + 0.45, 0.0, 1.0))
|
|
pc = mix(pc, rc, rim * 0.85)
|
|
pc = mix(pc, vec3(0.93, 0.97, 1.0), fm_hl(d, -2.2 + px * 0.5, px, px) * 0.45)
|
|
pc = mix(pc, vec3(0.93, 0.97, 1.0), fm_hl(d, -px * 1.5, px, px) * 0.75 * clamp(fr * 0.8 + 0.4, 0.0, 1.0))
|
|
pc = mix(pc, fm_edge(), fm_hl(d, -px * 0.5, px, px) * 0.8)
|
|
// a moulded step in the pane four points in: a dark line over a light one
|
|
pc = mix(pc, pc * 0.6, fm_hl(d, -4.0 + px * 0.5, px, px) * 0.8)
|
|
pc = mix(pc, vec3(0.78, 0.87, 0.92), fm_hl(d, -4.0 - px * 0.5, px, px) * 0.25)
|
|
// the smoke holds every peak under text down; right at the window's
|
|
// own edge, where no text runs, the light may rise again
|
|
col = fm_smoke(pc, mix(0.30, 0.078, smoothstep(1.5, 3.0, eo)))
|
|
}
|
|
return col
|
|
}
|
|
|
|
mod.widgets.Window.show_bg = true
|
|
mod.widgets.Window.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
return vec4(fm_hull(self.pos * self.rect_size, self.rect_size, px), 1.0)
|
|
}
|
|
|
|
|
|
// ---------------------------------------------------------------- band
|
|
|
|
// THE BAND is the sheet's own slider with two thumbs: the same track, the
|
|
// same lit value and the same thumb as the Slider, the value lit between
|
|
// the two thumbs, its quad grown as the slider's is so the thumbs keep
|
|
// their shape. The widget's grab geometry is set to the thumbs' own, so
|
|
// what is drawn is what is dragged.
|
|
mod.widgets.RangeSliderFlat.clip_x = false
|
|
mod.widgets.RangeSliderFlat.clip_y = false
|
|
mod.widgets.RangeSliderFlat.track_inset = 1.0
|
|
mod.widgets.RangeSliderFlat.handle_size = 15.0
|
|
mod.widgets.RangeSliderFlat.draw_bg.vertex = fn() {
|
|
let g = grow_vertex(self.rect_pos, self.rect_size, 6.0)
|
|
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
|
|
}
|
|
mod.widgets.RangeSliderFlat.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let m = 6.0
|
|
let size = self.rect_size
|
|
let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
let o = self.rect_pos
|
|
let live = 1.0 - self.disabled
|
|
let tr = 7.5
|
|
let cy = size.y - tr - 1.0
|
|
let th = 6.0 * px
|
|
let top = Finish.snap(o.y + cy, px) - o.y - 3.0 * px
|
|
let x0 = Finish.snap(o.x + 1.0, px) - o.x
|
|
let w = Finish.snap(o.x + size.x - 1.0, px) - o.x - x0
|
|
let k0 = x0 + tr + self.start_pos * (w - 2.0 * tr)
|
|
let k1 = x0 + tr + self.end_pos * (w - 2.0 * tr)
|
|
let hot = max(self.hover, self.drag) * live
|
|
var out = mix(fm_track(p, vec2(x0, top), w, th, px, x0, self.disabled, hot), fm_track(p, vec2(x0, top), w, th, px, k1, self.disabled, hot), step(k0, p.x))
|
|
let f0 = fm_fit(vec2(k0, top + 3.0 * px), o, tr, px)
|
|
let f1 = fm_fit(vec2(k1, top + 3.0 * px), o, tr, px)
|
|
out = Finish.over(out, fm_ball(p, f0.xy, f0.z, px, self.hot_start * live, self.drag * self.hot_start * live, self.disabled))
|
|
out = Finish.over(out, fm_crown(p, f0.xy, f0.z * 0.34, px, max(self.focus, self.drag * self.hot_start) * live))
|
|
out = Finish.over(out, fm_ball(p, f1.xy, f1.z, px, self.hot_end * live, self.drag * self.hot_end * live, self.disabled))
|
|
out = Finish.over(out, fm_crown(p, f1.xy, f1.z * 0.34, px, max(self.focus, self.drag * self.hot_end) * live))
|
|
return Finish.over(out, fm_focus(length(p - f1.xy) - f1.z, px, self.focus * live))
|
|
}
|
|
|
|
// ---------------------------------------------------------------- switches and meter
|
|
|
|
// THE SWITCHES are the sheet's own toggle, in proportion with the
|
|
// labels and the controls beside them: the rocker and the slide switch
|
|
// take the toggle's face, its size and shape, its quad and its label
|
|
// gap (the library's own faces for them are flat boxes sized for
|
|
// another face). The rocker turns on a click; the slide switch follows a
|
|
// drag, as the toggle does.
|
|
mod.widgets.ToggleRocker.label_walk.margin = mod.turtle.Inset{left: 44.0 right: 3.0 top: 0.0 bottom: 0.0}
|
|
mod.widgets.ToggleSlide.label_walk.margin = mod.turtle.Inset{left: 44.0 right: 3.0 top: 0.0 bottom: 0.0}
|
|
mod.widgets.ToggleRocker.draw_bg.size = uniform(16.0)
|
|
mod.widgets.ToggleSlide.draw_bg.size = uniform(16.0)
|
|
mod.widgets.ToggleRocker.min_height = 26.0
|
|
mod.widgets.ToggleSlide.min_height = 26.0
|
|
mod.widgets.ToggleRocker.padding = mod.turtle.Inset{left: 0.0 right: 3.0 top: 6.0 bottom: 6.0}
|
|
mod.widgets.ToggleSlide.padding = mod.turtle.Inset{left: 0.0 right: 3.0 top: 6.0 bottom: 6.0}
|
|
mod.widgets.ToggleRocker.draw_bg.pill_aspect = uniform(2.25)
|
|
mod.widgets.ToggleSlide.draw_bg.pill_aspect = uniform(2.25)
|
|
mod.widgets.ToggleRocker.draw_bg.knob_inset = uniform(0.0)
|
|
mod.widgets.ToggleSlide.draw_bg.knob_inset = uniform(0.0)
|
|
mod.widgets.ToggleRocker.draw_bg.pixel = mod.widgets.ToggleFlat.draw_bg.pixel
|
|
mod.widgets.ToggleSlide.draw_bg.pixel = mod.widgets.ToggleFlat.draw_bg.pixel
|
|
|
|
// THE METER sits in the same window as the readouts and the screen: the
|
|
// library's display housing drawn round its face -- the bezel ring, a shade
|
|
// of whatever ground it is set in with a device pixel of dark at its edge;
|
|
// the face with the surround's shadow falling across it from the top and a
|
|
// device pixel of black where the glass meets the bezel; the glass's quiet
|
|
// diagonal band and the light line inside its top edge; the lip of light on
|
|
// the ground under it -- with the same corner, so the meter and the
|
|
// readouts read as one family. The scale, the red zone, the needle and its
|
|
// pivot are the library's own.
|
|
mod.widgets.NeedleMeter.draw_bg.border_radius = uniform(theme.corner_radius * 0.5)
|
|
mod.widgets.NeedleMeter.draw_bg.color_bezel = uniform(theme.color_d_05)
|
|
mod.widgets.NeedleMeter.draw_bg.color_lip = uniform(theme.color_u_15)
|
|
mod.widgets.NeedleMeter.draw_bg.pixel = fn() {
|
|
let p = self.pos * self.rect_size
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let lo = vec2(Finish.snap(self.rect_pos.x, px), Finish.snap(self.rect_pos.y, px)) - self.rect_pos
|
|
let hi = vec2(Finish.snap(self.rect_pos.x + self.rect_size.x, px), Finish.snap(self.rect_pos.y + self.rect_size.y, px) - px) - self.rect_pos
|
|
let c = (lo + hi) * 0.5
|
|
let ho = (hi - lo) * 0.5
|
|
let r_out = min(self.border_radius, min(ho.x, ho.y))
|
|
let outer = Material.sd_box(p, c, ho, r_out)
|
|
let b = Finish.snap(2.0, px)
|
|
let hf = max(ho - vec2(b, b), vec2(0.5, 0.5))
|
|
let rf = max(r_out - b, 0.0)
|
|
let face_d = Material.sd_box(p, c, hf, rf)
|
|
let pv = self.pivot()
|
|
let rr = self.scale_radius()
|
|
let q = p - pv
|
|
let r = length(q)
|
|
let ang = atan2(q.x, 0.0 - q.y)
|
|
let sweep = self.sweep * 0.017453293
|
|
let t = ang / sweep + 0.5
|
|
let on_scale = step(-0.0001, t) * step(t, 1.0001)
|
|
var face = self.color.rgb
|
|
// The scale: a hairline arc and a tick at every division.
|
|
let arc = Finish.cover(abs(r - rr) - px * 0.5, px) * on_scale
|
|
let n = max(floor(self.majors + 0.5), 1.0) * max(floor(self.minors + 0.5), 1.0)
|
|
let k = floor(clamp(t, 0.0, 1.0) * n + 0.5)
|
|
let ak = (k / n - 0.5) * sweep
|
|
let perp = abs(q.x * cos(ak) + q.y * sin(ak))
|
|
let mn = max(floor(self.minors + 0.5), 1.0)
|
|
let major = 1.0 - step(0.5, k - mn * floor(k / mn + 0.0001))
|
|
let len = mix(self.tick_minor, self.tick_major, major)
|
|
let radial = max(rr - len - r, r - rr)
|
|
let tick = Finish.cover(perp - px * mix(0.5, 1.0, major), px) * Finish.cover(radial, px) * step(0.0, 0.0 - q.y * cos(ak) + q.x * sin(ak))
|
|
face = mix(face, self.color_scale.rgb, max(arc, tick))
|
|
if self.zone < 0.999 {
|
|
let zone_t = Finish.cover((self.zone - t) * sweep * r, px) * Finish.cover((t - 1.0) * sweep * r, px)
|
|
let zone_r = Finish.cover(max(rr + px - r, r - rr - 2.5), px)
|
|
face = mix(face, self.color_zone.rgb, zone_t * zone_r)
|
|
}
|
|
// The needle, from under the pivot cover to just past the scale.
|
|
let na = self.needle_angle(self.value)
|
|
let nd = vec2(sin(na), 0.0 - cos(na))
|
|
let along = dot(q, nd)
|
|
let across = abs(q.x * nd.y - q.y * nd.x)
|
|
let needle = Finish.cover(across - px * 0.5, px) * Finish.cover(along - (rr + 3.0), px) * Finish.cover(0.0 - along - max(self.pivot_size - px, 0.0), px)
|
|
face = mix(face, self.color_needle.rgb, needle)
|
|
let pd = r - self.pivot_size
|
|
face = mix(face, mix(self.color_needle.rgb, self.color.rgb, 0.35), Finish.cover(pd, px))
|
|
face = mix(face, face * 0.55, Finish.band(pd, 0.0, px, px))
|
|
// The housing: the surround's shadow across the face from the top, the
|
|
// black device pixel at the glass's edge, and the glass over it all.
|
|
let insh = Finish.inset(p, c, hf, rf, vec2(0.0, 2.0), 1.8)
|
|
face = face * (1.0 - 0.55 * insh)
|
|
face = mix(face, face * 0.4, Finish.band(face_d, 0.0, px, px))
|
|
let u = (p - (c - hf)) / max(hf * 2.0, vec2(1.0, 1.0))
|
|
let diag = u.x + u.y * 0.6
|
|
let band = smoothstep(0.0, 0.12, diag) * (1.0 - smoothstep(0.28, 0.5, diag))
|
|
let top = Finish.cover(abs(p.y - (c.y - hf.y) - px * 1.5) - px * 0.5, px) * 0.6
|
|
let ga = clamp(0.03 * (band + top), 0.0, 1.0) * Finish.cover(face_d + px, px)
|
|
face = face * (1.0 - ga) + vec3(ga, ga, ga)
|
|
let bz = self.color_bezel
|
|
var col = vec4(bz.rgb * bz.a, bz.a)
|
|
col = Finish.over(col, vec4(0.0, 0.0, 0.0, 0.45 * Finish.band(outer, 0.0, px, px)))
|
|
col = mix(col, vec4(face, 1.0), Finish.cover(face_d, px))
|
|
col = col * Finish.cover(outer, px)
|
|
let lip = Finish.ring_out(outer, 0.0, px, px) * step(hi.y, p.y)
|
|
let la = self.color_lip.a * lip
|
|
return Finish.over(vec4(self.color_lip.rgb * la, la), col) * self.color.a * self.opacity
|
|
}
|
|
|
|
// ---------------------------------------------------------------- lamps
|
|
// LAMPS: an LED, as the hardware in his pictures has them. The emitter is
|
|
// the light: at full current its middle burns toward white and its rim is
|
|
// its most saturated colour; at lower current the middle is no longer
|
|
// white, the colour deeper, the glow smaller; out, it is dark tinted glass,
|
|
// its colour barely there. Round it a bezel: a raised ring lit from the
|
|
// upper left, or a socket cut into the surface, its upper wall in shadow
|
|
// and a catch light on its lower rim. Over the lens one crisp highlight of
|
|
// a dome. Its light is added, never painted: a tight hot halo and a short
|
|
// bloom in its own hue, screened over whatever is there and gone in about
|
|
// two radii; on a pale ground, where light can barely brighten, its hue is
|
|
// also laid round it as a tint that keeps the ground's luminance (the
|
|
// library's halo rule: at most 0.45 one device pixel out, halving every
|
|
// 0.4 of its reach, gone at the reach), so it never darkens or greys its
|
|
// ground. A bar is the same light drawn out into a light pipe.
|
|
let fm_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 fm_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 fm_led_lamp_hue = fn(ink: vec3, vivid: float) -> vec3 {
|
|
let f = fm_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 fm_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 = fm_led_lamp_full(ink)
|
|
let hue = fm_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 = fm_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 machined metal ring.
|
|
// The halo's room is held in the lamp's own box (the library reserves a
|
|
// lens thickness either side once a halo is asked for), so the light is
|
|
// never cut at its edge.
|
|
mod.widgets.Lamp.halo = 0.3
|
|
mod.widgets.Lamp.halo_reach = 1.0
|
|
mod.widgets.Lamp.draw_bg.vertex = fn() {
|
|
let m = 2.0
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
|
|
}
|
|
mod.widgets.Lamp.draw_bg.pixel = fn() {
|
|
let m = 2.0
|
|
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let reach = clamp(self.reach, 0.0, 1.0)
|
|
let t = self.rect_size.y / (1.0 + 2.0 * reach)
|
|
let room = t * reach
|
|
let c = self.rect_size * 0.5
|
|
let half_len = max((self.rect_size.x - 2.0 * room) * 0.5, t * 0.5)
|
|
// The lens is the lamp's size; its socket stands round it in the room.
|
|
let hb = vec2(mix(t * 0.5, half_len, self.bar), t * 0.5)
|
|
let res = fm_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.220, 0.259, 0.294), max(room - 0.5, 2.0), vec3(0.62, 0.66, 0.70), 2.0)
|
|
return res * self.opacity
|
|
}
|
|
true
|