2346 lines
121 KiB
Text
2346 lines
121 KiB
Text
use mod.prelude.widgets_internal.*
|
|
use mod.sdf.Cap
|
|
|
|
// Machined anthracite metal. Raised parts are dark slabs set in a two
|
|
// device-pixel black bezel, lit by one device pixel along their top edge,
|
|
// with a tight contact shadow and one soft layer drawn outside their boxes.
|
|
// Wells are cut into the plate: a dark top wall and a faint lip under the
|
|
// lower rim. Caps and thumbs are turned metal. The one cyan is a value, a
|
|
// lit dash or a lit face, never a tint on the metal. Every rim is one device
|
|
// pixel, snapped.
|
|
mod.widgets.Button.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 3.0 bottom: 3.0}
|
|
mod.widgets.Button.draw_bg.border_radius = 2.0
|
|
mod.widgets.Button.draw_bg.border_size = 0.5
|
|
// A ROW OF FIELDS IS ONE HEIGHT: the box metrics are stated once here and
|
|
// handed to each field below. Buttons take the same 26.
|
|
let field_height = 26.0
|
|
let field_pad_v = mod.theme.mspace_1.top
|
|
let field_radius = 2.0
|
|
let field_border = 0.5
|
|
|
|
// The room a Fit field takes: where a field is Fit, the padding IS its
|
|
// height.
|
|
let field_room = fn(w) {
|
|
w.min_height = field_height
|
|
w.padding.top = field_pad_v
|
|
w.padding.bottom = field_pad_v
|
|
}
|
|
// The box a field draws. Given the draw object and not the widget, because
|
|
// not every field draws its own box.
|
|
let field_face = fn(d) {
|
|
d.border_radius = field_radius
|
|
d.border_size = field_border
|
|
}
|
|
// A field that states a frame and lays its own parts out inside it takes
|
|
// the height alone: a padding would displace its line.
|
|
let field_frame = fn(w) {
|
|
w.min_height = field_height
|
|
}
|
|
|
|
field_room(mod.widgets.TextInputFlat)
|
|
field_face(mod.widgets.TextInputFlat.draw_bg)
|
|
field_room(mod.widgets.TextInput)
|
|
field_face(mod.widgets.TextInput.draw_bg)
|
|
field_room(mod.widgets.ComboBox)
|
|
field_face(mod.widgets.ComboBox.draw_bg)
|
|
field_room(mod.widgets.DropDown)
|
|
field_face(mod.widgets.DropDown.draw_bg)
|
|
field_room(mod.widgets.DropDown2)
|
|
field_face(mod.widgets.DropDown2.draw_bg)
|
|
field_room(mod.widgets.FieldWell)
|
|
field_face(mod.widgets.FieldWell.draw_bg)
|
|
field_room(mod.widgets.TagField)
|
|
field_face(mod.widgets.TagField.draw_bg)
|
|
field_frame(mod.widgets.NumberField)
|
|
mod.widgets.NumberField.draw_bg.border_radius = field_radius
|
|
field_frame(mod.widgets.ValueInput)
|
|
field_frame(mod.widgets.DateField)
|
|
field_frame(mod.widgets.TimeField)
|
|
field_frame(mod.widgets.DatePicker)
|
|
field_frame(mod.widgets.DateRangePicker)
|
|
// The chrome-less input a well holds must impose no height of its own.
|
|
mod.widgets.WellInput.min_height = 0.0
|
|
mod.widgets.Window.pass.clear_color = #26282d
|
|
|
|
// Plain views stop clipping, so a part's shadow may fall into the gap
|
|
// beside it. Scroll views keep their own clip.
|
|
mod.widgets.View.clip_x = false
|
|
mod.widgets.View.clip_y = false
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// SHARED PARTS. Distances in points, negative inside, y down; `px` is one
|
|
// device pixel in points.
|
|
|
|
let g3 = fn(v: float) -> vec3 {
|
|
return vec3(v, v, v)
|
|
}
|
|
|
|
// A rounded box, true radius.
|
|
let ml_rbox = fn(p: vec2, c: vec2, h: vec2, r: float) -> float {
|
|
let rr = clamp(r, 0.0, min(h.x, h.y))
|
|
let q = abs(p - c) - h + vec2(rr, rr)
|
|
return min(max(q.x, q.y), 0.0) + length(max(q, vec2(0.0, 0.0))) - rr
|
|
}
|
|
|
|
// A segment's distance.
|
|
let ml_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 ml_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(ml_seg(p, a, b), ml_seg(p, b, e))
|
|
}
|
|
|
|
// A disc laid on the pixel grid: a whole number of device pixels across,
|
|
// its edge on a pixel boundary, so its one-pixel rings are one pixel.
|
|
// `c` is quad-local; returns (centre.x, centre.y, radius).
|
|
let ml_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 lit core, the fill's core, its light and dark rims.
|
|
let ml_acc = fn() -> vec3 {
|
|
return vec3(0.012, 0.898, 0.984)
|
|
}
|
|
let ml_fill = fn() -> vec3 {
|
|
return vec3(0.008, 0.765, 0.925)
|
|
}
|
|
let ml_rim_hi = fn() -> vec3 {
|
|
return vec3(0.471, 0.863, 0.922)
|
|
}
|
|
let ml_rim_lo = fn() -> vec3 {
|
|
return vec3(0.231, 0.514, 0.612)
|
|
}
|
|
// The accent as disabled parts show it: an unlit grey.
|
|
let ml_off = fn() -> vec3 {
|
|
return vec3(0.180, 0.180, 0.192)
|
|
}
|
|
let ml_bezel = fn() -> vec3 {
|
|
return vec3(0.043, 0.039, 0.063)
|
|
}
|
|
let ml_shadow_ink = fn() -> vec3 {
|
|
return vec3(0.020, 0.016, 0.031)
|
|
}
|
|
|
|
// Signed grain, -amount..amount with a triangular spread, one value per
|
|
// device pixel (pass `p / px`). The library hash nearly repeats every 97
|
|
// pixels (0.1031 x 97 is almost a whole number), which the eye finds on a
|
|
// wide ground; a first hash scaled far up displaces the lattice of the two
|
|
// that are summed, so no shift lines them up again.
|
|
let ml_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
|
|
}
|
|
|
|
// The plate: a top-lit fall of eight levels over the window, a faint
|
|
// darkening toward the far corners, and a bead-blasted surface: one grain
|
|
// value per device pixel with a rare brighter speck.
|
|
let ml_ground = fn(p: vec2, size: vec2, px: float) -> vec3 {
|
|
let t = clamp(p.y / max(size.y, 1.0), 0.0, 1.0)
|
|
var col = mix(vec3(0.165, 0.173, 0.192), vec3(0.133, 0.141, 0.161), t)
|
|
let q = (p / max(size, vec2(1.0, 1.0)) - vec2(0.5, 0.42)) * vec2(1.0, 1.15)
|
|
col = col * (1.0 - 0.16 * smoothstep(0.35, 0.9, length(q)))
|
|
col = col + g3(ml_grain(p / px, 0.030))
|
|
let i = floor(p / px)
|
|
let sp = step(0.9975, Finish.hash(vec2(i.x + 71.3, i.y + 19.7 + Finish.hash(i) * 613.7)))
|
|
return col + g3(0.028 * sp)
|
|
}
|
|
|
|
// The face of a raised part by height t 0..1: #2b2a30 at the top, #1f2229
|
|
// at the waist, #141319 at the foot, a 2:1 fall.
|
|
let ml_face = fn(t: float) -> vec3 {
|
|
let top = vec3(0.169, 0.165, 0.188)
|
|
let mid = vec3(0.122, 0.133, 0.161)
|
|
let foot = vec3(0.078, 0.075, 0.098)
|
|
return mix(mix(top, mid, clamp(t * 2.0, 0.0, 1.0)), foot, clamp(t * 2.0 - 1.0, 0.0, 1.0))
|
|
}
|
|
|
|
// A lit face: a dark cyan ramp under the bezel, the lit body, rising paler
|
|
// toward the foot.
|
|
let ml_lit_face = fn(t: float, yi: float) -> vec3 {
|
|
let body = mix(vec3(0.004, 0.902, 0.992), vec3(0.510, 0.886, 0.980), smoothstep(0.45, 1.0, t))
|
|
return mix(vec3(0.0, 0.208, 0.255), body, smoothstep(0.0, 3.0, yi))
|
|
}
|
|
|
|
// An LED dash: `lit` 0 shows the unlit bead `off` in its engraved slot, 1
|
|
// the lit core, white-hot over its middle third, with a halo of 0.25 at
|
|
// one device pixel that is gone within three.
|
|
let ml_dash = fn(p: vec2, c: vec2, hw: float, hh: float, px: float, ink: vec3, lit: float, off: vec3) -> vec4 {
|
|
let d = ml_rbox(p, c, vec2(hw, hh), hh)
|
|
let a = Finish.cover(d, px)
|
|
let across = abs(p.y - c.y) / max(hh, 0.001)
|
|
let hot = 1.0 - smoothstep(0.15, 0.6, across)
|
|
let core = mix(ink, vec3(1.0, 1.0, 1.0), 0.55 * hot)
|
|
let col = mix(off, core, lit)
|
|
var out = vec4(col * a, a)
|
|
// the slot's lip under an unlit dash
|
|
let lip = Finish.ring_out(d, 0.0, px, px) * step(c.y, p.y) * 0.07 * (1.0 - lit)
|
|
out = Finish.over(vec4(lip, lip, lip, lip), out)
|
|
let ha = 0.25 * lit * exp(-max(d - px, 0.0) / (0.6 * px)) * (1.0 - a) * step(0.0, d)
|
|
return Finish.over(vec4(ink * ha, ha), out)
|
|
}
|
|
|
|
// A lamp bead: a round lens, lit with a white-hot centre and a tight halo,
|
|
// or unlit at a third of its light in a darker hue.
|
|
let ml_bead = fn(p: vec2, c: vec2, rad: float, px: float, ink: vec3, lit: float) -> vec4 {
|
|
let q = p - c
|
|
let d = length(q) - rad
|
|
let a = Finish.cover(d, px)
|
|
let rr = clamp(length(q) / max(rad, 0.001), 0.0, 1.0)
|
|
let on = mix(mix(ink, vec3(1.0, 1.0, 1.0), 0.55), ink * 0.8, smoothstep(0.0, 1.0, rr))
|
|
let off = mix(ink * 0.30, ink * 0.18, rr) + g3(0.03)
|
|
var col = mix(off, on, lit)
|
|
col = mix(col, ml_bezel(), Finish.band(d, 0.0, px, px) * 0.6 * (1.0 - lit))
|
|
var out = vec4(col * a, a)
|
|
// the socket: a dark ring outside the lens
|
|
let sock = Finish.ring_out(d, 0.0, px, px) * 0.55
|
|
out = Finish.over(vec4(0.0, 0.0, 0.0, sock * (1.0 - lit)), out)
|
|
let ha = 0.25 * lit * exp(-max(d - px, 0.0) / (0.6 * px)) * (1.0 - a) * step(0.0, d)
|
|
return Finish.over(vec4(ink * ha, ha), out)
|
|
}
|
|
|
|
// A well cut into the plate: the fill, the plate's shadow falling in from
|
|
// the top wall, a dark mouth darkest on top. Its lip is `ml_lip`.
|
|
let ml_well = fn(p: vec2, c: vec2, h: vec2, r: float, px: float, fill: vec3, depth: float) -> vec3 {
|
|
let d = ml_rbox(p, c, h, r)
|
|
let sh = Finish.inset(p, c, h, r, vec2(0.0, depth * 0.5), max(depth * 0.4, px * 0.6))
|
|
var col = mix(fill, vec3(0.039, 0.035, 0.059), clamp(sh, 0.0, 1.0) * 0.85)
|
|
let up = 1.0 - smoothstep(-0.5, 0.5, p.y - c.y)
|
|
col = mix(col, vec3(0.039, 0.035, 0.059), Finish.band(d, 0.0, px, px) * mix(0.5, 0.95, up))
|
|
return col
|
|
}
|
|
|
|
// The lip under a well's lower rim, outside it, confined to the foot and
|
|
// the lower corners: premultiplied white.
|
|
let ml_lip = fn(p: vec2, c: vec2, h: vec2, r: float, px: float, a: float) -> vec4 {
|
|
let d = ml_rbox(p, c, h, r)
|
|
let foot = smoothstep(c.y + h.y - max(r, 1.0) - 1.5, c.y + h.y, p.y)
|
|
let l = Finish.ring_out(d, 0.0, px, px) * foot * a
|
|
return vec4(l, l, l, l)
|
|
}
|
|
|
|
// Focus: one device pixel of the accent, two device pixels out; no halo.
|
|
let ml_focus = fn(d: float, px: float, focus: float) -> vec4 {
|
|
let f = Finish.ring_out(d, 2.0 * px, 3.0 * px, px) * 0.7 * focus
|
|
let k = ml_acc()
|
|
return vec4(k * f, f)
|
|
}
|
|
|
|
// Turned metal: a conic sheen with two bright lobes at twelve and six
|
|
// o'clock and two dark ones at three and nine, over lathe rings one device
|
|
// pixel apart. `lo` and `hi` are the dark and bright lobes' luminance.
|
|
// Returns a luminance.
|
|
let ml_conic = fn(q: vec2, px: float, lo: float, hi: float, turn: float) -> float {
|
|
let r = max(length(q), 0.0001)
|
|
let cs = q.x / r
|
|
let sn = q.y / r
|
|
let c1 = cs * cos(turn) - sn * sin(turn)
|
|
let s1 = sn * cos(turn) + cs * sin(turn)
|
|
// cos of twice the angle from vertical: +1 top and bottom, -1 sides
|
|
let c2 = s1 * s1 - c1 * c1
|
|
let k = c2 * 0.5 + 0.5
|
|
var l = mix(lo, hi, k * k * (3.0 - 2.0 * k))
|
|
let rings = Finish.noise(vec2(r / px, 0.37)) - 0.5
|
|
l = l + rings * 0.016 * smoothstep(0.0, 2.0, r)
|
|
// the pinprick where the lobes meet
|
|
l = l + 0.04 * (1.0 - smoothstep(0.0, px * 1.2, r))
|
|
return l
|
|
}
|
|
|
|
// A turned thumb standing on the plate: contact ring, a cast shadow to the
|
|
// lower right, a bright chamfer ring, a groove, the spun face. `hover`,
|
|
// `press` and `dis` are its states.
|
|
let ml_disc = fn(p: vec2, c: vec2, rad: float, px: float, hover: float, press: float, dis: float) -> vec4 {
|
|
let hh = vec2(rad, rad)
|
|
let dia = 2.0 * rad
|
|
var out = vec4(0.0, 0.0, 0.0, 0.0)
|
|
let cast = Finish.drop(p, c, hh, rad, vec2(dia * 0.06, dia * 0.08), dia * 0.14) * 0.6 * (1.0 - 0.5 * press) * mix(1.0, 0.4, dis)
|
|
let ink = ml_shadow_ink()
|
|
out = Finish.over(out, vec4(ink * cast, cast))
|
|
let q = p - c
|
|
let d = length(q) - rad
|
|
let con = Finish.ring_out(d, 0.0, px, px) * 0.7
|
|
out = Finish.over(out, vec4(vec3(0.027, 0.024, 0.047) * con, con))
|
|
var face = g3(ml_conic(q, px, 0.50, 0.80, 0.12 * hover))
|
|
face = face * vec3(0.98, 0.99, 1.0)
|
|
face = face + g3(0.08 * hover - 0.05 * press)
|
|
face = mix(face, mix(g3(0.28), face * 0.45, 0.5), dis)
|
|
// chamfer: a bright ring two device pixels wide, brightest at the foot
|
|
let t = clamp(q.y / rad * 0.5 + 0.5, 0.0, 1.0)
|
|
let ch = mix(vec3(0.78, 0.79, 0.80), vec3(0.96, 0.97, 0.97), smoothstep(0.35, 1.0, t))
|
|
face = mix(face, vec3(0.349, 0.365, 0.369), Finish.band(d, 2.0 * px, 3.0 * px, px) * 0.9)
|
|
face = mix(face, mix(ch, g3(0.32), dis), Finish.band(d, 0.0, 2.0 * px, px) * 0.9)
|
|
face = mix(face, vec3(0.42, 0.43, 0.45), Finish.band(d, 0.0, px, px) * 0.6)
|
|
let a = Finish.cover(d, px)
|
|
return Finish.over(out, vec4(face * a, a))
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// BUTTONS. One face for the family; `kind` picks the dressing:
|
|
// 0 flat plate (no soft layer), 1 raised, 2 raised with a lit dash (primary),
|
|
// 3 raised with the dash unlit (secondary), 4 raised with a dark red bead
|
|
// (danger), 5 engraved outline, 6 bare until touched, 7 pad (a lighter
|
|
// plate with no bezel). The quad is grown by `m` on every side for the
|
|
// shadow and the focus ring.
|
|
let ml_button = fn(pos: vec2, size: vec2, o: vec2, px: float, hover: float, down: float, focus: float, disabled: float, loading: float, anim: float, r: float, kind: float, m: float) -> vec4 {
|
|
let p = pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
let live = 1.0 - disabled
|
|
let press = down * live
|
|
let hov = hover * live * (1.0 - press)
|
|
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o
|
|
let hi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y, px)) - o
|
|
let c = (lo + hi) * 0.5
|
|
let h = (hi - lo) * 0.5
|
|
let d = ml_rbox(p, c, h, r)
|
|
let bare = step(5.5, kind) * step(kind, 6.5)
|
|
let outl = step(4.5, kind) * step(kind, 5.5)
|
|
let pad = step(6.5, kind)
|
|
let solid = 1.0 - bare - outl
|
|
let bez = 2.0 * px * (1.0 - pad)
|
|
let soft_on = step(0.5, kind) * solid
|
|
|
|
var out = vec4(0.0, 0.0, 0.0, 0.0)
|
|
let ink = ml_shadow_ink()
|
|
// the soft layer: a little right, mostly below, read at arm's length
|
|
// and faded within the 8 point gap to the next row
|
|
let soft = Finish.drop(p, c, h, r, vec2(0.5, 3.0), 3.2) * 0.40 * soft_on * (1.0 - press) * live
|
|
out = Finish.over(out, vec4(ink * soft, soft))
|
|
// the contact line right under the edge
|
|
let con = Finish.drop(p, c, h, r, vec2(0.0, px), px * 1.2) * 0.6 * solid * mix(1.0, 0.25, press) * mix(1.0, 0.5, disabled)
|
|
out = Finish.over(out, vec4(ink * con, con))
|
|
|
|
// the face
|
|
let t = clamp((p.y - lo.y) / max(2.0 * h.y, 1.0), 0.0, 1.0)
|
|
let yi = p.y - lo.y - bez
|
|
var face = ml_face(t)
|
|
if pad > 0.5 {
|
|
face = mix(vec3(0.184, 0.188, 0.208), vec3(0.141, 0.149, 0.169), t)
|
|
}
|
|
face = face + g3(0.026 * hov)
|
|
// pressed: a flat well-dark face with the bezel's shadow inside its top
|
|
let sunk = mix(vec3(0.051, 0.047, 0.067), vec3(0.082, 0.078, 0.102), smoothstep(0.0, 2.0 * px + 0.6, yi))
|
|
face = mix(face, sunk, press)
|
|
face = mix(face, vec3(0.122, 0.118, 0.141), disabled)
|
|
face = face + g3(ml_grain((p + o) / px, 0.012))
|
|
// the top highlight: one device pixel, then a short ramp into the face
|
|
let up = 1.0 - smoothstep(-0.5, 0.5, p.y - c.y)
|
|
let topw = 1.0 - smoothstep(lo.y + bez + r + 0.5, lo.y + bez + r + 1.5, p.y)
|
|
let botw = smoothstep(hi.y - bez - r - 1.5, hi.y - bez - r - 0.5, p.y)
|
|
let hl_row = Finish.band(d, bez, bez + px, px) * topw
|
|
let ramp = (1.0 - smoothstep(0.0, 2.0, yi - px)) * step(0.0, yi) * 0.30
|
|
let hl = mix(vec3(0.231, 0.227, 0.251), vec3(0.263, 0.259, 0.282), hov)
|
|
let lit_top = max(hl_row, ramp) * (1.0 - press) * live
|
|
face = mix(face, mix(hl, vec3(0.30, 0.30, 0.32), pad * 0.5), lit_top)
|
|
// the bottom line
|
|
let low = smoothstep(-0.5, 0.5, p.y - c.y)
|
|
face = mix(face, vec3(0.031, 0.031, 0.039), Finish.band(d, bez, bez + px, px) * botw * (1.0 - press) * (1.0 - pad))
|
|
// the bezel, or the pad's single dark edge
|
|
face = mix(face, ml_bezel(), Finish.band(d, 0.0, max(bez, px), px) * mix(1.0, 0.75, pad))
|
|
|
|
// the dash of the primary, secondary and danger faces
|
|
if kind > 1.5 && kind < 4.5 {
|
|
let hw = clamp(h.x * 0.42, 5.0, 14.0)
|
|
let dy = Finish.snap(o.y + hi.y - bez - 3.0, px) - o.y
|
|
let is_red = step(3.5, kind)
|
|
let lit = step(kind, 2.5) * live
|
|
let dink = mix(ml_acc(), vec3(0.918, 0.392, 0.431), is_red)
|
|
let doff = mix(vec3(0.035, 0.035, 0.047), vec3(0.227, 0.078, 0.090), is_red)
|
|
let dash = ml_dash(p, vec2(c.x, dy + px), hw, px, px, mix(dink, ml_off(), disabled), lit, mix(doff, vec3(0.06, 0.06, 0.07), disabled))
|
|
face = face * (1.0 - dash.a) + dash.rgb
|
|
}
|
|
|
|
var fa = 1.0
|
|
if kind > 4.5 && kind < 6.5 {
|
|
fa = mix(0.0, 0.55, hov) + 0.45 * press
|
|
}
|
|
let a = Finish.cover(d, px) * fa
|
|
out = Finish.over(out, vec4(face * a, a))
|
|
|
|
// the engraved outline: a dark line on the edge, light below its top
|
|
// run and under its foot
|
|
if outl > 0.5 {
|
|
let dk = Finish.band(d, 0.0, px, px) * mix(0.85, 0.4, disabled)
|
|
out = Finish.over(out, vec4(ml_bezel() * dk, dk))
|
|
let li = (Finish.band(d, px, 2.0 * px, px) * up + Finish.ring_out(d, 0.0, px, px) * low) * 0.07 * live
|
|
out = Finish.over(out, vec4(li, li, li, li))
|
|
}
|
|
|
|
out = Finish.over(out, ml_focus(d, px, focus * live))
|
|
|
|
// the wait spinner, which the stock face drew and this one must too
|
|
if loading > 0.0 {
|
|
let sdf = Sdf2d.viewport(p)
|
|
let rr = max(min(h.x, h.y) - 5.0, 2.0)
|
|
let a0 = anim * 2.0 * PI
|
|
sdf.arc_round_caps(c.x, c.y, rr, a0, a0 + 1.5 * PI, 1.2)
|
|
let k = ml_acc()
|
|
sdf.fill(vec4(k * loading, loading))
|
|
out = Finish.over(out, sdf.result)
|
|
}
|
|
return out
|
|
}
|
|
|
|
// The drop-down face: the raised face with an engraved split 22 points
|
|
// from its right end (1 dpx dark, 1 dpx light, inset a fifth of the height)
|
|
// and a stroked chevron 6 by 3.5 points behind it; `held` is pressed.
|
|
let ml_dropdown = fn(pos: vec2, size: vec2, o: vec2, px: float, hover: float, held: float, focus: float, disabled: float, m: float) -> vec4 {
|
|
var out = ml_button(pos, size, o, px, hover, held, focus, disabled, 0.0, 0.0, 2.0, 1.0, m)
|
|
let p = pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
let live = 1.0 - disabled
|
|
let dx = Finish.snap(o.x + size.x - 22.0, px) - o.x
|
|
let in_y = step(size.y * 0.2, p.y) * step(p.y, size.y * 0.8)
|
|
let div_d = step(dx - px, p.x) * step(p.x, dx) * in_y * 0.9
|
|
let div_l = step(dx, p.x) * step(p.x, dx + px) * in_y * 0.9 * (1.0 - held)
|
|
let bz = ml_bezel()
|
|
out = Finish.over(out, vec4(bz * div_d, div_d))
|
|
let hl = vec3(0.231, 0.227, 0.251)
|
|
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(ml_chevron(p, cc, 6.0, 3.5, 0.0) - 0.65, px)
|
|
let ink = mix(mix(vec3(0.667, 0.667, 0.675), vec3(0.784, 0.792, 0.812), hover * live), vec3(0.29, 0.286, 0.314), disabled)
|
|
return Finish.over(out, vec4(ink * ch, ch))
|
|
}
|
|
|
|
let grow_vertex = fn(rp: vec2, rs: vec2, m: float) -> vec4 {
|
|
return vec4(rp.x - m, rp.y - m, rs.x + 2.0 * m, rs.y + 2.0 * m)
|
|
}
|
|
|
|
// Button faces. The flat root and the bare face keep to their own box, so
|
|
// every template derived from them with a face of its own is left as it is;
|
|
// the raised faces grow their quad for the shadow and the focus ring.
|
|
mod.widgets.ButtonFlat.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
return self.host_over(ml_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 2.0, 0.0, 0.0), 0.0)
|
|
}
|
|
mod.widgets.ButtonFlatter.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
return self.host_over(ml_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 2.0, 6.0, 0.0), 0.0)
|
|
}
|
|
mod.widgets.Button.draw_bg.vertex = fn() {
|
|
let g = grow_vertex(self.rect_pos, self.rect_size, 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(ml_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 2.0, 1.0, 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(ml_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 2.0, 2.0, 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(ml_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 2.0, 3.0, 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(ml_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 2.0, 7.0, 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(ml_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 2.0, 4.0, 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(ml_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 2.0, 5.0, 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(ml_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 1.5, 1.0, 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(ml_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 2.0, 1.0, 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(ml_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 2.5, 1.0, 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(ml_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.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(ml_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 2.0, 1.0, 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 ml_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
|
|
// MultiSelect copied the face when it was made; it takes the same.
|
|
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 ml_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, a 2 point radius.
|
|
mod.widgets.ButtonFlat.padding = mod.turtle.Inset{left: 10.0 right: 10.0 top: 3.0 bottom: 3.0}
|
|
mod.widgets.Button.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 3.0 bottom: 3.0}
|
|
mod.widgets.ButtonPrimary.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 3.0 bottom: 3.0}
|
|
mod.widgets.ButtonTertiary.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 3.0 bottom: 3.0}
|
|
mod.widgets.ButtonOutline.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 3.0 bottom: 3.0}
|
|
mod.widgets.ButtonIcon.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 3.0 bottom: 3.0}
|
|
mod.widgets.Button.icon_walk = mod.turtle.Walk{width: 13.0 height: Fit}
|
|
mod.widgets.ButtonFlat.min_height = 26.0
|
|
mod.widgets.Button.min_height = 26.0
|
|
// The extra small face keeps room round its legend: 6 points each side.
|
|
mod.widgets.ButtonXs.min_height = 18.0
|
|
mod.widgets.ButtonXs.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 1.0 bottom: 1.0}
|
|
mod.widgets.ButtonSm.min_height = 22.0
|
|
mod.widgets.ButtonLg.min_height = 30.0
|
|
|
|
// Legends: semibold, light grey on the metal; the primary legend is the
|
|
// accent, the danger legend a red.
|
|
mod.widgets.ButtonFlat.draw_text.text_style = mod.theme.font_bold{font_size: 8.25}
|
|
mod.widgets.ButtonFlat.draw_bg.loading_color = #x03e5fb
|
|
// The trailing icon has no colour of its own in the library: legend ink.
|
|
mod.widgets.ButtonFlat.draw_icon_end.color = #aaaaac
|
|
mod.widgets.ButtonPrimary.draw_text.color = #4fc6d6
|
|
mod.widgets.ButtonPrimary.draw_text.color_hover = #x86e4ef
|
|
mod.widgets.ButtonPrimary.draw_text.color_down = #3a97a4
|
|
mod.widgets.ButtonPrimary.draw_text.color_focus = #4fc6d6
|
|
mod.widgets.ButtonPrimary.draw_text.color_disabled = #5c5b62
|
|
mod.widgets.ButtonPrimary.draw_icon.color = #4fc6d6
|
|
mod.widgets.ButtonPrimary.draw_icon_end.color = #4fc6d6
|
|
mod.widgets.ButtonPrimary.draw_text.text_style = mod.theme.font_bold{font_size: 8.25}
|
|
mod.widgets.ButtonSecondary.draw_text.color = #aaaaac
|
|
mod.widgets.ButtonSecondary.draw_text.color_hover = #c8cacf
|
|
mod.widgets.ButtonSecondary.draw_text.color_down = #8a8990
|
|
mod.widgets.ButtonSecondary.draw_text.color_focus = #aaaaac
|
|
mod.widgets.ButtonSecondary.draw_icon.color = #aaaaac
|
|
mod.widgets.ButtonTertiary.draw_text.color = #aaaaac
|
|
mod.widgets.ButtonTertiary.draw_text.color_hover = #c8cacf
|
|
mod.widgets.ButtonTertiary.draw_text.color_down = #8a8990
|
|
mod.widgets.ButtonTertiary.draw_text.color_focus = #aaaaac
|
|
mod.widgets.ButtonTertiary.draw_icon.color = #aaaaac
|
|
mod.widgets.ButtonDanger.draw_text.color = #xe8858c
|
|
mod.widgets.ButtonDanger.draw_text.color_hover = #f2a4a9
|
|
mod.widgets.ButtonDanger.draw_text.color_down = #b8666c
|
|
mod.widgets.ButtonDanger.draw_text.color_focus = #xe8858c
|
|
mod.widgets.ButtonDanger.draw_icon.color = #xe8858c
|
|
mod.widgets.ButtonOutline.draw_text.color = #aaaaac
|
|
mod.widgets.ButtonOutline.draw_text.color_hover = #c8cacf
|
|
mod.widgets.ButtonOutline.draw_text.color_down = #8a8990
|
|
mod.widgets.ButtonOutline.draw_text.color_focus = #aaaaac
|
|
mod.widgets.ButtonOutline.draw_text.color_disabled = #5c5b62
|
|
mod.widgets.ButtonOutline.draw_icon.color = #aaaaac
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// CHECK BOXES. A 13 point well two points in from the row, the tick lit in
|
|
// the accent with a halo held tight; the well stays dark when set.
|
|
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 its round well
|
|
let r = mix(1.5, h.y, step(20.0, self.border_radius))
|
|
let d = ml_rbox(p, c, h, r)
|
|
let live = 1.0 - self.disabled
|
|
var fill = mix(vec3(0.086, 0.082, 0.106), vec3(0.137, 0.137, 0.153), self.disabled)
|
|
fill = fill + g3(0.04 * self.hover * live * (1.0 - self.down) - 0.03 * self.down * live)
|
|
var col = ml_well(p, c, h, r, px, fill, (2.0 + 1.2 * self.down) * (1.0 - 0.7 * self.disabled))
|
|
// error: the mouth turns red
|
|
col = mix(col, vec3(0.918, 0.392, 0.431), Finish.band(d, 0.0, px, px) * self.error)
|
|
// the tick, or the mixed dash, 1.8 points
|
|
let k = sz / 13.0
|
|
let m0 = lo + vec2(3.3, 6.8) * k
|
|
let m1 = lo + vec2(5.6, 9.1) * k
|
|
let m2 = lo + vec2(9.8, 4.1) * k
|
|
let ck = min(ml_seg(p, m0, m1), ml_seg(p, m1, m2)) - 0.9 * k
|
|
let dash = ml_seg(p, lo + vec2(3.4, 6.5) * k, lo + vec2(9.6, 6.5) * k) - 0.9 * k
|
|
let show_ck = self.active * (1.0 - self.mixed)
|
|
let md = mix(mix(1000.0, ck, show_ck), dash, self.mixed)
|
|
let mk = Finish.cover(md, px)
|
|
let edge = Finish.band(md, 0.0, px, px)
|
|
var tick = mix(vec3(0.522, 0.827, 0.827), vec3(0.392, 0.757, 0.776), edge)
|
|
tick = mix(tick, vec3(0.20, 0.30, 0.32), self.disabled)
|
|
// a tight halo under the mark, inside the well
|
|
col = mix(col, tick, mk)
|
|
let a = Finish.cover(d, px)
|
|
var out = ml_lip(p, c, h, r, px, 0.07)
|
|
out = Finish.over(out, vec4(col * a, a))
|
|
return Finish.over(out, ml_focus(d, px, self.focus * live))
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// TOGGLES. A slot 36 by 16 points holding a machined block as tall as the
|
|
// slot, shaded as a cylinder across its width. The part of the slot the
|
|
// block leaves open carries an LED dash: lit when on, an engraved slot
|
|
// when off.
|
|
mod.widgets.ToggleFlat.label_walk.margin = mod.turtle.Inset{left: 44.0 right: 3.0 top: 0.0 bottom: 0.0}
|
|
mod.widgets.ToggleFlat.draw_bg.size = uniform(16.0)
|
|
mod.widgets.ToggleFlat.min_height = 26.0
|
|
mod.widgets.ToggleFlat.padding = mod.turtle.Inset{left: 0.0 right: 3.0 top: 6.0 bottom: 6.0}
|
|
mod.widgets.ToggleFlat.draw_bg.pill_aspect = uniform(2.25)
|
|
mod.widgets.ToggleFlat.draw_bg.knob_inset = uniform(0.0)
|
|
mod.widgets.ToggleFlat.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let o = self.rect_pos
|
|
let sz = vec2(self.size * self.pill_aspect, self.size)
|
|
let x0 = mix(2.0, self.rect_size.x - sz.x - 2.0, self.mark_at_end)
|
|
let lo = vec2(Finish.snap(o.x + x0, px), Finish.snap(o.y + (self.rect_size.y - sz.y) * 0.5, px)) - o
|
|
let h = sz * 0.5
|
|
let c = lo + h
|
|
let r = 2.0
|
|
let d = ml_rbox(p, c, h, r)
|
|
let live = 1.0 - self.disabled
|
|
let knob_t = mix(self.active, self.drag_pos, self.drag)
|
|
var fill = mix(vec3(0.082, 0.094, 0.129), vec3(0.106, 0.102, 0.125), self.disabled)
|
|
fill = fill + g3(0.03 * self.hover * live - 0.02 * self.down * live)
|
|
var col = ml_well(p, c, h, r, px, fill, 2.4 + self.down)
|
|
col = mix(col, vec3(0.918, 0.392, 0.431), Finish.band(d, 0.0, px, px) * self.error)
|
|
let a = Finish.cover(d, px)
|
|
var out = ml_lip(p, c, h, r, px, 0.07)
|
|
out = Finish.over(out, vec4(col * a, a))
|
|
// the block: 14 points wide, its left edge on a pixel boundary
|
|
let bw = 14.0
|
|
let bx = Finish.snap(o.x + lo.x + knob_t * (sz.x - bw), px) - o.x
|
|
let bh = vec2(bw * 0.5, h.y)
|
|
let bc = vec2(bx + bh.x, c.y)
|
|
// the dash in the open part of the slot
|
|
let open_c = mix(lo.x + sz.x - (sz.x - bw) * 0.5, lo.x + (sz.x - bw) * 0.5, knob_t)
|
|
let dsh = ml_dash(p, vec2(open_c, Finish.snap(o.y + c.y, px) - o.y), 4.0, px, px, mix(ml_acc(), ml_off(), self.disabled), self.active * live, vec3(0.255, 0.251, 0.275))
|
|
out = Finish.over(out, dsh)
|
|
// its shadow: contact and a short soft layer
|
|
let ink = ml_shadow_ink()
|
|
let sd = Finish.drop(p, bc, bh, 1.5, vec2(0.6, 1.4), 1.2) * 0.35 * live
|
|
out = Finish.over(out, vec4(ink * sd, sd))
|
|
let cd = Finish.drop(p, bc, bh, 1.5, vec2(0.0, px), px) * 0.6
|
|
out = Finish.over(out, vec4(ink * cd, cd))
|
|
let bd = ml_rbox(p, bc, bh, 1.5)
|
|
// cylinder shading across the width: dark edges, a bright band left of
|
|
// the middle, a second rim band near the far edge
|
|
let u = clamp((p.x - (bc.x - bh.x)) / bw, 0.0, 1.0)
|
|
var face = mix(vec3(0.176, 0.173, 0.196), vec3(0.365, 0.361, 0.392), smoothstep(0.0, 0.25, u))
|
|
face = mix(face, vec3(0.396, 0.388, 0.431), smoothstep(0.25, 0.5, u))
|
|
face = mix(face, vec3(0.224, 0.220, 0.251), smoothstep(0.5, 0.75, u))
|
|
face = mix(face, vec3(0.392, 0.388, 0.420), smoothstep(0.75, 0.85, u) * (1.0 - smoothstep(0.88, 0.95, u)))
|
|
face = mix(face, vec3(0.188, 0.184, 0.216), smoothstep(0.9, 1.0, u))
|
|
// lit from above: +5 % at the top falling to -14 % at the foot
|
|
let bv = clamp((p.y - (bc.y - bh.y)) / max(2.0 * bh.y, 1.0), 0.0, 1.0)
|
|
face = face * mix(1.05, 0.86, bv)
|
|
face = face + g3(0.07 * self.hover * live - 0.05 * self.down * live)
|
|
face = mix(face, g3(0.16), self.disabled * 0.7)
|
|
// top highlight two device pixels, bottom line one
|
|
let bt = p.y - (bc.y - bh.y)
|
|
face = mix(face, vec3(0.514, 0.510, 0.541), step(0.0, bt) * step(bt, 2.0 * px - 0.01) * live)
|
|
face = mix(face, vec3(0.051, 0.047, 0.078), Finish.band(bd, 0.0, px, px) * smoothstep(-0.5, 0.5, p.y - bc.y))
|
|
// the dark edge on the sides and foot; the top edge is the highlight
|
|
face = mix(face, ml_bezel(), Finish.band(bd, 0.0, px, px) * 0.6 * (1.0 - step(bt, 2.0 * px - 0.01) * live))
|
|
let ba = Finish.cover(bd, px)
|
|
out = Finish.over(out, vec4(face * ba, ba))
|
|
return Finish.over(out, ml_focus(d, px, self.focus * live))
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// RADIOS. A round well; the chosen one holds a lit bead, white-hot at its
|
|
// centre with a halo held within two points.
|
|
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 = ml_fit(vec2(2.0 + sz * 0.5, self.rect_size.y * 0.5), o, sz * 0.5, px)
|
|
let c = kf.xy
|
|
let rad = kf.z
|
|
let h = vec2(rad, rad)
|
|
let live = 1.0 - self.disabled
|
|
let d = length(p - c) - rad
|
|
var fill = mix(vec3(0.086, 0.082, 0.106), vec3(0.137, 0.137, 0.153), self.disabled)
|
|
fill = fill + g3(0.04 * self.hover * live - 0.03 * self.down * live)
|
|
let col = ml_well(p, c, h, rad, px, fill, (2.0 + 1.2 * self.down) * (1.0 - 0.7 * self.disabled))
|
|
let a = Finish.cover(d, px)
|
|
var out = ml_lip(p, c, h, rad, px, 0.07)
|
|
out = Finish.over(out, vec4(col * a, a))
|
|
let bead = ml_bead(p, c, rad * 0.45, px, mix(vec3(0.506, 0.976, 0.969), ml_off(), self.disabled), live)
|
|
out = Finish.over(out, vec4(bead.rgb * self.active, bead.a * self.active))
|
|
return Finish.over(out, ml_focus(d, px, self.focus * live))
|
|
}
|
|
|
|
// Radio tabs: machined segments; the chosen one is a lit face with a dark
|
|
// legend and a halo held within two points.
|
|
let ml_segment = fn(p: vec2, size: vec2, o: vec2, px: float, hover: float, down: float, focus: float, dis: float, on: float, soft: float) -> vec4 {
|
|
let live = 1.0 - dis
|
|
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o
|
|
let hi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y, px)) - o
|
|
let h = (hi - lo) * 0.5
|
|
let c = lo + h
|
|
let r = 2.0
|
|
let d = ml_rbox(p, c, h, r)
|
|
let t = clamp((p.y - lo.y) / max(2.0 * h.y, 1.0), 0.0, 1.0)
|
|
let yi = p.y - lo.y - 2.0 * px
|
|
let press = down * live * (1.0 - on)
|
|
var face = ml_face(t) + g3(0.026 * hover * live * (1.0 - press))
|
|
let sunk = mix(vec3(0.051, 0.047, 0.067), vec3(0.082, 0.078, 0.102), smoothstep(0.0, 2.0 * px + 0.6, yi))
|
|
face = mix(face, sunk, press)
|
|
face = mix(face, vec3(0.122, 0.118, 0.141), dis)
|
|
let lit = on * live
|
|
face = mix(face, ml_lit_face(t, yi), lit)
|
|
face = mix(face, mix(ml_face(t), vec3(0.10, 0.20, 0.22), 0.6), on * dis)
|
|
face = face + g3(ml_grain((p + o) / px, 0.010))
|
|
let up = 1.0 - smoothstep(lo.y + 2.0 * px + r + 0.5, lo.y + 2.0 * px + r + 1.5, p.y)
|
|
let low = smoothstep(hi.y - 2.0 * px - r - 1.5, hi.y - 2.0 * px - r - 0.5, p.y)
|
|
let ramp = (1.0 - smoothstep(0.0, 2.0, yi - px)) * step(0.0, yi) * 0.30
|
|
face = mix(face, vec3(0.231, 0.227, 0.251), max(Finish.band(d, 2.0 * px, 3.0 * px, px) * up, ramp) * (1.0 - press) * (1.0 - lit) * live)
|
|
face = mix(face, mix(vec3(0.031, 0.031, 0.039), vec3(0.373, 0.627, 0.698), lit), Finish.band(d, 2.0 * px, 3.0 * px, px) * low)
|
|
face = mix(face, mix(ml_bezel(), vec3(0.059, 0.133, 0.149), lit), Finish.band(d, 0.0, 2.0 * px, px))
|
|
// focus: one device pixel of the accent on the bezel's inner edge,
|
|
// inside the box, in place of the lit top row (a ring outside it is cut
|
|
// by a row that clips)
|
|
face = mix(face, ml_acc(), Finish.band(d, 2.0 * px, 3.0 * px, px) * 0.7 * focus * live * (1.0 - lit))
|
|
var out = vec4(0.0, 0.0, 0.0, 0.0)
|
|
let ink = ml_shadow_ink()
|
|
let sft = Finish.drop(p, c, h, r, vec2(0.5, 3.0), 3.2) * 0.40 * soft * (1.0 - press) * live
|
|
out = Finish.over(out, vec4(ink * sft, sft))
|
|
let con = Finish.drop(p, c, h, r, vec2(0.0, px), px * 1.2) * 0.6 * (1.0 - 0.7 * press)
|
|
out = Finish.over(out, vec4(ink * con, con))
|
|
// the lit face's halo, a quarter at one device pixel
|
|
let k = ml_acc()
|
|
let ha = 0.25 * lit * exp(-max(d - px, 0.0) / (0.6 * px)) * step(0.0, d)
|
|
out = Finish.over(out, vec4(k * ha, ha))
|
|
let a = Finish.cover(d, px)
|
|
return Finish.over(out, vec4(face * a, a))
|
|
}
|
|
|
|
mod.widgets.RadioButtonTabFlat.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 6.0 bottom: 6.0}
|
|
mod.widgets.RadioButtonTabFlat.min_height = 26.0
|
|
mod.widgets.RadioButtonTabFlat.label_walk.margin = mod.turtle.Inset{left: 0.0 right: 0.0 top: 0.0 bottom: 0.0}
|
|
mod.widgets.RadioButtonTabFlat.clip_x = false
|
|
mod.widgets.RadioButtonTabFlat.clip_y = false
|
|
mod.widgets.RadioButtonTabFlat.draw_bg.vertex = fn() {
|
|
let g = grow_vertex(self.rect_pos, self.rect_size, 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 ml_segment(p, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.active, 1.0)
|
|
}
|
|
mod.widgets.RadioButtonTabFlat.draw_text.text_style = mod.theme.font_bold{font_size: 8.25}
|
|
mod.widgets.RadioButtonTabFlat.draw_text.color = #9a9aa0
|
|
mod.widgets.RadioButtonTabFlat.draw_text.color_hover = #c8cacf
|
|
mod.widgets.RadioButtonTabFlat.draw_text.color_active = #0b2a33
|
|
mod.widgets.RadioButtonTabFlat.draw_text.color_disabled = #5c5b62
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// SLIDERS. A groove seven device pixels deep cut into the plate, the value
|
|
// a three device-pixel line inside it (light rim, core, dark rim), and a
|
|
// 12 point turned thumb whose centre 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 value line; `lo` its top left, `w` its length, `fx`
|
|
// where the value ends.
|
|
let ml_track = fn(p: vec2, lo: vec2, w: float, px: float, fx: float, dis: float, hot: float) -> vec4 {
|
|
let th = 7.0 * px
|
|
let h = vec2(w * 0.5, th * 0.5)
|
|
let c = lo + h
|
|
let r = h.y
|
|
let d = ml_rbox(p, c, h, r)
|
|
let fill = mix(vec3(0.082, 0.078, 0.102), vec3(0.106, 0.102, 0.125), dis)
|
|
var col = ml_well(p, c, h, r, px, fill, 1.6)
|
|
// the value line: rows 2, 3 and 4 of the seven, from the start to fx
|
|
let row = floor((p.y - lo.y) / px)
|
|
let inx = step(lo.x + 2.0 * px, p.x) * step(p.x, fx)
|
|
let r2 = step(1.5, row) * step(row, 2.5)
|
|
let r3 = step(2.5, row) * step(row, 3.5)
|
|
let r4 = step(3.5, row) * step(row, 4.5)
|
|
var lit = ml_rim_hi() * r2 + ml_fill() * r3 + ml_rim_lo() * r4
|
|
lit = lit * (1.0 + 0.10 * hot)
|
|
lit = mix(lit, ml_off() * (1.0 + 0.2 * r2), dis)
|
|
col = mix(col, lit, inx * (r2 + r3 + r4))
|
|
let a = Finish.cover(d, px)
|
|
let out = ml_lip(p, c, h, r, px, 0.07)
|
|
return Finish.over(out, vec4(col * a, a))
|
|
}
|
|
|
|
// The thumb's lit centre while held or focused: an accent dot with a dark
|
|
// cyan edge.
|
|
let ml_thumb_dot = 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 col = mix(vec3(0.290, 0.898, 0.929), vec3(0.051, 0.255, 0.298), Finish.band(d, 0.0, px, px))
|
|
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 = 6.0
|
|
let cy = size.y - tr - 1.0
|
|
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 = ml_track(p, vec2(x0, top), w, px, kx, self.disabled, hot)
|
|
let kf = ml_fit(vec2(kx, top + 3.5 * px), o, tr, px)
|
|
out = Finish.over(out, ml_disc(p, kf.xy, kf.z, px, self.hover * live, self.drag * live, self.disabled))
|
|
out = Finish.over(out, ml_thumb_dot(p, kf.xy, kf.z * 0.35, px, max(self.focus, self.drag) * live))
|
|
return Finish.over(out, ml_focus(length(p - kf.xy) - kf.z, px, self.focus * live))
|
|
}
|
|
|
|
// The minimal slider: a hairline groove, the accent thread in it and a
|
|
// small turned cap.
|
|
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.0, 4.5, 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)
|
|
// groove: two dark rows over one light row
|
|
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.07
|
|
let lit = step(p.x, kx) * dark
|
|
var out = vec4(light, light, light, light)
|
|
let g = mix(vec3(0.047, 0.043, 0.067), mix(ml_fill(), ml_off(), self.disabled), lit)
|
|
out = Finish.over(out, vec4(g * dark, dark))
|
|
let kf = ml_fit(vec2(kx, cy - px), o, tr, px)
|
|
out = Finish.over(out, ml_disc(p, kf.xy, kf.z, px, self.hover * live, self.drag * live, self.disabled))
|
|
return Finish.over(out, ml_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 well beside its label, the value line inside it
|
|
// and a turned cap riding in the well; the value reads on the well.
|
|
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, px) - o.x, Finish.snap(o.y + size.y, px) - o.y - px)
|
|
let h = (hi - vec2(x0, y0)) * 0.5
|
|
let c = vec2(x0, y0) + h
|
|
let r = h.y
|
|
let d = ml_rbox(p, c, h, r)
|
|
let fill = mix(vec3(0.082, 0.078, 0.102), vec3(0.106, 0.102, 0.125), self.disabled) + g3(0.018 * self.hover * live)
|
|
var col = ml_well(p, c, h, r, px, fill, 2.2)
|
|
let cr = h.y - 2.0 * px
|
|
let kx = x0 + 2.0 * px + cr + self.slide_pos * (2.0 * h.x - 4.0 * px - 2.0 * cr)
|
|
// the value line, three device pixels at the well's middle
|
|
let mid = Finish.snap(o.y + c.y, px) - o.y
|
|
let row = floor((p.y - mid) / px + 1.5)
|
|
let inx = step(x0 + 3.0 * px, p.x) * step(p.x, kx)
|
|
let r2 = step(-0.5, row) * step(row, 0.5)
|
|
let r3 = step(0.5, row) * step(row, 1.5)
|
|
let r4 = step(1.5, row) * step(row, 2.5)
|
|
var lit = ml_rim_hi() * r2 + ml_fill() * r3 + ml_rim_lo() * r4
|
|
lit = mix(lit, ml_off(), self.disabled)
|
|
col = mix(col, lit, inx * (r2 + r3 + r4))
|
|
let a = Finish.cover(d, px)
|
|
var out = ml_lip(p, c, h, r, px, 0.07)
|
|
out = Finish.over(out, vec4(col * a, a))
|
|
let kf = ml_fit(vec2(kx, c.y), o, cr, px)
|
|
out = Finish.over(out, ml_disc(p, kf.xy, kf.z, px, self.hover * live, self.drag * live, self.disabled))
|
|
return Finish.over(out, ml_focus(d, px, self.focus * live))
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// KNOBS. A turned cap on a black skirt: a cast shadow to the lower right, a
|
|
// contact ring, a bright chamfer ring and a groove round a spun face, an
|
|
// engraved pointer. From 32 points up a knurled band and a ledge ring. The
|
|
// value is a thin accent arc outside the skirt; its unlit rest is an
|
|
// engraved groove, not a grey bar.
|
|
let ml_knob = fn(p: vec2, c: vec2, rad: float, px: float, val: float, gap: float, hover: float, press: float, dis: float) -> vec4 {
|
|
let dia = rad * 2.0
|
|
let hh = vec2(rad, rad)
|
|
let live = 1.0 - dis
|
|
let ink = ml_shadow_ink()
|
|
var out = vec4(0.0, 0.0, 0.0, 0.0)
|
|
let cast = Finish.drop(p, c, hh, rad, vec2(dia * 0.08, dia * 0.09), dia * 0.06) * 0.6 * mix(1.0, 0.4, dis)
|
|
out = Finish.over(out, vec4(ink * cast, cast))
|
|
let q = p - c
|
|
let d = length(q) - rad
|
|
let big = step(34.0, dia)
|
|
// skirt, ledge and knurl on the larger caps
|
|
let skirt = max(floor(dia * 0.04 / px + 0.5), 2.0) * px * big
|
|
let knurl = max(floor(dia * 0.055 / px + 0.5), 2.0) * px * big
|
|
let ang = atan2(q.x, -q.y)
|
|
let teeth = 0.5 + 0.5 * cos(ang * 90.0)
|
|
let up = 1.0 - smoothstep(-0.3, 0.3, q.y / rad)
|
|
var face = g3(ml_conic(q, px, 0.45, 0.86, 0.10 * hover))
|
|
face = face * vec3(0.985, 0.995, 1.0)
|
|
face = face + g3(0.04 * hover - 0.03 * press)
|
|
face = mix(face, mix(g3(0.30), face * 0.5, 0.5), dis)
|
|
// from the outside in: contact ring, skirt, ledge, knurl, chamfer, groove
|
|
let e0 = 0.0
|
|
let e1 = e0 + px
|
|
let e2 = e1 + skirt
|
|
let e3 = e2 + px * big
|
|
let e4 = e3 + knurl
|
|
let cw = max(floor(dia * 0.024 / px + 0.5), 2.0) * px
|
|
let e5 = e4 + cw
|
|
let e6 = e5 + px
|
|
let kt = mix(vec3(0.20, 0.205, 0.215), vec3(0.47, 0.48, 0.49), teeth) * mix(0.8, 1.15, up)
|
|
let chm = mix(vec3(0.776, 0.792, 0.796), vec3(0.992, 1.0, 0.996), smoothstep(-0.2, 0.9, q.y / rad))
|
|
face = mix(face, vec3(0.349, 0.365, 0.369), Finish.band(d, e5, e6, px))
|
|
face = mix(face, mix(chm, g3(0.34), dis), Finish.band(d, e4, e5, px))
|
|
face = mix(face, mix(kt, g3(0.20), dis), Finish.band(d, e3, e4, px) * big)
|
|
face = mix(face, vec3(0.341, 0.357, 0.369), Finish.band(d, e2, e3, px) * big)
|
|
face = mix(face, vec3(0.016, 0.016, 0.008), Finish.band(d, e1, e2, px) * big)
|
|
face = mix(face, vec3(0.059, 0.078, 0.090), Finish.band(d, e0, e1, 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 - e6
|
|
let pa = c + dir * inner * 0.45
|
|
let pb = c + dir * inner * 0.92
|
|
let pw = max(dia * 0.015, 0.55)
|
|
let pl = ml_seg(p, pa, pb) - pw
|
|
let pl_lit = ml_seg(p - vec2(0.0, px), pa, pb) - pw
|
|
let on_face = step(length(q), inner)
|
|
face = mix(face, g3(0.96), Finish.cover(pl_lit, px) * 0.5 * live * on_face)
|
|
face = mix(face, mix(vec3(0.157, 0.161, 0.169), g3(0.3), dis), Finish.cover(pl, px) * on_face)
|
|
let a = Finish.cover(d, px)
|
|
return Finish.over(out, vec4(face * a, a))
|
|
}
|
|
|
|
// The value arc: `ar` its middle radius, `aw` its width. The unlit part is a
|
|
// groove one device pixel dark with a lighter pixel outside it.
|
|
let ml_arc = fn(p: vec2, c: vec2, ar: float, aw: float, px: float, val: float, gap: float, dis: float, hot: float) -> vec4 {
|
|
let start = gap * 0.5
|
|
let sweep = 2.0 * PI - gap
|
|
let q = p - c
|
|
var ang = atan2(-q.x, q.y)
|
|
if ang < 0.0 {
|
|
ang = ang + 2.0 * PI
|
|
}
|
|
let rd = abs(length(q) - ar) - aw * 0.5
|
|
let in_arc = step(start, ang) * step(ang, start + sweep)
|
|
let track_d = mix(1000.0, rd, in_arc)
|
|
let lit_in = step(start, ang) * step(ang, start + sweep * val)
|
|
let lit_d = mix(1000.0, rd, lit_in)
|
|
var out = vec4(0.0, 0.0, 0.0, 0.0)
|
|
// the groove: dark inside, light lip outside
|
|
let gd = Finish.cover(track_d, px)
|
|
let lipd = mix(1000.0, abs(length(q) - ar - aw * 0.5 - px * 0.5) - px * 0.5, in_arc)
|
|
let lip = Finish.cover(lipd, px) * 0.06
|
|
out = vec4(lip, lip, lip, lip)
|
|
out = Finish.over(out, vec4(vec3(0.102, 0.114, 0.141) * gd, gd))
|
|
let la = Finish.cover(lit_d, px)
|
|
let k = mix(ml_fill() * (1.0 + 0.1 * hot), ml_off(), dis)
|
|
return Finish.over(out, vec4(k * la, la))
|
|
}
|
|
|
|
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 rad = floor(rr * 0.72 / px) * px
|
|
let aw = 2.0 * px
|
|
let ar = floor((rad + 2.0 * rad * 0.07) / px + 0.5) * px + aw * 0.5
|
|
let hot = max(self.hover, self.drag) * live
|
|
var out = ml_arc(p, c, ar, aw, px, self.slide_pos, gap, self.disabled, hot)
|
|
out = Finish.over(out, ml_knob(p, c, rad, px, self.slide_pos, gap, max(self.hover, self.focus) * live, self.drag * live, self.disabled))
|
|
return Finish.over(out, ml_focus(length(p - c) - ar - aw * 0.5, 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 rad = floor(rr * 0.74 / px) * px
|
|
let aw = 2.0 * px
|
|
let ar = floor((rad + 2.0 * rad * 0.07) / px + 0.5) * px + aw * 0.5
|
|
let hot = max(self.hover, self.drag) * live
|
|
var out = ml_arc(p, c, ar, aw, px, self.slide_pos, gap, self.disabled, hot)
|
|
out = Finish.over(out, ml_knob(p, c, rad, px, self.slide_pos, gap, max(self.hover, self.focus) * live, self.drag * live, self.disabled))
|
|
return Finish.over(out, ml_focus(length(p - c) - ar - aw * 0.5, px, self.focus * live))
|
|
}
|
|
|
|
// The dial's readout: an emissive display in a dark well, cyan digits with
|
|
// no bloom.
|
|
mod.widgets.RotaryFlat.text_input.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 3.0 bottom: 3.0}
|
|
mod.widgets.RotaryFlat.text_input.min_height = 20.0
|
|
mod.widgets.RotaryFlat.text_input.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let o = self.rect_pos
|
|
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o
|
|
let hi = vec2(Finish.snap(o.x + self.rect_size.x, px), Finish.snap(o.y + self.rect_size.y, px)) - o - vec2(0.0, px)
|
|
let h = (hi - lo) * 0.5
|
|
let c = lo + h
|
|
let d = ml_rbox(p, c, h, 2.0)
|
|
var col = ml_well(p, c, h, 2.0, px, vec3(0.059, 0.067, 0.075), 2.4)
|
|
// one soft band of reflection over the top third of the glass
|
|
let t = clamp((p.y - lo.y) / max(2.0 * h.y, 1.0), 0.0, 1.0)
|
|
col = col + g3(0.02 * (1.0 - smoothstep(0.1, 0.35, t)) * step(2.0 * px, p.y - lo.y))
|
|
let a = Finish.cover(d, px)
|
|
var out = ml_lip(p, c, h, 2.0, px, 0.07)
|
|
return Finish.over(out, vec4(col * a, a))
|
|
}
|
|
mod.widgets.RotaryFlat.text_input.draw_text.color = #3fc9dc
|
|
mod.widgets.RotaryFlat.text_input.draw_text.color_hover = #3fc9dc
|
|
mod.widgets.RotaryFlat.text_input.draw_text.color_focus = #x5fd6e6
|
|
mod.widgets.RotaryFlat.text_input.draw_text.color_empty = #1f5a63
|
|
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: 8 points, caption ink.
|
|
mod.widgets.RotaryKnob.draw_text.text_style = mod.theme.font_regular{font_size: 6.0}
|
|
mod.widgets.RotaryKnob.draw_text.color = #x8b8f98
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// FADERS. A turned puck sunk in a channel milled into the plate: the slider
|
|
// bench's switch puck, drawn by the cap engine (`mod.sdf.Cap`). The channel
|
|
// is the puck's own width with round ends and runs from one track inset to
|
|
// the other, so at either stop the puck's rim meets the channel's end. Its
|
|
// floor is the plate's well ink; the top wall throws its shadow in and the
|
|
// lower lip catches the light. The puck is grey spun metal, lower than the
|
|
// channel is deep, so it throws nothing on the plate: its shadow and contact
|
|
// shade stay on the channel's floor. There is no value fill: the bench's
|
|
// puck runs its channel with no track in it. Hover and focus lift its face
|
|
// a little, a press sinks it half a point deeper, focus rings it in the
|
|
// accent.
|
|
mod.widgets.SliderFader.cap_size = 18.0
|
|
// A puck is as wide as it is long, so it does not read cap_cross.
|
|
mod.widgets.SliderFader.draw_bg.cap_cross = uniform(0.45)
|
|
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.
|
|
mod.widgets.SliderFader.draw_bg.cap_light = uniform(vec3(-0.3, -0.58, 0.78))
|
|
mod.widgets.SliderFader.draw_bg.cap_intensity = uniform(0.45)
|
|
mod.widgets.SliderFader.draw_bg.cap_specular = uniform(0.64)
|
|
mod.widgets.SliderFader.draw_bg.cap_roughness = uniform(0.92)
|
|
mod.widgets.SliderFader.draw_bg.cap_gloss = uniform(0.46)
|
|
mod.widgets.SliderFader.draw_bg.cap_rim = uniform(0.0)
|
|
mod.widgets.SliderFader.draw_bg.cap_ao = uniform(1.0)
|
|
mod.widgets.SliderFader.draw_bg.cap_ao_reach = uniform(0.2)
|
|
mod.widgets.SliderFader.draw_bg.cap_reflection = uniform(0.56)
|
|
mod.widgets.SliderFader.draw_bg.cap_reflection_perspective = uniform(0.35)
|
|
mod.widgets.SliderFader.draw_bg.cap_metal = uniform(1.0)
|
|
mod.widgets.SliderFader.draw_bg.cap_spun = uniform(1.0)
|
|
mod.widgets.SliderFader.draw_bg.cap_glow = uniform(0.0)
|
|
mod.widgets.SliderFader.draw_bg.cap_exposure = uniform(0.0)
|
|
mod.widgets.SliderFader.draw_bg.cap_rolloff = uniform(0.6)
|
|
mod.widgets.SliderFader.draw_bg.cap_shadow = uniform(0.7)
|
|
mod.widgets.SliderFader.draw_bg.cap_shadow_blur = uniform(12.0)
|
|
mod.widgets.SliderFader.draw_bg.cap_contact = uniform(1.0)
|
|
mod.widgets.SliderFader.draw_bg.cap_inner = uniform(0.9)
|
|
// The puck's solid: corner share, edge width share, blend, height in points.
|
|
mod.widgets.SliderFader.draw_bg.cap_shape = uniform(vec4(1.0, 1.0, 0.04, 2.5))
|
|
// How deep the channel is cut, in points: a point deeper than the puck is
|
|
// tall, which is what sinks it.
|
|
mod.widgets.SliderFader.draw_bg.cap_well_depth = uniform(3.5)
|
|
// The inks: the puck in the bench's grey metal, the plate and its well in
|
|
// the sheet's own colours, the light and the shade the bench lit it with.
|
|
mod.widgets.SliderFader.draw_bg.handle_color = uniform(#8f8f8f)
|
|
mod.widgets.SliderFader.draw_bg.ground_color = uniform(theme.color_bg_app)
|
|
mod.widgets.SliderFader.draw_bg.well_color = uniform(theme.color_inset)
|
|
mod.widgets.SliderFader.draw_bg.light_color = uniform(#ffffff)
|
|
mod.widgets.SliderFader.draw_bg.shadow_color = uniform(#x3a3a3e)
|
|
// The quad grows by the shadow's blur on every side, the room the bench
|
|
// gives a cap: the puck overhangs nothing and throws no glow, and the lip
|
|
// and the focus ring stay within it on a fader thinner than the puck.
|
|
mod.widgets.SliderFader.draw_bg.vertex = fn() {
|
|
let g = grow_vertex(self.rect_pos, self.rect_size, 12.0)
|
|
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
|
|
}
|
|
mod.widgets.SliderFader.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let m = 12.0
|
|
let size = self.rect_size
|
|
let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
let o = self.rect_pos
|
|
let live = 1.0 - self.disabled
|
|
let vert = self.is_vertical
|
|
// 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 at = foot + self.slide_pos * travel
|
|
// The channel: the puck's width in whole device pixels, its long walls
|
|
// on pixel boundaries so the lip is one crisp row, its ends round.
|
|
let oc = mix(o.y, o.x, vert)
|
|
let ww = max(floor(self.cap_px / px + 0.5), 2.0) * px
|
|
let w0 = Finish.snap(oc + (across - ww) * 0.5, px) - oc
|
|
let mid = w0 + ww * 0.5
|
|
let wbox = vec4(along * 0.5, mid, max(along * 0.5 - self.inset_px, ww * 0.5), ww * 0.5)
|
|
let wr = wbox.w
|
|
let depth = self.cap_well_depth
|
|
let l = normalize(self.cap_light)
|
|
let lt = Cap.light(l, vert)
|
|
let ground = self.ground_color.rgb
|
|
let light_ink = self.light_color.rgb
|
|
let shadow_ink = self.shadow_color.rgb
|
|
var col = Cap.well(ground, ab, wbox, wr, depth, lt, px, self.well_color.rgb, light_ink, shadow_ink, self.cap_inner, self.cap_ao, self.cap_shadow_blur, self.cap_shadow)
|
|
// The puck: round, its outline at `fp.x`.
|
|
let shape = self.cap_shape
|
|
let hs = vec2(self.cap_px * 0.5, self.cap_px * 0.5)
|
|
let q = ab - vec2(at, mid)
|
|
let r = shape.x * min(hs.x, hs.y)
|
|
let fp = Cap.sd_cap(q, hs, r, vec2(1.0, 0.0))
|
|
// Only a pixel within reach of the puck 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 {
|
|
// A press sinks the puck half a point into its channel.
|
|
let sink = 0.5 * self.drag * live
|
|
// The edge curve alone lofts the puck. The bench keeps its along
|
|
// and across curves flat at one, whose readings are (1, 0) at every
|
|
// point, and it has no ribs, so those three are passed as that.
|
|
let plain = vec2(1.0, 0.0)
|
|
let edge = Cap.curve(Cap.edge_x(fp.x, hs, shape.y), 3.0, vec2(0.0, 0.249), vec2(0.0407, 0.1252), vec2(-0.0813, 0.0), vec2(0.244, 1.0), vec2(0.0813, 0.0), vec2(-0.126, 0.0), vec2(1.0, 1.0), vec2(0.126, 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 f = Cap.field(q, hs, fp, plain, plain, edge, plain, shape, vec4(0.0, 0.0, 0.0, 1.0), vec4(0.0, 0.0, 0.0, 0.0), vec4(0.0, 0.5, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 0.0))
|
|
// Its surface height over the channel's floor, and how much of the
|
|
// sky the channel's walls leave it.
|
|
let z = max(f.x * shape.w - sink, 0.0)
|
|
let open = 1.0 - clamp((depth - z) / depth, 0.0, 1.0)
|
|
let body = self.handle_color.rgb
|
|
var 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, 0.0, open, 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_intensity, body, light_ink, shadow_ink, ground)
|
|
// Turned metal: a fan along the light, a fine grain round it.
|
|
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.in_well(cap, ab, z, wbox, wr, depth, lt, self.cap_ao, self.cap_inner, shadow_ink)
|
|
// Hover and focus lift the face, by as much as the sheet lifts its
|
|
// turned thumbs: the old block cap's 0.03 barely shows on bright
|
|
// metal. Disabled goes to the sheet's dimmed turned metal.
|
|
cap = cap + vec3(0.08, 0.08, 0.08) * max(self.hover, self.focus) * live
|
|
cap = mix(cap, mix(vec3(0.28, 0.28, 0.28), cap * 0.45, 0.5), self.disabled)
|
|
let cov = Cap.cover(fp.x, px)
|
|
// The shadow and the contact shade fall on the channel's floor and
|
|
// stay in it; `rise` is below zero while the puck is sunk.
|
|
let rise = shape.w - sink - depth
|
|
let in_floor = Cap.well_mask(ab, wbox, wr, depth, px).x
|
|
let sh = Cap.shadow(q, hs, r, vec3(0.8784, 0.8784, 1.0), lt, rise, self.cap_shadow_blur, self.cap_shadow, px) * mix(1.0, 0.4, self.disabled)
|
|
let ct = Cap.contact(fp.x, max(max(rise * 0.3, 1.0), depth * 0.45), min(self.cap_contact * 1.6, 1.0))
|
|
col = mix(col, col * shadow_ink, clamp(sh + ct, 0.0, 1.0) * (1.0 - cov) * in_floor)
|
|
col = col + body * Cap.glow(fp.x, hs, self.cap_glow) * 0.9 * (1.0 - cov)
|
|
col = mix(col, cap, cov)
|
|
}
|
|
// Laid over the plate without painting it: what is lighter than the
|
|
// plate adds its light, what is darker takes its share of the plate.
|
|
let ev = pow(2.0, self.cap_exposure)
|
|
let out = Cap.on_ground(Cap.tone(col, ev, self.cap_rolloff), Cap.tone(ground, ev, self.cap_rolloff))
|
|
return Finish.over(out, ml_focus(fp.x, px, self.focus * live))
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// TEXT FIELDS. A well 26 points tall: a dark top wall with the plate's
|
|
// shadow falling in over a few points, the lip on the last row of the box.
|
|
// Focus lights the mouth in the accent, inside the box, so a field needs
|
|
// no room outside itself.
|
|
let ml_field = fn(p: vec2, size: vec2, o: vec2, px: float, hover: float, focus: float, dis: float, r: float) -> vec4 {
|
|
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o
|
|
let hi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y, px)) - o - vec2(0.0, px)
|
|
let h = (hi - lo) * 0.5
|
|
let c = lo + h
|
|
let d = ml_rbox(p, c, h, r)
|
|
// the floor is cut, not painted: dark under the top wall, rising to the
|
|
// well's own shade by the middle and a little more at the foot
|
|
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.040, 0.038, 0.052), vec3(0.100, 0.097, 0.120), rise)
|
|
fill = mix(fill, vec3(0.106, 0.102, 0.125), clamp(t * 2.0 - 1.0, 0.0, 1.0))
|
|
fill = fill + g3(0.014 * hover * (1.0 - dis))
|
|
fill = mix(fill, vec3(0.106, 0.102, 0.125), dis)
|
|
fill = fill + g3(ml_grain((p + o) / px, 0.006))
|
|
var col = ml_well(p, c, h, r, px, fill, 2.0)
|
|
// focus: the mouth and a row inside it in the accent
|
|
let k = ml_acc()
|
|
col = mix(col, k, Finish.band(d, 0.0, px, px) * focus * 0.6)
|
|
let low = smoothstep(-0.5, 0.5, p.y - c.y)
|
|
col = mix(col, k, Finish.band(d, 0.0, px, px) * low * focus * 0.35)
|
|
let a = Finish.cover(d, px)
|
|
var out = ml_lip(p, c, h, r, px, 0.07)
|
|
return Finish.over(out, vec4(col * a, a))
|
|
}
|
|
|
|
mod.widgets.TextInputFlat.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 3.0 bottom: 3.0}
|
|
mod.widgets.TextInputFlat.draw_bg.pixel = fn() {
|
|
// The value field inside a slider or a well draws no box of its own.
|
|
if self.color.a < 0.01 {
|
|
return vec4(0.0, 0.0, 0.0, 0.0)
|
|
}
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
return ml_field(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, self.hover, self.focus, self.disabled, 2.0)
|
|
}
|
|
mod.widgets.TextInputFlat.draw_text.color = #c8cacf
|
|
mod.widgets.TextInputFlat.draw_text.color_hover = #xd7d9de
|
|
mod.widgets.TextInputFlat.draw_text.color_focus = #xd7d9de
|
|
mod.widgets.TextInputFlat.draw_text.color_empty = #6b6d75
|
|
mod.widgets.TextInputFlat.draw_text.color_disabled = #5c5b62
|
|
mod.widgets.TextInputFlat.draw_cursor.color = #x03e5fb
|
|
|
|
mod.widgets.NumberField.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
return ml_field(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, self.hover, self.focus, self.disabled, 2.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 ml_field(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, self.hover, self.focus, self.disabled, 2.0)
|
|
}
|
|
|
|
// The stepper: a raised machined column set into the well's end, split by
|
|
// an engraved line, each half with a small stroked chevron.
|
|
let ml_spin = fn(p: vec2, size: vec2, o: vec2, px: float, hot: float, down: float, dis: float) -> vec4 {
|
|
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o + vec2(0.0, 2.0 * px)
|
|
let hi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y, px)) - o - vec2(2.0 * px, 3.0 * px)
|
|
let h = (hi - lo) * 0.5
|
|
let c = lo + h
|
|
let d = ml_rbox(p, c, h, 1.5)
|
|
let live = 1.0 - dis
|
|
let t = clamp((p.y - lo.y) / max(2.0 * h.y, 1.0), 0.0, 1.0)
|
|
var face = ml_face(t)
|
|
let upper = step(p.y, c.y)
|
|
let hh = mix(max(0.0, -hot), max(0.0, hot), upper) * live
|
|
let dn = mix(max(0.0, -down), max(0.0, down), upper) * live
|
|
face = face + g3(0.03 * hh * (1.0 - dn))
|
|
face = mix(face, vec3(0.082, 0.078, 0.102), dn)
|
|
face = mix(face, vec3(0.122, 0.118, 0.141), dis)
|
|
let mid = Finish.snap(o.y + c.y, px) - o.y
|
|
face = mix(face, ml_bezel(), step(mid - px, p.y) * step(p.y, mid))
|
|
face = mix(face, vec3(0.231, 0.227, 0.251), step(mid, p.y) * step(p.y, mid + px) * live)
|
|
face = mix(face, vec3(0.231, 0.227, 0.251), Finish.band(d, px, 2.0 * px, px) * (1.0 - smoothstep(-0.5, 0.5, p.y - lo.y - 1.5)) * live)
|
|
face = mix(face, ml_bezel(), Finish.band(d, 0.0, px, px))
|
|
// the chevrons, 5 by 2.5 points, a 1.3 point stroke
|
|
let ink = mix(vec3(0.667, 0.667, 0.675), vec3(0.29, 0.286, 0.314), dis)
|
|
let cu = vec2(c.x, (lo.y + mid) * 0.5)
|
|
let cl = vec2(c.x, (mid + hi.y) * 0.5)
|
|
let chd = min(ml_chevron(p, cu, 5.0, 2.5, 1.0), ml_chevron(p, cl, 5.0, 2.5, 0.0)) - 0.65
|
|
face = mix(face, ink + g3(0.1 * hh), Finish.cover(chd, px))
|
|
let a = Finish.cover(d, px)
|
|
return vec4(face * a, a)
|
|
}
|
|
mod.widgets.NumberSpin.width = 16.0
|
|
mod.widgets.NumberSpin.draw_text.color = #00000000
|
|
mod.widgets.NumberSpin.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
return ml_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 ml_spin(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, self.hot, self.down, self.disabled)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// DROP-DOWNS. The raised face with an engraved split 22 points from its
|
|
// right end and a stroked chevron behind it; open is pressed.
|
|
mod.widgets.DropDownFlat.padding = mod.turtle.Inset{left: 10.0 right: 30.0 top: 3.0 bottom: 3.0}
|
|
mod.widgets.DropDown.padding = mod.turtle.Inset{left: 10.0 right: 30.0 top: 3.0 bottom: 3.0}
|
|
mod.widgets.DropDownFlat.clip_x = false
|
|
mod.widgets.DropDownFlat.clip_y = false
|
|
mod.widgets.DropDownFlat.draw_bg.vertex = fn() {
|
|
let g = grow_vertex(self.rect_pos, self.rect_size, 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 ml_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_bold{font_size: 8.25}
|
|
|
|
// The combo box: the well with a raised key joined at its right end.
|
|
mod.widgets.ComboBox.padding = mod.turtle.Inset{left: 8.0 right: 26.0 top: 3.0 bottom: 3.0}
|
|
mod.widgets.ComboBox.input.min_height = 0.0
|
|
mod.widgets.ComboBox.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let size = self.rect_size
|
|
let o = self.rect_pos
|
|
let live = 1.0 - self.disabled
|
|
var out = ml_field(p, size, o, px, self.hover, self.focus, self.disabled, 2.0)
|
|
let kx = Finish.snap(o.x + size.x - 20.0, px) - o.x
|
|
let hi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y, px)) - o - vec2(2.0 * px, 3.0 * px)
|
|
let klo = vec2(kx, Finish.snap(o.y, px) - o.y + 2.0 * px)
|
|
let kh = (hi - klo) * 0.5
|
|
let kc = klo + kh
|
|
let kd = ml_rbox(p, kc, kh, 1.5)
|
|
let t = clamp((p.y - klo.y) / max(2.0 * kh.y, 1.0), 0.0, 1.0)
|
|
let held = self.down * self.hover * live
|
|
var face = ml_face(t) + g3(0.026 * self.hover * live * (1.0 - held))
|
|
face = mix(face, vec3(0.082, 0.078, 0.102), held)
|
|
face = mix(face, vec3(0.122, 0.118, 0.141), self.disabled)
|
|
face = mix(face, vec3(0.231, 0.227, 0.251), step(klo.y, p.y) * step(p.y, klo.y + px) * (1.0 - held) * live)
|
|
face = mix(face, ml_bezel(), Finish.band(kd, 0.0, px, px))
|
|
let ka = Finish.cover(kd, px)
|
|
out = Finish.over(out, vec4(face * ka, ka))
|
|
let cc = vec2(kc.x, Finish.snap(o.y + kc.y, px) - o.y)
|
|
let ch = Finish.cover(ml_chevron(p, cc, 6.0, 3.5, 0.0) - 0.65, px)
|
|
let ink = mix(vec3(0.667, 0.667, 0.675), vec3(0.29, 0.286, 0.314), self.disabled)
|
|
return Finish.over(out, vec4(ink * ch, ch))
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// SEGMENTS AND TABS. The segmented control is a row of machined push
|
|
// buttons: the strip is raised in the buttons' own material (bezel, top
|
|
// line, foot line, a short shadow), and the chosen answer is pushed in deep
|
|
// between its neighbours. The tab strip is a row of machined segments on a
|
|
// tray; the tab in use keeps its face and takes a two device pixel accent
|
|
// line along its foot.
|
|
let ml_strip = fn(pos: vec2, size: vec2, o: vec2, px: float, m: float) -> vec4 {
|
|
let p = pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o
|
|
let hi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y, px)) - o
|
|
let h = (hi - lo) * 0.5
|
|
let c = lo + h
|
|
// the short shadow, held within the six points to the next row
|
|
let ink = ml_shadow_ink()
|
|
let soft = Finish.drop(p, c, h, 2.0, vec2(0.5, 2.0), 2.0) * 0.40
|
|
var face = ml_button(pos, size, o, px, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 2.0, 0.0, m)
|
|
// Down a column every answer is a cap of its own: each keeps the face's
|
|
// fall from top to foot, and where two meet the upper one's foot line,
|
|
// a bezel row and the lower one's top line. The answers are 26 points
|
|
// tall under this sheet (its size_control_m) and start two points in;
|
|
// a strip one answer tall has no join, and draws as above.
|
|
let sh = 26.0
|
|
let n = floor((size.y - 4.0) / sh + 0.5)
|
|
let col = step(abs(size.y - 4.0 - n * sh), 0.5) * step(1.5, n)
|
|
let kc = clamp(floor((p.y - 2.0) / sh), 0.0, max(n - 1.0, 0.0))
|
|
let t0 = clamp((p.y - lo.y) / max(2.0 * h.y, 1.0), 0.0, 1.0)
|
|
let tc = clamp((p.y - kc * sh) / (sh + 4.0), 0.0, 1.0)
|
|
let bez = 2.0 * px
|
|
let inx = step(lo.x + bez, p.x) * (1.0 - step(hi.x - bez, p.x))
|
|
let iny = step(lo.y + bez, p.y) * (1.0 - step(hi.y - bez, p.y))
|
|
let fall = (ml_face(tc) - ml_face(t0)) * inx * iny * col * face.a
|
|
face = vec4(face.rgb + fall, face.a)
|
|
let kb = floor((p.y - 2.0) / sh + 0.5)
|
|
let yb = Finish.snap(o.y + 2.0 + kb * sh, px) - o.y
|
|
let join = inx * step(0.5, kb) * step(kb, n - 0.5) * col
|
|
let r_foot = step(yb - px, p.y) * (1.0 - step(yb, p.y)) * join
|
|
let r_bez = step(yb, p.y) * (1.0 - step(yb + px, p.y)) * join
|
|
let r_top = step(yb + px, p.y) * (1.0 - step(yb + 2.0 * px, p.y)) * join
|
|
face = Finish.over(face, vec4(vec3(0.031, 0.031, 0.039) * r_foot, r_foot))
|
|
face = Finish.over(face, vec4(ml_bezel() * r_bez, r_bez))
|
|
face = Finish.over(face, vec4(vec3(0.231, 0.227, 0.251) * r_top, r_top))
|
|
return Finish.over(vec4(ink * soft, soft), face)
|
|
}
|
|
|
|
// The chosen answer, pushed in: a well the segment's width that runs from
|
|
// under the strip's top bezel to above its bottom bezel (the strip lays its
|
|
// answers two points in), black under the top edge and shaded along both
|
|
// walls, with the neighbours' ends standing lit over it. Under the label a
|
|
// small lamp in the accent, the milled LED dash, its light pooling on the
|
|
// floor and reaching the well's foot.
|
|
let ml_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 + 2.0 * px
|
|
let y1 = Finish.snap(o.y + size.y + 2.0, px) - o.y - 2.0 * px
|
|
let inside = step(x0, p.x) * (1.0 - step(x1, p.x)) * step(y0, p.y) * (1.0 - step(y1, p.y))
|
|
let t = clamp((p.y - y0) / max(y1 - y0, 1.0), 0.0, 1.0)
|
|
// the floor
|
|
var col = mix(vec3(0.118, 0.122, 0.141), vec3(0.098, 0.102, 0.118), t)
|
|
col = col + g3(ml_grain((p + o) / px, 0.008))
|
|
// the shade: black under the top edge, gone in about six rows; along
|
|
// each wall a shade gone in four; a darker last row at the foot
|
|
let top = exp(-max(p.y - y0, 0.0) / (2.2 * px))
|
|
let wl = 0.8 * exp(-max(p.x - x0, 0.0) / (1.4 * px))
|
|
let wr = 0.8 * exp(-max(x1 - px - p.x, 0.0) / (1.4 * px))
|
|
let foot = 0.45 * step(y1 - px, p.y)
|
|
let dk = 1.0 - (1.0 - top) * (1.0 - wl) * (1.0 - wr) * (1.0 - foot)
|
|
col = mix(col, vec3(0.012, 0.012, 0.020), clamp(dk, 0.0, 1.0))
|
|
// the lamp's light on the floor: a broad soft pool round the lamp
|
|
let k = ml_acc()
|
|
let lc = vec2(Finish.snap(o.x + (x0 + x1) * 0.5, px) - o.x, Finish.snap(o.y + size.y, px) - o.y - 6.0 * px)
|
|
let q = vec2((p.x - lc.x) / max((x1 - x0) * 0.26, 1.0), (p.y - lc.y) / 3.4)
|
|
let pool = exp(-dot(q, q)) * 0.26 * (1.0 - 0.6 * top)
|
|
col = mix(col, k * 0.85, pool)
|
|
var res = vec4(col, 1.0) * inside
|
|
// the neighbours' ends over the well: one lit column each, down to the
|
|
// strip's foot line
|
|
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.263, 0.259, 0.282), vec3(0.176, 0.173, 0.196), t)
|
|
res = Finish.over(res, vec4(lit, 1.0) * (el + er) * ey)
|
|
// the lamp: two device pixel rows sixteen long, the top row white-hot,
|
|
// the lower one the accent, in a tight halo on the floor
|
|
let ld = ml_rbox(p, lc, vec2(8.0 * px, px), px)
|
|
let la = Finish.cover(ld, px)
|
|
let hot = mix(mix(k, vec3(1.0, 1.0, 1.0), 0.70), mix(k, vec3(1.0, 1.0, 1.0), 0.25), step(lc.y, p.y))
|
|
let near = 0.42 * exp(-max(ld, 0.0) / (1.1 * px)) + 0.14 * exp(-max(ld, 0.0) / (3.0 * px))
|
|
let ha = clamp(near, 0.0, 1.0) * (1.0 - la) * step(0.0, ld)
|
|
res = Finish.over(res, vec4(k * ha, ha) * inside)
|
|
res = Finish.over(res, vec4(hot * la, la) * inside)
|
|
return res
|
|
}
|
|
|
|
mod.widgets.SegmentedControlFlat.draw_bg.vertex = fn() {
|
|
let g = grow_vertex(self.rect_pos, self.rect_size, 6.0)
|
|
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
|
|
}
|
|
mod.widgets.SegmentedControlFlat.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
return ml_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 ml_push_well(p, self.rect_size, self.rect_pos, px)
|
|
}
|
|
mod.widgets.SegmentedControlFlat.draw_text.color = #9a9aa0
|
|
mod.widgets.SegmentedControlFlat.draw_text_selected.color = #eceef2
|
|
mod.widgets.SegmentedControlFlat.palette.on_neutral_container = #eceef2
|
|
|
|
// 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
|
|
// an engraved rule along the strip's foot: dark over light
|
|
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.06
|
|
let bz = ml_bezel()
|
|
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 = ml_rbox(p, self.rect_size * 0.5 - vec2(0.0, px), h, 2.0)
|
|
let a = Finish.cover(d, px) * 0.05
|
|
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
|
|
var out = ml_segment(p, self.rect_size - vec2(0.0, 2.0 * px), o, px, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
|
|
// the accent line along the foot, two device pixels, no halo
|
|
let yb = Finish.snap(o.y + self.rect_size.y, px) - o.y
|
|
let inx = step(3.0, p.x) * step(p.x, self.rect_size.x - 3.0)
|
|
let ln = step(yb - 2.0 * px, p.y) * step(p.y, yb) * inx
|
|
let k = ml_acc()
|
|
return Finish.over(out, vec4(k * 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.07 * in_y
|
|
let bz = ml_bezel()
|
|
return Finish.over(vec4(bz * dk, dk), vec4(lt, lt, lt, lt))
|
|
}
|
|
mod.widgets.Tabs.draw_mark.color = #x8e8e94
|
|
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
|
|
// 7 points across, a 1 point stroke; the plus is the cross turned
|
|
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(ml_seg(p, c - u, c + u), ml_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 = #x8e8e94
|
|
mod.widgets.Tabs.draw_text_active.color = #c8cacf
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// PROGRESS. The slider's well, six points; the value is the slider's own
|
|
// three device-pixel line (light rim, core, dark rim), continuous. LED
|
|
// cells belong to the level meter only. 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
|
|
// the well spans the whole bar; a stacked segment (drawn with no track)
|
|
// is only its piece of the line
|
|
let bx0 = Finish.snap(o.x, px) - o.x
|
|
let bx1 = Finish.snap(o.x + w, px) - o.x
|
|
let h = vec2((bx1 - bx0) * 0.5, th * 0.5)
|
|
let c = vec2(bx0, top) + h
|
|
let r = 2.0
|
|
let d = ml_rbox(p, c, h, r)
|
|
let dis = step(4.5, self.intent)
|
|
var ink = ml_fill()
|
|
if self.intent > 0.5 {
|
|
ink = self.fill_color().rgb
|
|
}
|
|
ink = mix(ink, ml_off(), dis)
|
|
let col = ml_well(p, c, h, r, px, vec3(0.082, 0.078, 0.102), 1.6)
|
|
let a = Finish.cover(d, px) * self.track
|
|
var out = ml_lip(p, c, h, r, px, 0.07 * self.track)
|
|
out = Finish.over(out, vec4(col * a, a))
|
|
// the value: the slider's line, rows 4, 5 and 6 of the nine (light rim,
|
|
// core, dark rim), continuous from 2 dpx in; a segment after the first
|
|
// leaves one dark device pixel before it
|
|
let row = floor((p.y - top) / px)
|
|
let r2 = step(2.5, row) * step(row, 3.5)
|
|
let r3 = step(3.5, row) * step(row, 4.5)
|
|
let r4 = step(4.5, row) * step(row, 5.5)
|
|
let span = bx1 - bx0 - 4.0 * px
|
|
let lx0 = Finish.snap(o.x + bx0 + 2.0 * px + span * self.start, px) - o.x + px * step(0.001, self.start)
|
|
let lx1 = Finish.snap(o.x + bx0 + 2.0 * px + span * self.end, px) - o.x
|
|
let xv = Finish.snap(o.x + bx0 + 2.0 * px + span * clamp(self.value, self.start, self.end), px) - o.x
|
|
var on = step(lx0, p.x) * step(p.x, xv) * step(0.0, self.value)
|
|
if self.value < 0.0 {
|
|
// the unknown value: still dashes 8 dpx long on a 16 dpx pitch at
|
|
// 40 % of the line
|
|
let sx = floor((p.x - lx0) / px)
|
|
on = step(0.0, sx) * step(fract(sx / 16.0), 0.49) * 0.4 * step(p.x, lx1)
|
|
}
|
|
var lit = ml_rim_hi() * r2 + ml_fill() * r3 + ml_rim_lo() * r4
|
|
if self.intent > 0.5 {
|
|
lit = mix(ink, vec3(1.0, 1.0, 1.0), 0.35) * r2 + ink * r3 + ink * 0.55 * r4
|
|
}
|
|
lit = mix(lit, ml_off() * (1.0 + 0.2 * r2), dis)
|
|
let la = (r2 + r3 + r4) * on
|
|
out = Finish.over(out, vec4(lit * la, la))
|
|
return out * self.opacity
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// LEVEL METERS. A ladder of cells in a well: the accent up to -6 dB, amber
|
|
// to -2, red above, all plain with no halo; unlit cells keep a tenth of
|
|
// their light. The over lamp is a red bead, dark until it latches.
|
|
mod.widgets.LevelMeter.height = 6.0
|
|
mod.widgets.LevelMeterColumn.width = 6.0
|
|
mod.widgets.LevelMeter.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let vert = self.vertical
|
|
let along = mix(self.rect_size.x, self.rect_size.y, vert)
|
|
let across = mix(self.rect_size.y, self.rect_size.x, vert)
|
|
let a = mix(p.x, self.rect_size.y - p.y, vert)
|
|
let b = mix(p.y, p.x, vert)
|
|
let q = vec2(a, b)
|
|
let lamp_room = self.lamp * (across + 3.0)
|
|
let bar = max(4.0, along - lamp_room)
|
|
let h = vec2(bar * 0.5, across * 0.5 - px * 0.5)
|
|
let c = h
|
|
let r = 1.5
|
|
let d = ml_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)
|
|
// cells three points long with a point between
|
|
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(ml_fill(), vec3(0.996, 0.663, 0.098), step(0.70, share)), vec3(0.918, 0.392, 0.431), step(0.88, share))
|
|
var col = ml_well(q, c, h, r, px, vec3(0.063, 0.059, 0.078), 1.2)
|
|
col = mix(col, mix(col, zone, 0.10), cell_row)
|
|
col = mix(col, zone, lit)
|
|
// the hold mark: one cell fully 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(ml_lip(q, c, h, r, px, 0.07), vec4(col * ca, ca))
|
|
if self.lamp > 0.5 {
|
|
let la = bar + 3.0 + across * 0.5
|
|
let lb = across * 0.5
|
|
let lc = vec2(mix(la, lb, vert), mix(lb, self.rect_size.y - la, vert))
|
|
out = Finish.over(out, ml_bead(p, lc, min(across * 0.5 - px, 3.0), px, vec3(0.918, 0.392, 0.431), step(0.5, self.over)))
|
|
}
|
|
return out * self.opacity
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// SCROLL BARS. An 8 point well with a 6 point machined thumb in it; a held
|
|
// thumb shows an accent centre line.
|
|
mod.widgets.ScrollBar.bar_size = 8.0
|
|
mod.widgets.ScrollBar.bar_side_margin = 0.0
|
|
mod.widgets.ScrollBar.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let vert = self.is_vertical
|
|
let a = mix(p.x, p.y, vert)
|
|
let b = mix(p.y, p.x, vert)
|
|
let along = mix(self.rect_size.x, self.rect_size.y, vert)
|
|
let across = mix(self.rect_size.y, self.rect_size.x, vert)
|
|
let q = vec2(a, b)
|
|
let sh = vec2(along * 0.5, across * 0.5)
|
|
let sc = sh
|
|
let sd = ml_rbox(q, sc, sh, 3.0)
|
|
// the well, shaded from the top (or left) wall
|
|
let wall = 1.0 - smoothstep(0.0, 2.0, b)
|
|
var col = mix(vec3(0.082, 0.078, 0.102), vec3(0.039, 0.035, 0.059), wall * 0.8)
|
|
let sa = Finish.cover(sd, px)
|
|
var out = vec4(col * sa, sa)
|
|
// the thumb
|
|
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 = ml_rbox(q, tc, th, 3.0)
|
|
let tt = clamp((b - (tc.y - th.y)) / 6.0, 0.0, 1.0)
|
|
var face = mix(vec3(0.169, 0.165, 0.188), vec3(0.122, 0.118, 0.141), tt)
|
|
face = face + g3(0.032 * self.hover)
|
|
face = mix(face, vec3(0.231, 0.227, 0.251), Finish.band(td, 0.0, px, px) * (1.0 - smoothstep(0.3, 0.6, tt)))
|
|
face = mix(face, vec3(0.031, 0.031, 0.039), 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, ml_acc(), cl * self.drag * 0.6)
|
|
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 metal: a soft shadow outside
|
|
// it, a black edge, a lit top line and a dark foot line, horizontal
|
|
// brushing and the plate's grain. The sunken panel is a machined pocket:
|
|
// a bright lip on the plate above its top edge, a dark top wall, a floor
|
|
// darkening downward and dressed with a fine crosshatch, a lit lip inside
|
|
// its foot.
|
|
let ml_plate = fn(p: vec2, size: vec2, o: vec2, px: float, r: 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 d = ml_rbox(p, c, h, r)
|
|
let ink = ml_shadow_ink()
|
|
var out = vec4(0.0, 0.0, 0.0, 0.0)
|
|
let soft = Finish.drop(p, c, h, r, vec2(0.0, 2.0), 2.4) * 0.45 * (1.0 - sunken)
|
|
out = Finish.over(out, vec4(ink * soft, soft))
|
|
let con = Finish.drop(p, c, h, r, vec2(0.0, px), px) * 0.5 * (1.0 - sunken)
|
|
out = Finish.over(out, vec4(ink * con, con))
|
|
// the bright lip above a pocket's top edge, on the plate
|
|
let upo = 1.0 - smoothstep(c.y - h.y + max(r, 1.0), c.y - h.y + max(r, 1.0) + 2.0, p.y)
|
|
let lip = Finish.ring_out(d, 0.0, px, px) * upo * 0.12 * sunken
|
|
out = Finish.over(out, vec4(lip, lip, lip, lip))
|
|
let t = clamp((p.y - lo.y) / max(2.0 * h.y, 1.0), 0.0, 1.0)
|
|
let q = p + o
|
|
// raised: #2a2c31 falling a little, brushed along x
|
|
var face = mix(vec3(0.173, 0.180, 0.200), vec3(0.153, 0.161, 0.180), t)
|
|
let br = Finish.brushed(q, vec2(1.0, 0.0), px * 1.2) - 0.5
|
|
face = face + g3(br * 0.028 + ml_grain(q / px, 0.020))
|
|
// sunken: a floor darkening downward, a crosshatch of 1 dpx lines on a
|
|
// three pixel pitch, and the dark top wall
|
|
let i = floor(q / px)
|
|
let xa = step(fract((i.x + i.y) / 3.0), 0.2)
|
|
let xb = step(fract((i.x - i.y + 3000.0) / 3.0), 0.2)
|
|
let hatch = max(xa, xb)
|
|
var floor_c = mix(vec3(0.086, 0.090, 0.106), vec3(0.067, 0.071, 0.086), t)
|
|
floor_c = floor_c + g3(0.026 * hatch - 0.009 + ml_grain(q / px, 0.010))
|
|
let wall = min(h.y * 2.0 * 0.06, 6.0)
|
|
let yi = p.y - lo.y
|
|
floor_c = mix(vec3(0.039, 0.039, 0.039), floor_c, smoothstep(0.0, wall, yi))
|
|
let insh = Finish.inset(p, c, h, r, vec2(0.0, 1.6), 2.2)
|
|
floor_c = mix(floor_c, vec3(0.020, 0.020, 0.028), clamp(insh, 0.0, 1.0) * 0.7)
|
|
face = mix(face, floor_c, sunken)
|
|
let up = 1.0 - smoothstep(-0.5, 0.5, p.y - c.y)
|
|
let topw = 1.0 - smoothstep(lo.y + r + 0.5, lo.y + r + 1.5, p.y)
|
|
let low = smoothstep(hi.y - r - 1.5, hi.y - r - 0.5, p.y)
|
|
// raised: lit top line, dark foot line inside a black edge
|
|
face = mix(face, vec3(0.231, 0.239, 0.259), Finish.band(d, px, 2.0 * px, px) * topw * (1.0 - sunken))
|
|
face = mix(face, vec3(0.082, 0.086, 0.098), Finish.band(d, px, 2.0 * px, px) * low * (1.0 - sunken))
|
|
// sunken: the lit lip inside the foot
|
|
face = mix(face, vec3(0.227, 0.235, 0.255), Finish.band(d, 0.0, px, px) * smoothstep(c.y + h.y - max(r, 1.0) - 1.5, c.y + h.y, p.y) * sunken)
|
|
face = mix(face, ml_bezel(), Finish.band(d, 0.0, px, px) * mix(1.0, up * 0.8, sunken))
|
|
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 ml_plate(p, self.rect_size, self.rect_pos, px, 4.0, self.sunken, 1.0)
|
|
}
|
|
|
|
// The card is the raised plate too; its palette and shadow numbers from
|
|
// Rust are not read.
|
|
mod.widgets.Card.radius = 4.0
|
|
// Header, body and footer share the panels' 12 point inset.
|
|
mod.widgets.Card.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 8.0 bottom: 8.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 = ml_plate(p, self.rect_size, self.rect_pos, px, 4.0, 0.0, 1.0)
|
|
let d = ml_rbox(p, self.rect_size * 0.5, self.rect_size * 0.5, 4.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 at the true radius.
|
|
mod.widgets.RoundedView.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
var fill = self.color
|
|
if self.color_2.x > -0.5 {
|
|
fill = mix(self.color, self.color_2, mix(self.pos.y, self.pos.x, self.gradient_fill_horizontal))
|
|
}
|
|
let lo = vec2(self.border_inset.x, self.border_inset.y)
|
|
let hi = self.rect_size - vec2(self.border_inset.z, self.border_inset.w)
|
|
let h = (hi - lo) * 0.5
|
|
let c = lo + h
|
|
let d = ml_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 lifts more with a lit top line, a dark foot and a one
|
|
// device pixel accent bar at its head.
|
|
mod.widgets.ListItemOne.height = 22.0
|
|
mod.widgets.ListItem.padding = mod.turtle.Inset{left: 12.0 right: 8.0 top: 0.0 bottom: 0.0}
|
|
mod.widgets.ListItem.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let o = self.rect_pos
|
|
let live = 1.0 - self.disabled
|
|
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o
|
|
let hi = vec2(Finish.snap(o.x + self.rect_size.x, px), Finish.snap(o.y + self.rect_size.y, px)) - o
|
|
let lay = (self.hover * 0.025 - self.down * 0.04) * live
|
|
var 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, 0.0)
|
|
let c = lo + (hi - lo) * 0.5 - vec2(5.0, 0.0)
|
|
let d = ml_rbox(p, c, h, 2.0)
|
|
let t = clamp((p.y - lo.y) / max(2.0 * h.y, 1.0), 0.0, 1.0)
|
|
var col = mix(vec3(0.200, 0.204, 0.227), vec3(0.176, 0.180, 0.200), t)
|
|
let up = 1.0 - smoothstep(-0.5, 0.5, p.y - c.y)
|
|
col = mix(col, vec3(0.290, 0.294, 0.318), Finish.band(d, 0.0, px, px) * up)
|
|
col = mix(col, vec3(0.043, 0.039, 0.063), Finish.band(d, 0.0, px, px) * (1.0 - up))
|
|
let bar = step(lo.x + px, p.x) * step(p.x, lo.x + 2.0 * px) * step(lo.y + 3.0, p.y) * step(p.y, hi.y - 3.0)
|
|
col = mix(col, ml_acc(), bar * live)
|
|
let a = Finish.cover(d, px)
|
|
out = Finish.over(out, vec4(col * a, a))
|
|
}
|
|
// the hairline under the row
|
|
let yb = hi.y
|
|
let lt = step(yb - px, p.y) * step(p.y, yb) * 0.04
|
|
return Finish.over(out, vec4(lt, lt, lt, lt))
|
|
}
|
|
mod.widgets.ListItem.draw_text.color = #b4b5ba
|
|
|
|
// The open list of a drop-down: a raised sheet; the row under the pointer
|
|
// becomes a well with an accent bar at its head, the chosen row carries a
|
|
// small accent dot.
|
|
mod.widgets.PopupMenuFlat.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
return ml_plate(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, 3.0, 0.0, 1.0)
|
|
}
|
|
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 = ml_rbox(p, c, h, 2.0)
|
|
let live = 1.0 - self.disabled
|
|
let hv = self.hover * live
|
|
let col = ml_well(p, c, h, 2.0, px, vec3(0.082, 0.078, 0.102), 2.0)
|
|
let a = Finish.cover(d, px) * hv
|
|
var out = vec4(col * a, a)
|
|
let bar = step(1.0 + px, p.x) * step(p.x, 1.0 + 2.0 * px) * step(4.0, p.y) * step(p.y, self.rect_size.y - 4.0) * hv
|
|
let k = ml_acc()
|
|
out = Finish.over(out, vec4(k * bar, bar))
|
|
let dt = Finish.cover(ml_rbox(p, vec2(6.0, c.y), vec2(1.1, 1.1), 0.3), 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, rounded at the top, lit along its top edge.
|
|
mod.widgets.Tab.padding = mod.turtle.Inset{left: 10.0 right: 10.0 top: 3.0 bottom: 3.0}
|
|
mod.widgets.Tab.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let o = self.rect_pos
|
|
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y + 2.0, px)) - o
|
|
let hi = vec2(Finish.snap(o.x + self.rect_size.x, px), Finish.snap(o.y + self.rect_size.y, px)) - o + vec2(0.0, 4.0)
|
|
let h = (hi - lo) * 0.5
|
|
let c = lo + h
|
|
let d = ml_rbox(p, c, h, 3.0)
|
|
var face = vec3(0.173, 0.180, 0.200) + g3(ml_grain((p + o) / px, 0.012))
|
|
face = mix(face, vec3(0.231, 0.239, 0.259), Finish.band(d, px, 2.0 * px, px) * step(p.y, lo.y + 3.0))
|
|
face = mix(face, ml_bezel(), Finish.band(d, 0.0, px, px))
|
|
let a = Finish.cover(d, px) * self.active
|
|
let lift = Finish.cover(d, px) * 0.04 * 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.18)
|
|
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.05
|
|
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 raised face; a chosen chip is
|
|
// lit. A badge is a small plate 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(20.0, self.rect_size.y)
|
|
let dy = (self.rect_size.y - ch) * 0.5
|
|
let sz = vec2(self.rect_size.x, ch)
|
|
let pp = (p - vec2(0.0, dy) + vec2(m, m)) / (sz + vec2(2.0 * m, 2.0 * m))
|
|
// chosen: the fill carries colour
|
|
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)
|
|
// a pale fill carries dark ink, so it is drawn as a pale key too
|
|
let lw = vec3(0.2126, 0.7152, 0.0722)
|
|
let pale = step(dot(self.ink.rgb, lw), 0.35)
|
|
// a pale neutral fill under light ink is a chosen chip: the key is held in
|
|
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 ml_button(pp, sz, o + vec2(0.0, dy), px, self.hover, max(self.down, held), self.keyed, 0.0, 0.0, 0.0, 2.0, 1.0, m)
|
|
}
|
|
// a coloured chip is lit in its own colour, so a role keeps its meaning
|
|
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 = ml_rbox(q, c, h, 2.0)
|
|
let t = clamp((q.y - lo.y) / max(2.0 * h.y, 1.0), 0.0, 1.0)
|
|
let yi = q.y - lo.y - 2.0 * px
|
|
let k = mix(self.color.rgb, vec3(0.62, 0.63, 0.66), 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 = 0.25 * chosen * exp(-max(d - px, 0.0) / (0.6 * px)) * step(0.0, d)
|
|
var out = vec4(k * ha, ha)
|
|
let a = Finish.cover(d, px)
|
|
out = Finish.over(out, vec4(face * a, a))
|
|
return Finish.over(out, ml_focus(d, px, self.keyed))
|
|
}
|
|
mod.widgets.BadgePalette.neutral = #15141a
|
|
mod.widgets.BadgePalette.on_neutral = #3fc9dc
|
|
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 plate
|
|
let r = mix(2.0, h.y, step(self.rect_size.x, self.rect_size.y + 0.5) * step(self.rect_size.y, 12.5))
|
|
let d = ml_rbox(p, c, h, r)
|
|
let col = ml_well(p, c, h, r, px, vec3(0.059, 0.067, 0.075), 2.0)
|
|
let a = Finish.cover(d, px) * self.color.a
|
|
var out = ml_lip(p, c, h, r, px, 0.07)
|
|
return Finish.over(out, vec4(col * a, a))
|
|
}
|
|
|
|
// Rules between groups: one dark device row over one light one.
|
|
mod.widgets.Hr.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let y = Finish.snap(self.rect_pos.y + self.rect_size.y * 0.5, px) - self.rect_pos.y
|
|
let dk = step(y - px, p.y) * (1.0 - step(y, p.y)) * 0.45
|
|
let lt = step(y, p.y) * (1.0 - step(y + px, p.y)) * 0.07
|
|
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.45
|
|
let lt = step(x, p.x) * (1.0 - step(x + px, p.x)) * 0.07
|
|
return Finish.over(vec4(0.0, 0.0, 0.0, dk), vec4(lt, lt, lt, lt))
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// THE PLATE under everything: the window's own ground.
|
|
mod.widgets.Window.show_bg = true
|
|
mod.widgets.Window.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
return vec4(ml_ground(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 = 12.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 = 6.0
|
|
let cy = size.y - tr - 1.0
|
|
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(ml_track(p, vec2(x0, top), w, px, x0, self.disabled, hot), ml_track(p, vec2(x0, top), w, px, k1, self.disabled, hot), step(k0, p.x))
|
|
let f0 = ml_fit(vec2(k0, top + 3.5 * px), o, tr, px)
|
|
let f1 = ml_fit(vec2(k1, top + 3.5 * px), o, tr, px)
|
|
out = Finish.over(out, ml_disc(p, f0.xy, f0.z, px, self.hot_start * live, self.drag * self.hot_start * live, self.disabled))
|
|
out = Finish.over(out, ml_thumb_dot(p, f0.xy, f0.z * 0.35, px, max(self.focus, self.drag * self.hot_start) * live))
|
|
out = Finish.over(out, ml_disc(p, f1.xy, f1.z, px, self.hot_end * live, self.drag * self.hot_end * live, self.disabled))
|
|
out = Finish.over(out, ml_thumb_dot(p, f1.xy, f1.z * 0.35, px, max(self.focus, self.drag * self.hot_end) * live))
|
|
return Finish.over(out, ml_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 ml_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 ml_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 ml_led_lamp_hue = fn(ink: vec3, vivid: float) -> vec3 {
|
|
let f = ml_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 ml_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 = ml_led_lamp_full(ink)
|
|
let hue = ml_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 = ml_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, polished on its upper left.
|
|
// 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 = ml_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.149, 0.157, 0.176), max(room - 0.5, 2.0), vec3(0.62, 0.63, 0.66), 2.0)
|
|
return res * self.opacity
|
|
}
|
|
true
|