2715 lines
150 KiB
Text
2715 lines
150 KiB
Text
use mod.prelude.widgets_internal.*
|
|
use mod.sdf.Cap
|
|
|
|
// Dark gloss with LED. Every face here is drawn in true points (one device
|
|
// pixel is `px`), and every shadow, lip and glow is drawn OUTSIDE the layout
|
|
// box: the quad is grown in `vertex`, the templates that throw a shadow stop
|
|
// clipping, and no padding is spent on relief.
|
|
|
|
// ---------------------------------------------------------------- helpers
|
|
|
|
// One device pixel in points.
|
|
let gl_px = fn(dpi: float) -> float {
|
|
return 1.0 / max(dpi, 0.5)
|
|
}
|
|
|
|
// An edge rounded to a whole device pixel in window space, handed back in
|
|
// box space: a hairline on it lands on one pixel row.
|
|
let gl_snap = fn(v: float, o: float, px: float) -> float {
|
|
return Finish.snap(o + v, px) - o
|
|
}
|
|
|
|
// Distance from p to the segment a..b, in points.
|
|
let gl_seg = fn(p: vec2, a: vec2, b: vec2) -> float {
|
|
let pa = p - a
|
|
let ba = b - a
|
|
let t = clamp(dot(pa, ba) / max(dot(ba, ba), 0.0001), 0.0, 1.0)
|
|
return length(pa - ba * t)
|
|
}
|
|
|
|
// A raised cap: the drop and contact shadows under it, the black outline,
|
|
// the quiet face gradient, the one top line, the faint side light, the
|
|
// bottom inner rim. `lift` 1 casts the drop shadow, 0 seats the cap on the
|
|
// ground with the contact shadow only. `rim` is the bottom inner rim's ink
|
|
// (the LED's spill on a lit cap). Premultiplied.
|
|
let gl_cap = fn(p: vec2, lo: vec2, hi: vec2, r: float, px: float, hover: float, down: float, disabled: float, focus: float, lift: float, tint: vec3, rim: vec4, gloss: float) -> vec4 {
|
|
let c = (lo + hi) * 0.5
|
|
let h = (hi - lo) * 0.5
|
|
let hh = hi.y - lo.y
|
|
// The shadow is sized by the cap's height up to a 24 point cap and no
|
|
// further, so a large knob still throws at most 6 points.
|
|
let sh_h = min(hh, 24.0)
|
|
let d = Material.sd_box(p, c, h, r)
|
|
let up = (1.0 - down) * (1.0 - disabled)
|
|
|
|
// What the cap throws on the ground: a short drop that falls down with
|
|
// a negative spread, and a tight contact line under the outline.
|
|
// Two layers read off the reference cap: a firm edge 0.11H down whose
|
|
// sides show for barely a point, and a soft tail to 0.28H.
|
|
let drop1 = Finish.drop(p, c, h - vec2(0.05 * sh_h, 0.01 * sh_h), r, vec2(0.0, 0.11 * sh_h), 0.055 * sh_h) * 0.72
|
|
let drop2 = Finish.drop(p, c, h - vec2(0.1 * sh_h, 0.06 * sh_h), r, vec2(0.0, 0.14 * sh_h), 0.1 * sh_h) * 0.18
|
|
let drop = (1.0 - (1.0 - drop1) * (1.0 - drop2)) * lift * up
|
|
let contact = Finish.drop(p, c, h, r, vec2(0.0, px), px * 0.6) * 0.85 * (1.0 - 0.6 * disabled)
|
|
let sh = 1.0 - (1.0 - drop) * (1.0 - contact)
|
|
var under = vec4(0.0, 0.0, 0.0, sh)
|
|
// A held cap sits in the ground: a 1 dp lip #474c56 shows on the ground
|
|
// right under its lower edge.
|
|
let lip = Finish.ring_out(d, 0.0, px, px) * step(c.y + h.y * 0.5, p.y) * down * (1.0 - disabled)
|
|
under = Finish.over(under, vec4(0.278, 0.298, 0.337, 1.0) * lip)
|
|
|
|
// The face.
|
|
let t = clamp((p.y - lo.y) / max(hh, 1.0), 0.0, 1.0)
|
|
var face = mix(vec3(0.247, 0.255, 0.290), vec3(0.165, 0.173, 0.204), t) + tint
|
|
// Under the hand the face lifts; a small cap (a check box, a radio, a
|
|
// thumb, a handle) lifts twice as far, or the change is lost on it.
|
|
let small = 1.0 - step(23.5, hh)
|
|
face = face + vec3(0.02, 0.02, 0.022) * (1.0 + small) * hover * up
|
|
// Held or chosen: sunk like the chosen cell of the segmented strip -- a
|
|
// floor that falls a little, and a shade under the top edge that fades
|
|
// over a fifth of the height. `tint` casts a chosen well blue.
|
|
let shade = 0.5 * (1.0 - smoothstep(0.0, 0.2 * min(hh, 24.0), p.y - lo.y - px))
|
|
let held = mix(mix(vec3(0.169, 0.176, 0.204), vec3(0.141, 0.149, 0.173), t) + tint, vec3(0.0, 0.0, 0.0), shade)
|
|
face = mix(face, held, down * (1.0 - disabled))
|
|
face = mix(face, vec3(0.200, 0.208, 0.235), disabled)
|
|
|
|
// Which way the outline faces here: the lines below follow it, so a
|
|
// round cap gets an arc where a square one gets a straight line.
|
|
let s = vec2(step(0.0, p.x - c.x) * 2.0 - 1.0, step(0.0, p.y - c.y) * 2.0 - 1.0)
|
|
let q = abs(p - c) - (h - vec2(r, r))
|
|
let qc = max(q, vec2(0.0, 0.0))
|
|
var n = vec2(0.0, s.y)
|
|
if q.x > q.y {
|
|
n = vec2(s.x, 0.0)
|
|
}
|
|
if q.x > 0.0 && q.y > 0.0 {
|
|
n = qc / max(length(qc), 0.0001) * s
|
|
}
|
|
let inner = Finish.band(d, px, px * 2.0, px)
|
|
// The one top line: 1 dp inside the top outline, full along the top and
|
|
// gone by the time the edge has turned 60 degrees down.
|
|
// Under the hand the line brightens to #7a7c84 (+0.06) on a button,
|
|
// #86888f on a small cap.
|
|
// Lacquer (`gloss` 1: buttons, thumbs, knob caps, the segmented caps;
|
|
// 0 keeps the matte cap): a soft sheen across the upper part of a raised
|
|
// face, +0.036 at the top, its lower edge rising a little toward the
|
|
// sides and gone by half the height. A held or disabled cap is matte.
|
|
let uv = clamp((p - lo) / max(hi - lo, vec2(1.0, 1.0)), vec2(0.0, 0.0), vec2(1.0, 1.0))
|
|
face = face + vec3(0.036, 0.036, 0.040) * Finish.sheen(uv, 0.0, 0.5, 0.2) * up * gloss
|
|
// The top line; with gloss a touch brighter (+0.03 at rest).
|
|
let hl0 = mix(vec3(0.416, 0.424, 0.455), mix(vec3(0.478, 0.486, 0.518), vec3(0.525, 0.533, 0.561), small), hover)
|
|
let hl1 = mix(vec3(0.447, 0.455, 0.486), mix(vec3(0.498, 0.506, 0.538), vec3(0.545, 0.553, 0.581), small), hover)
|
|
let hl = mix(hl0, hl1, gloss)
|
|
face = mix(face, hl, inner * smoothstep(0.5, 0.97, -n.y) * up)
|
|
// The sides take at most +0.03, over the top quarter only.
|
|
let side = inner * smoothstep(0.7, 1.0, abs(n.x)) * (1.0 - smoothstep(0.0, 0.25 * hh, p.y - lo.y))
|
|
face = face + vec3(0.03, 0.03, 0.03) * side * up
|
|
// The bottom inner rim keeps the lower edge off the shadow.
|
|
face = mix(face, rim.rgb, inner * smoothstep(0.5, 0.97, n.y) * rim.a * (1.0 - disabled))
|
|
// The outline, black at the top, blacker at the bottom.
|
|
let ol = mix(vec3(0.039, 0.039, 0.047), vec3(0.016, 0.016, 0.020), t)
|
|
face = mix(face, mix(ol, vec3(0.102, 0.106, 0.125), disabled), Finish.band(d, 0.0, px, px))
|
|
|
|
let a = Finish.cover(d, px)
|
|
var o = Finish.over(under, vec4(face * a, a))
|
|
// Focus: a thin ring of the LED's rim colour just outside the outline,
|
|
// and a 2 point breath of it.
|
|
let ring = Finish.ring_out(d, 0.0, px, px) * 0.9 + Finish.glow(d - px, 0.7) * 0.3 * (1.0 - Finish.cover(d, px))
|
|
o = Finish.over(o, vec4(0.31, 0.506, 0.702, 1.0) * clamp(ring, 0.0, 1.0) * focus * (1.0 - disabled))
|
|
return o
|
|
}
|
|
|
|
// An LED bar or dot laid over `dst`: a white-hot core, a 1 dp saturated rim
|
|
// and a short halo when lit (`halo` scales it; 0 for a light that shows a
|
|
// value); a dark lens when not (a bar: one dark glass). `hs` is the half
|
|
// size; the ends are round.
|
|
let gl_led = fn(dst: vec4, p: vec2, c: vec2, hs: vec2, lit: float, halo: float, px: float, core: vec3, rimc: vec3) -> vec4 {
|
|
let d = Finish.sd_pill(p, c, hs)
|
|
let slot = Finish.cover(d, px)
|
|
// Unlit: a dark lens, not a hole -- a black rim, near-black glass that
|
|
// catches a little of the light on its upper half, and on a dot large
|
|
// enough to show it a small catch-light up and to the left.
|
|
let tl = clamp((p.y - (c.y - hs.y)) / max(2.0 * hs.y, px), 0.0, 1.0)
|
|
var lens = mix(vec3(0.165, 0.176, 0.200), vec3(0.071, 0.078, 0.090), smoothstep(0.2, 0.9, tl))
|
|
let sp = p - (c + vec2(-0.3 * hs.x, -0.35 * hs.y))
|
|
lens = lens + vec3(0.16, 0.17, 0.19) * exp(-dot(sp, sp) / max(2.0 * 0.09 * hs.y * hs.y, 0.001)) * step(1.5, hs.y)
|
|
// A bar has no room for a rim, a light row and a catch-light: unlit it
|
|
// is one glass of three device pixels, falling #1e2127 -> #15171a with a
|
|
// faint cast of the lamp's blue, its top row lifted 0.03.
|
|
let bar = step(hs.y * 2.5, hs.x)
|
|
let gc = c + vec2(0.0, px * 0.5)
|
|
let gd = Finish.sd_pill(p, gc, vec2(hs.x + px * 0.5, px * 1.5))
|
|
let gt = clamp((p.y - (gc.y - px * 1.5)) / (px * 3.0), 0.0, 1.0)
|
|
var glass = mix(vec3(0.118, 0.129, 0.153), vec3(0.082, 0.090, 0.102), gt)
|
|
glass = mix(glass, vec3(0.31, 0.506, 0.702), 0.08) + vec3(0.03, 0.03, 0.03) * (1.0 - step(gc.y - px * 0.5, p.y))
|
|
var o = Finish.over(dst, vec4(0.024, 0.024, 0.031, 1.0) * Finish.ring_out(d, 0.0, px, px) * 0.55 * (1.0 - lit) * (1.0 - bar))
|
|
o = Finish.over(o, vec4(lens, 1.0) * slot * (1.0 - lit) * (1.0 - bar))
|
|
o = Finish.over(o, vec4(glass, 1.0) * Finish.cover(gd, px) * (1.0 - lit) * bar)
|
|
// Lit: the halo first, under the core. Two falls: a thin one that is
|
|
// gone in a device pixel or two, and a wide weak one that lies a little
|
|
// light on the floor round the lamp.
|
|
let x = max(d - px, 0.0)
|
|
let g = (0.45 * exp(-(x * x) / (2.0 * 0.5 * 0.5)) + 0.22 * exp(-(x * x) / (2.0 * 1.4 * 1.4))) * (1.0 - slot)
|
|
o = Finish.over(o, vec4(rimc, 1.0) * g * 0.8 * halo * lit)
|
|
// The saturated rim, one device pixel round the core.
|
|
let rim = Finish.ring_out(d, 0.0, px, px)
|
|
o = Finish.over(o, vec4(rimc, 1.0) * rim * lit)
|
|
// A hot top on the core.
|
|
let hot = 1.0 - smoothstep(0.0, hs.y * 1.2, p.y - (c.y - hs.y))
|
|
let col = mix(core, vec3(1.0, 1.0, 1.0), hot * 0.5)
|
|
o = Finish.over(o, vec4(col, 1.0) * slot * lit)
|
|
return o
|
|
}
|
|
|
|
// The lamp under a chosen label -- the chosen segment's, the tab in use's,
|
|
// the Primary's -- counted in whole device pixels. Its core is two rows
|
|
// between `xs.x` and `xs.y` (snapped), from the row at `y0` (snapped): the
|
|
// top row `core`, the lower one tinted toward `glow`, and one light-blue
|
|
// column at either end. Round it the halo lies on the floor in two layers:
|
|
// a tight one -- a pale row just above the core (brightest over its
|
|
// middle), a blue row under it, two blue-grey rows fading after that, and
|
|
// at the ends a pale fall gone three columns out -- over a soft wide one
|
|
// that rises three rows above the core, lies faintly along its sides and
|
|
// runs on down to the foot of the well. What lies above the core stays
|
|
// under an eighth of its light past the pale row, so a descender keeps its
|
|
// well. `halo` scales the light round the core; premultiplied over `dst`.
|
|
let gl_lamp = fn(dst: vec4, p: vec2, xs: vec2, y0: float, px: float, lit: float, halo: float, core: vec3, glow: vec3) -> vec4 {
|
|
// Which row from the core's top, and how many columns past its ends
|
|
// (0 inside, 0.5 on the first column out).
|
|
let k = floor((p.y - y0) / px)
|
|
let e = max(max(xs.x - p.x, p.x - xs.y), 0.0) / px
|
|
let hw = max((xs.y - xs.x) * 0.5, px)
|
|
let dx = clamp(abs(p.x - (xs.x + xs.y) * 0.5) / hw, 0.0, 1.0)
|
|
let pale = mix(glow, core, 0.5)
|
|
let dim = mix(glow, vec3(0.56, 0.58, 0.64), 0.45)
|
|
let m4 = step(-4.5, k) * step(k, -3.5)
|
|
let m3 = step(-3.5, k) * step(k, -2.5)
|
|
let m2 = step(-2.5, k) * step(k, -1.5)
|
|
let m1 = step(-1.5, k) * step(k, -0.5)
|
|
let body = step(-0.5, k) * step(k, 1.5)
|
|
let p1 = step(1.5, k) * step(k, 2.5)
|
|
let p2 = step(2.5, k) * step(k, 3.5)
|
|
let p3 = step(3.5, k) * step(k, 4.5)
|
|
let p4 = step(4.5, k) * step(k, 5.5)
|
|
let p5 = step(5.5, k) * step(k, 6.5)
|
|
// The soft wide layer.
|
|
let wide = exp(-(e * e) / (2.0 * 3.4 * 3.4)) * (1.0 - 0.3 * dx * dx)
|
|
let wa = (0.12 * m4 + 0.22 * m3 + 0.32 * m2 + 0.15 * m1 + 0.20 * body + 0.08 * (p1 + p2 + p3) + 0.25 * p4 + 0.15 * p5) * wide
|
|
var o = Finish.over(dst, vec4(dim, 1.0) * clamp(wa * halo, 0.0, 1.0) * lit)
|
|
// The tight layer.
|
|
let top = exp(-(e * e) / (2.0 * 1.6 * 1.6)) * (1.0 - 0.3 * dx * dx)
|
|
let side = exp(-(e * e) / (2.0 * 2.2 * 2.2))
|
|
let ce = max(e - 1.5, 0.0)
|
|
let ends = 0.5 * exp(-(ce * ce) / (2.0 * 0.75 * 0.75)) * step(1.0, e) * (1.0 - 0.4 * step(0.5, k))
|
|
var a = 0.72 * top * m1 + ends * body
|
|
a = a + (0.50 * p1 + 0.46 * p2 + 0.36 * p3 + 0.10 * p4) * side
|
|
let hc = mix(mix(pale, glow, p1), dim, p2 + p3 + p4)
|
|
o = Finish.over(o, vec4(hc, 1.0) * clamp(a * halo, 0.0, 1.0) * lit)
|
|
// The core and its two end columns; the lower row takes a little of
|
|
// the glow's own hue.
|
|
let hue = glow / max(max(glow.x, glow.y), max(glow.z, 0.001))
|
|
let lower = core * mix(vec3(1.0, 1.0, 1.0), hue, 0.35)
|
|
let inside = 1.0 - step(0.25, e)
|
|
let endc = step(0.25, e) * (1.0 - step(1.0, e))
|
|
let cc = mix(mix(core, lower, step(0.5, k)), mix(core, glow, 0.4), endc)
|
|
return Finish.over(o, vec4(cc, 1.0) * (inside + 0.95 * endc) * body * lit)
|
|
}
|
|
|
|
// The three-quarter arc a waiting button turns in place of its label.
|
|
let gl_spinner = fn(dst: vec4, p: vec2, c: vec2, rad: float, t: float, amt: float, stroke: float) -> vec4 {
|
|
let sdf = Sdf2d.viewport(p)
|
|
let a0 = t * 2.0 * PI
|
|
sdf.arc_round_caps(c.x, c.y, rad, a0, a0 + 1.5 * PI, stroke)
|
|
sdf.fill(vec4(0.94, 0.98, 1.0, amt))
|
|
return Finish.over(dst, sdf.result)
|
|
}
|
|
|
|
// ---------------------------------------------------------------- buttons
|
|
|
|
// Every button is the same slim cap: 24 points on a 13 point line, 12
|
|
// points either side. Its shadow falls outside the box, so the box is the
|
|
// outline and nothing is paid in padding.
|
|
mod.widgets.ButtonFlat.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 3.0 bottom: 3.0}
|
|
mod.widgets.ButtonFlat.min_height = 24.0
|
|
// Glyphs sit at 14 points so an icon never makes a cap taller than the row;
|
|
// an icon alone gets a square cap.
|
|
mod.widgets.ButtonFlat.icon_walk.width = 14.0
|
|
mod.widgets.ButtonIcon.padding = mod.turtle.Inset{left: 5.0 right: 5.0 top: 3.0 bottom: 3.0}
|
|
mod.widgets.ButtonFlatIcon.padding = mod.turtle.Inset{left: 5.0 right: 5.0 top: 3.0 bottom: 3.0}
|
|
mod.widgets.ButtonFlatterIcon.padding = mod.turtle.Inset{left: 5.0 right: 5.0 top: 3.0 bottom: 3.0}
|
|
// The caps that carry a lamp lift their label one device pixel, so the lamp
|
|
// can stand two rows off the bottom rim and still clear the descenders.
|
|
mod.widgets.ButtonPrimary.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 2.3333 bottom: 3.6667}
|
|
mod.widgets.ButtonSecondary.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 2.3333 bottom: 3.6667}
|
|
mod.widgets.ButtonDanger.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 2.3333 bottom: 3.6667}
|
|
// The short rungs keep their own heights under the row minimum.
|
|
mod.widgets.ButtonXs.min_height = 0.0
|
|
mod.widgets.ButtonSm.min_height = 0.0
|
|
// The smallest rung's label one step down, so its cap height is a third of
|
|
// its cap like every other rung's.
|
|
mod.widgets.ButtonXs.draw_text.text_style.font_size = 6.5
|
|
mod.widgets.ButtonFlat.draw_bg.border_radius = uniform(1.0)
|
|
mod.widgets.ButtonFlat.draw_bg.border_size = uniform(0.34)
|
|
mod.widgets.ButtonFlat.clip_x = false
|
|
mod.widgets.ButtonFlat.clip_y = false
|
|
mod.widgets.ButtonFlat.draw_bg.vertex = fn() {
|
|
let m = 7.0
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
|
|
}
|
|
|
|
// The cap and its states, shared by every button template; `kind` picks the
|
|
// variant: 0 seated cap, 1 raised cap, 2 primary (lit LED), 3 secondary (dark
|
|
// LED slot), 4 tonal (tinted face), 5 danger (red LED), 6 outline, 7 flatter.
|
|
let gl_button = fn(pos: vec2, size: vec2, o: vec2, dpi: float, hover: float, down: float, disabled: float, focus: float, loading: float, anim: float, kind: float, dash: float) -> vec4 {
|
|
let m = 7.0
|
|
let px = gl_px(dpi)
|
|
let p = pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
let lo = vec2(gl_snap(0.0, o.x, px), gl_snap(0.0, o.y, px))
|
|
let hi = vec2(gl_snap(size.x, o.x, px), gl_snap(size.y, o.y, px))
|
|
let c = (lo + hi) * 0.5
|
|
let h = (hi - lo) * 0.5
|
|
let r = 2.0
|
|
var res = vec4(0.0, 0.0, 0.0, 0.0)
|
|
if kind > 5.5 {
|
|
// Outline: no face, the black ring cut into the ground with its lip;
|
|
// under the hand a faint cap shows, held it sinks.
|
|
let d = Material.sd_box(p, c, h, r)
|
|
if kind > 6.5 {
|
|
// Flatter: nothing at rest.
|
|
if hover + down + focus > 0.001 {
|
|
res = gl_cap(p, lo, hi, r, px, hover, down, disabled, 0.0, 0.0, vec3(0.0, 0.0, 0.0), vec4(0.196, 0.204, 0.227, 1.0), 1.0) * clamp(max(hover, down), 0.0, 1.0)
|
|
}
|
|
} else {
|
|
var ring = Finish.band(d, 0.0, px, px)
|
|
if dash > 0.0 {
|
|
ring = ring * step(0.5, fract((p.x + p.y) / (dash * 2.0)))
|
|
}
|
|
let lip = Finish.ring_out(d, 0.0, px, px) * step(c.y + h.y * 0.5, p.y)
|
|
let inner = Finish.band(d, px, px * 2.0, px) * step(p.y, c.y - h.y * 0.5)
|
|
res = vec4(0.28, 0.30, 0.34, 1.0) * lip * 0.9
|
|
res = Finish.over(res, vec4(0.0, 0.0, 0.0, 1.0) * inner * 0.25)
|
|
res = Finish.over(res, vec4(0.039, 0.039, 0.047, 1.0) * ring)
|
|
if hover + down > 0.001 {
|
|
res = Finish.over(res, gl_cap(p, lo, hi, r, px, hover, down, disabled, 0.0, 0.0, vec3(0.0, 0.0, 0.0), vec4(0.196, 0.204, 0.227, 1.0), 1.0) * clamp(max(hover, down), 0.0, 1.0) * 0.85)
|
|
}
|
|
}
|
|
let fr = Finish.ring_out(d, 0.0, px, px) * 0.9 + Finish.glow(d - px, 0.7) * 0.3 * (1.0 - Finish.cover(d, px))
|
|
res = Finish.over(res, vec4(0.31, 0.506, 0.702, 1.0) * clamp(fr, 0.0, 1.0) * focus * (1.0 - disabled))
|
|
} else {
|
|
var tint = vec3(0.0, 0.0, 0.0)
|
|
var rim = vec4(0.196, 0.204, 0.227, 1.0)
|
|
let lit = 1.0 - disabled
|
|
if kind > 3.5 && kind < 4.5 {
|
|
// The tonal cap: the face a breath toward the LED's blue, no
|
|
// more than 4 steps of blue over the family's face.
|
|
tint = vec3(-0.004, 0.0, 0.012)
|
|
}
|
|
if kind > 1.5 && kind < 2.5 {
|
|
// Primary: the LED's spill lies along the bottom inner rim, and
|
|
// a tight breath of it under the cap.
|
|
rim = vec4(0.31, 0.506, 0.702, 0.45 * lit)
|
|
let dg = Material.sd_box(p, c + vec2(0.0, 0.05 * (hi.y - lo.y)), h - vec2(2.0, 0.0), r)
|
|
let g = exp(-(max(dg, 0.0) * max(dg, 0.0)) / (2.0 * 1.2 * 1.2)) * (1.0 - Finish.cover(dg, px))
|
|
res = vec4(0.31, 0.506, 0.702, 1.0) * g * 0.18 * lit * (1.0 - down * 0.5)
|
|
}
|
|
if kind > 4.5 {
|
|
rim = vec4(0.56, 0.22, 0.18, 0.35 * lit)
|
|
}
|
|
var lift = 1.0
|
|
if kind < 0.5 {
|
|
lift = 0.0
|
|
}
|
|
res = Finish.over(res, gl_cap(p, lo, hi, r, px, hover, down, disabled, focus, lift, tint, rim, 1.0))
|
|
if kind > 1.5 && kind < 3.5 || kind > 4.5 {
|
|
// The lamp: the chosen segment's lamp (`gl_lamp`), its core two
|
|
// device pixels whose middle stands 4 points above the bottom
|
|
// edge, 16 device pixels wide with a light end column either
|
|
// side; the label (raised one device pixel on these caps)
|
|
// clears its pale top row by two rows of face. Held, its halo
|
|
// halves. Unlit (Secondary, or disabled) it is the dark glass.
|
|
let lc = vec2(c.x, gl_snap(hi.y - 4.0, o.y, px))
|
|
let hs = vec2(min(5.5, h.x * 0.4), px)
|
|
let lw = min(5.3333, h.x * 0.4)
|
|
let xs = vec2(gl_snap(c.x - lw, o.x, px), gl_snap(c.x + lw, o.x, px))
|
|
var on = lit
|
|
var core = vec3(0.910, 0.918, 0.922)
|
|
var glow = vec3(0.405, 0.593, 0.752)
|
|
if kind > 2.5 && kind < 3.5 {
|
|
on = 0.0
|
|
}
|
|
if kind > 4.5 {
|
|
// Danger: the same lamp, red.
|
|
core = vec3(0.97, 0.80, 0.76)
|
|
glow = vec3(0.86, 0.26, 0.2)
|
|
}
|
|
let dark = gl_led(res, p, lc, hs, 0.0, 0.0, px, core, glow)
|
|
let lamp = gl_lamp(res, p, xs, lc.y - px, px, 1.0, 1.0 - down * 0.5, core, glow)
|
|
res = mix(dark, lamp, on)
|
|
}
|
|
}
|
|
if loading > 0.001 {
|
|
// The arc ends two device pixels or more above where a lamp's rim
|
|
// stands: a smaller ring, lifted one device pixel.
|
|
let rad = max(min(h.x, h.y) - 7.25, 2.0)
|
|
res = gl_spinner(res, p, c - vec2(0.0, px), rad, anim, loading, 1.5)
|
|
}
|
|
return res
|
|
}
|
|
|
|
mod.widgets.ButtonFlat.draw_bg.pixel = fn() {
|
|
return self.host_over(gl_button(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.down, self.disabled, self.focus, self.loading, self.anim_time, 0.0, 0.0), 7.0)
|
|
}
|
|
mod.widgets.Button.draw_bg.pixel = fn() {
|
|
return self.host_over(gl_button(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.down, self.disabled, self.focus, self.loading, self.anim_time, 1.0, 0.0), 7.0)
|
|
}
|
|
mod.widgets.ButtonPrimary.draw_bg.pixel = fn() {
|
|
return self.host_over(gl_button(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.down, self.disabled, self.focus, self.loading, self.anim_time, 2.0, 0.0), 7.0)
|
|
}
|
|
mod.widgets.ButtonSecondary.draw_bg.pixel = fn() {
|
|
return self.host_over(gl_button(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.down, self.disabled, self.focus, self.loading, self.anim_time, 3.0, 0.0), 7.0)
|
|
}
|
|
mod.widgets.ButtonTertiary.draw_bg.pixel = fn() {
|
|
return self.host_over(gl_button(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.down, self.disabled, self.focus, self.loading, self.anim_time, 4.0, 0.0), 7.0)
|
|
}
|
|
mod.widgets.ButtonDanger.draw_bg.pixel = fn() {
|
|
return self.host_over(gl_button(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.down, self.disabled, self.focus, self.loading, self.anim_time, 5.0, 0.0), 7.0)
|
|
}
|
|
mod.widgets.ButtonOutline.draw_bg.pixel = fn() {
|
|
return self.host_over(gl_button(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.down, self.disabled, self.focus, self.loading, self.anim_time, 6.0, self.border_dash), 7.0)
|
|
}
|
|
mod.widgets.ButtonFlatter.draw_bg.pixel = fn() {
|
|
return self.host_over(gl_button(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.down, self.disabled, self.focus, self.loading, self.anim_time, 7.0, 0.0), 7.0)
|
|
}
|
|
|
|
// Label ink per state: caps #d4d7de, the primary's lit legend near white,
|
|
// every variant dims to #5c5e66 when disabled.
|
|
mod.widgets.ButtonPrimary.draw_text.color = #f4f6f8
|
|
mod.widgets.ButtonPrimary.draw_text.color_hover = #f8f9fa
|
|
mod.widgets.ButtonPrimary.draw_text.color_down = #f4f6f8
|
|
mod.widgets.ButtonPrimary.draw_text.color_focus = #f4f6f8
|
|
mod.widgets.ButtonPrimary.draw_text.color_disabled = #5c5e66
|
|
mod.widgets.ButtonPrimary.draw_icon.color = #f4f6f8
|
|
mod.widgets.ButtonSecondary.draw_text.color = #d4d7de
|
|
mod.widgets.ButtonSecondary.draw_text.color_hover = #dcdee3
|
|
mod.widgets.ButtonSecondary.draw_text.color_down = #c4c7ce
|
|
mod.widgets.ButtonSecondary.draw_text.color_focus = #d4d7de
|
|
mod.widgets.ButtonSecondary.draw_icon.color = #d4d7de
|
|
mod.widgets.ButtonTertiary.draw_text.color = #d4d7de
|
|
mod.widgets.ButtonTertiary.draw_text.color_hover = #dcdee3
|
|
mod.widgets.ButtonTertiary.draw_text.color_down = #c4c7ce
|
|
mod.widgets.ButtonTertiary.draw_text.color_focus = #d4d7de
|
|
mod.widgets.ButtonTertiary.draw_icon.color = #d4d7de
|
|
mod.widgets.ButtonDanger.draw_text.color = #f4f6f8
|
|
mod.widgets.ButtonDanger.draw_text.color_hover = #f8f9fa
|
|
mod.widgets.ButtonDanger.draw_text.color_down = #f4f6f8
|
|
mod.widgets.ButtonDanger.draw_text.color_focus = #f4f6f8
|
|
mod.widgets.ButtonDanger.draw_icon.color = #f4f6f8
|
|
mod.widgets.ButtonOutline.draw_text.color = #d4d7de
|
|
mod.widgets.ButtonOutline.draw_text.color_hover = #dcdee3
|
|
mod.widgets.ButtonOutline.draw_text.color_down = #c4c7ce
|
|
mod.widgets.ButtonOutline.draw_text.color_focus = #d4d7de
|
|
mod.widgets.ButtonOutline.draw_text.color_disabled = #5c5e66
|
|
mod.widgets.ButtonOutline.draw_icon.color = #d4d7de
|
|
// The trailing icon takes the cap's ink too, never the drawing's own black.
|
|
mod.widgets.ButtonFlat.draw_icon_end.color = #d4d7de
|
|
|
|
// ---------------------------------------------------------------- selection
|
|
|
|
// A recessed well: no outer shadow, a 1 dp inner top shade, the black
|
|
// outline, and a 1 dp light lip on the ground under its lower edge.
|
|
// Premultiplied; `fill` is the well's own ink, `lip` 0 drops the lip.
|
|
let gl_well = fn(p: vec2, lo: vec2, hi: vec2, r: float, px: float, fill: vec3, focus: float, disabled: float, lip: float) -> vec4 {
|
|
let c = (lo + hi) * 0.5
|
|
let h = (hi - lo) * 0.5
|
|
let d = Material.sd_box(p, c, h, r)
|
|
let t = clamp((p.y - lo.y) / max(hi.y - lo.y, 1.0), 0.0, 1.0)
|
|
var res = vec4(0.28, 0.30, 0.34, 1.0) * Finish.ring_out(d, 0.0, px, px) * step(c.y, p.y) * lip * (1.0 - disabled * 0.6)
|
|
// The lower half a shade lighter, the first device pixels under the top
|
|
// outline a shade darker.
|
|
var col = fill + vec3(0.008, 0.008, 0.009) * step(0.5, t)
|
|
let top = p.y - lo.y
|
|
// Whole device-pixel rows (the edges are snapped): the row under the
|
|
// outline #1a1b20, the next half way to it.
|
|
col = mix(col, vec3(0.102, 0.106, 0.125), 1.0 - step(px * 2.0, top))
|
|
col = mix(col, vec3(0.102, 0.106, 0.125), 0.5 * step(px * 2.0, top) * (1.0 - step(px * 3.0, top)))
|
|
col = mix(col, mix(vec3(0.039, 0.039, 0.047), vec3(0.024, 0.043, 0.067), t), Finish.band(d, 0.0, px, px))
|
|
let a = Finish.cover(d, px)
|
|
res = Finish.over(res, vec4(col * a, a))
|
|
let ring = Finish.ring_out(d, 0.0, px, px) * 0.9 + Finish.glow(d - px, 0.7) * 0.3 * (1.0 - a)
|
|
res = Finish.over(res, vec4(0.31, 0.506, 0.702, 1.0) * clamp(ring, 0.0, 1.0) * focus * (1.0 - disabled))
|
|
return res
|
|
}
|
|
|
|
// The quad of a small control grows by this much so its shadow and focus
|
|
// ring can fall outside the box.
|
|
mod.widgets.CheckBoxFlat.draw_bg.vertex = fn() {
|
|
let m = 5.0
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
|
|
}
|
|
mod.widgets.CheckBoxFlat.clip_x = false
|
|
mod.widgets.CheckBoxFlat.clip_y = false
|
|
mod.widgets.CheckBoxFlat.padding = mod.turtle.Inset{left: 8.0 right: 8.0 top: 4.0 bottom: 4.0}
|
|
mod.widgets.CheckBoxFlat.draw_bg.size = uniform(14.0)
|
|
mod.widgets.CheckBoxFlat.draw_bg.border_radius = uniform(1.0)
|
|
mod.widgets.CheckBoxFlat.label_walk.margin.left = 14.0
|
|
|
|
// A lit mark: a pale core along the stroke's middle, the accent at its
|
|
// edges and a soft halo of the lamp's blue round it. `dk` is the distance
|
|
// to the stroke's edge, `w` its half width.
|
|
let gl_lit_mark = fn(dst: vec4, dk: float, w: float, px: float, on: float, disabled: float, error: float) -> vec4 {
|
|
let edge = mix(mix(vec3(0.227, 0.655, 0.965), vec3(0.94, 0.32, 0.23), error), vec3(0.294, 0.306, 0.341), disabled)
|
|
let core = mix(mix(vec3(0.88, 0.96, 1.0), vec3(1.0, 0.86, 0.82), error), vec3(0.36, 0.37, 0.40), disabled)
|
|
let glow = mix(vec3(0.31, 0.506, 0.702), vec3(0.72, 0.25, 0.18), error)
|
|
let x = max(dk, 0.0)
|
|
let g = (0.30 * exp(-(x * x) / (2.0 * 0.45 * 0.45)) + 0.14 * exp(-(x * x) / (2.0 * 1.3 * 1.3))) * (1.0 - Finish.cover(dk, px)) * (1.0 - disabled)
|
|
var res = Finish.over(dst, vec4(glow, 1.0) * g * on)
|
|
let ink = mix(edge, core, 1.0 - smoothstep(0.15 * w, 0.8 * w, dk + w))
|
|
return Finish.over(res, vec4(ink, 1.0) * Finish.cover(dk, px) * on)
|
|
}
|
|
|
|
// The check box: a 14 point raised cap. Checked, it sinks into a blue well
|
|
// (the chosen segment's) and its mark is lit.
|
|
mod.widgets.CheckBoxFlat.draw_bg.pixel = fn() {
|
|
let m = 5.0
|
|
let px = gl_px(self.draw_pass.dpi_factor)
|
|
let o = self.rect_pos
|
|
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
let sz = self.size
|
|
let x0 = (self.rect_size.x - sz) * self.mark_at_end
|
|
let y0 = self.rect_size.y * 0.5 - sz * 0.5
|
|
let lo = vec2(gl_snap(x0, o.x, px), gl_snap(y0, o.y, px))
|
|
let hi = vec2(gl_snap(x0 + sz, o.x, px), gl_snap(y0 + sz, o.y, px))
|
|
let c = (lo + hi) * 0.5
|
|
let r = min(self.border_radius * 2.0, sz * 0.5)
|
|
let on = max(self.active, self.mixed)
|
|
let held = max(max(self.down, self.pressed), on)
|
|
let tint = vec3(-0.006, 0.030, 0.062) * on * (1.0 - self.disabled)
|
|
var res = gl_cap(p, lo, hi, r, px, self.hover, held, self.disabled, self.focus, 1.0, tint, vec4(0.196, 0.204, 0.227, 1.0), 0.0)
|
|
// The error: the outline turns the alt red.
|
|
let d = Material.sd_box(p, c, (hi - lo) * 0.5, r)
|
|
res = Finish.over(res, vec4(0.94, 0.32, 0.23, 1.0) * Finish.band(d, 0.0, px, px) * self.error)
|
|
// The mark: a check 8 points across in a 1.4 point stroke, lit; mixed
|
|
// is a dash of three device pixel rows, a pale core row between two of
|
|
// the accent, on the row through the middle of the well. The check
|
|
// stands one device pixel lower, in the well.
|
|
let cm = c + vec2(0.0, px * 0.5)
|
|
let dm = min(gl_seg(p, vec2(cm.x - 3.6, cm.y + 0.1), vec2(cm.x - 1.1, cm.y + 2.6)), gl_seg(p, vec2(cm.x - 1.1, cm.y + 2.6), vec2(cm.x + 3.7, cm.y - 2.8)))
|
|
let cy = gl_snap((lo.y + hi.y) * 0.5, o.y, px) + px * 0.5
|
|
let dd = gl_seg(p, vec2(c.x - 3.5, cy), vec2(c.x + 3.5, cy))
|
|
let dk = mix(dm - 0.7, dd - px * 1.5, self.mixed)
|
|
return gl_lit_mark(res, dk, mix(0.7, px * 1.5, self.mixed), px, on, self.disabled, self.error)
|
|
}
|
|
|
|
// The toggle: a sunk 34 x 18 track with a raised thumb half its length; the
|
|
// thumb carries an LED bar, lit when on, a dark slot when off.
|
|
mod.widgets.ToggleFlat.draw_bg.size = uniform(18.0)
|
|
mod.widgets.ToggleFlat.draw_bg.pill_aspect = uniform(1.89)
|
|
mod.widgets.ToggleFlat.label_walk.margin.left = 34.0
|
|
mod.widgets.ToggleFlat.draw_bg.pixel = fn() {
|
|
let m = 5.0
|
|
let px = gl_px(self.draw_pass.dpi_factor)
|
|
let o = self.rect_pos
|
|
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
let tw = self.size * self.pill_aspect
|
|
let th = self.size
|
|
let x0 = (self.rect_size.x - tw) * self.mark_at_end
|
|
let y0 = self.rect_size.y * 0.5 - th * 0.5
|
|
let lo = vec2(gl_snap(x0, o.x, px), gl_snap(y0, o.y, px))
|
|
let hi = vec2(gl_snap(x0 + tw, o.x, px), gl_snap(y0 + th, o.y, px))
|
|
// On, the track's floor takes the chosen segment's blue cast.
|
|
let on = self.active * (1.0 - self.disabled)
|
|
var res = gl_well(p, lo, hi, 2.5, px, mix(vec3(0.141, 0.149, 0.176), vec3(0.137, 0.165, 0.204), on), self.focus, self.disabled, 1.0)
|
|
// The thumb travels half the track.
|
|
let kt = mix(self.active, self.drag_pos, self.drag)
|
|
let kw = gl_snap(x0 + tw * 0.5, o.x, px) - lo.x
|
|
let kx = gl_snap(x0 + (tw - tw * 0.5) * kt, o.x, px)
|
|
let klo = vec2(kx, lo.y)
|
|
let khi = vec2(kx + kw, hi.y)
|
|
let thumb = gl_cap(p, klo, khi, 2.5, px, self.hover, max(self.pressed, self.down), self.disabled, 0.0, 0.5,vec3(0.0, 0.0, 0.0), vec4(0.196, 0.204, 0.227, 1.0), 1.0)
|
|
res = Finish.over(res, thumb)
|
|
// The LED bar, 8 x 1.5 points, in the middle of the thumb.
|
|
let lc = vec2((klo.x + khi.x) * 0.5, gl_snap((lo.y + hi.y) * 0.5, o.y, px))
|
|
res = gl_led(res, p, lc, vec2(4.0, px), on, 0.8, px, vec3(0.94, 0.984, 1.0), vec3(0.31, 0.506, 0.702))
|
|
return res
|
|
}
|
|
|
|
// The radio: the check box's cap made round. Chosen, it sinks into the blue
|
|
// well and an LED dot is lit in its middle; not chosen, the cap stays up and
|
|
// the dot is a dark lens.
|
|
mod.widgets.RadioButtonFlat.draw_bg.vertex = fn() {
|
|
let m = 5.0
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
|
|
}
|
|
mod.widgets.RadioButtonFlat.clip_x = false
|
|
mod.widgets.RadioButtonFlat.clip_y = false
|
|
mod.widgets.RadioButtonFlat.draw_bg.size = uniform(14.0)
|
|
mod.widgets.RadioButtonFlat.padding = mod.turtle.Inset{left: 8.0 right: 8.0 top: 4.0 bottom: 4.0}
|
|
mod.widgets.RadioButtonFlat.label_walk.margin.left = 14.0
|
|
mod.widgets.RadioButtonFlat.draw_bg.pixel = fn() {
|
|
let m = 5.0
|
|
let px = gl_px(self.draw_pass.dpi_factor)
|
|
let o = self.rect_pos
|
|
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
let sz = self.size
|
|
let y0 = self.rect_size.y * 0.5 - sz * 0.5
|
|
let lo = vec2(gl_snap(0.0, o.x, px), gl_snap(y0, o.y, px))
|
|
let hi = vec2(gl_snap(sz, o.x, px), gl_snap(y0 + sz, o.y, px))
|
|
let c = (lo + hi) * 0.5
|
|
let on = self.active * (1.0 - self.disabled)
|
|
let tint = vec3(-0.006, 0.030, 0.062) * on
|
|
var res = gl_cap(p, lo, hi, sz * 0.5, px, self.hover, max(self.down, on), self.disabled, self.focus, 1.0, tint, vec4(0.196, 0.204, 0.227, 1.0), 0.0)
|
|
// The dot: 4.5 points, one device pixel lower in the well when lit.
|
|
let cd = c + vec2(0.0, px * 0.5 * on)
|
|
res = gl_led(res, p, cd, vec2(2.25 - px, 2.25 - px), on, 0.8, px, vec3(0.88, 0.96, 1.0), vec3(0.31, 0.506, 0.702))
|
|
return res
|
|
}
|
|
|
|
// The radio tab: a segment of a joined strip. At rest a raised cap; chosen,
|
|
// it sinks, flat and tinted toward the light, its legend lit and the
|
|
// chosen segment's lamp under it.
|
|
mod.widgets.RadioButtonTabFlat.draw_bg.vertex = fn() {
|
|
let m = 7.0
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
|
|
}
|
|
mod.widgets.RadioButtonTabFlat.clip_x = false
|
|
mod.widgets.RadioButtonTabFlat.clip_y = false
|
|
mod.widgets.RadioButtonTabFlat.padding = mod.turtle.Inset{left: 0.0 right: 8.0 top: 4.0 bottom: 4.0}
|
|
mod.widgets.RadioButtonTabFlat.min_height = 24.0
|
|
mod.widgets.RadioButtonTabFlat.draw_bg.pixel = fn() {
|
|
let m = 7.0
|
|
let px = gl_px(self.draw_pass.dpi_factor)
|
|
let o = self.rect_pos
|
|
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
let lo = vec2(gl_snap(0.0, o.x, px), gl_snap(0.0, o.y, px))
|
|
let hi = vec2(gl_snap(self.rect_size.x, o.x, px), gl_snap(self.rect_size.y, o.y, px))
|
|
let sel = self.active
|
|
let tint = vec3(-0.006, 0.030, 0.062) * sel
|
|
let res = gl_cap(p, lo, hi, 2.0, px, self.hover, max(self.down, sel), self.disabled, self.focus, 1.0, tint, vec4(0.196, 0.204, 0.227, 1.0), 0.0)
|
|
// The lamp, centred under the legend (which stands centred, 1.7 points
|
|
// above the cap's middle): its core 6.5 points under the middle, so a
|
|
// descender keeps two rows of well above its pale top row.
|
|
let c = (lo + hi) * 0.5
|
|
let xs = vec2(gl_snap(c.x - 5.3333, o.x, px), gl_snap(c.x + 5.3333, o.x, px))
|
|
return gl_lamp(res, p, xs, gl_snap(c.y + 6.5, o.y, px), px, sel * (1.0 - self.disabled), 1.0, vec3(0.910, 0.918, 0.922), vec3(0.405, 0.593, 0.752))
|
|
}
|
|
mod.widgets.RadioButtonTabFlat.draw_text.color = #c4c7ce
|
|
mod.widgets.RadioButtonTabFlat.draw_text.color_hover = #dcdee3
|
|
mod.widgets.RadioButtonTabFlat.draw_text.color_active = #fafafa
|
|
mod.widgets.RadioButtonTabFlat.draw_text.color_disabled = #5c5e66
|
|
|
|
// ---------------------------------------------------------------- sliders
|
|
|
|
// A lit bar laid in a groove, like the lamp and like the chosen segment's
|
|
// legend: counted in whole device-pixel rows from `lo.y` (snapped), one
|
|
// pale core row and one row of the body under it in the middle of the
|
|
// floor, and on the rows round them a soft halo on the floor -- 15 % of the
|
|
// core's light on the next row, 7 % on the one after. Past the bar's end
|
|
// the halo runs on along the floor, falling by half every device pixel,
|
|
// between `gx.x` and `gx.y` (the floor's ends). `body` is the role colour; the core
|
|
// is it taken most of the way to white. Premultiplied over `dst`.
|
|
let gl_lit_bar = fn(dst: vec4, p: vec2, lo: vec2, hi: vec2, gx: vec2, px: float, body: vec3, disabled: float) -> vec4 {
|
|
let n = max(floor((hi.y - lo.y) / px + 0.5), 1.0)
|
|
let k = floor((p.y - lo.y) / px)
|
|
let rows = step(0.0, k) * step(k, n - 1.0)
|
|
let kc = max(floor(n * 0.5) - 1.0, 0.0)
|
|
let kb = min(kc + 1.0, n - 1.0)
|
|
// How far this row stands from the lit pair, in rows.
|
|
let dr = max(max(kc - k, k - kb), 0.0)
|
|
let core = mix(body, vec3(1.0, 1.0, 1.0), 0.8)
|
|
let deep = mix(body, vec3(0.13, 0.14, 0.17), 0.3)
|
|
var col = mix(core, deep, step(kb - 0.5, k) * step(kc + 0.5, kb))
|
|
col = mix(col, mix(body, vec3(0.3, 0.33, 0.38), 0.7), disabled)
|
|
let lit = step(dr, 0.5)
|
|
let halo_row = mix(0.0, 0.34, step(0.5, dr) * step(dr, 1.5)) + mix(0.0, 0.16, step(1.5, dr) * step(dr, 2.5))
|
|
let inx = step(lo.x, p.x) * step(p.x, hi.x)
|
|
let past = max(max(p.x - hi.x, lo.x - p.x), 0.0) / px
|
|
let fall = exp(-max(past - 0.5, 0.0) * 0.76) * (1.0 - inx) * step(gx.x, p.x) * step(p.x, gx.y)
|
|
let along = mix(0.34, halo_row, step(0.5, dr))
|
|
let g = (halo_row * inx + along * fall) * rows * (1.0 - disabled)
|
|
var res = Finish.over(dst, vec4(deep, 1.0) * g)
|
|
return Finish.over(res, vec4(col, 1.0) * lit * inx * rows)
|
|
}
|
|
let gl_fill = fn(dst: vec4, p: vec2, lo: vec2, hi: vec2, gx: vec2, px: float, disabled: float) -> vec4 {
|
|
return gl_lit_bar(dst, p, lo, hi, gx, px, vec3(0.231, 0.682, 1.0), disabled)
|
|
}
|
|
|
|
// A round handle cap: the button's cap made round, with a dimple in its
|
|
// middle, or a small LED dot there while it is held or focused.
|
|
let gl_handle = fn(dst: vec4, p: vec2, c: vec2, rad: float, px: float, hover: float, held: float, disabled: float) -> vec4 {
|
|
let lo = c - vec2(rad, rad)
|
|
let hi = c + vec2(rad, rad)
|
|
var res = Finish.over(dst, gl_cap(p, lo, hi, rad, px, hover, 0.0, disabled, 0.0, 1.0, vec3(0.0, 0.0, 0.0), vec4(0.196, 0.204, 0.227, 1.0), 1.0))
|
|
let d = Finish.sd_circle(p, c, 1.5)
|
|
res = Finish.over(res, vec4(0.30, 0.31, 0.35, 1.0) * Finish.ring_out(d, 0.0, px, px) * step(c.y + 0.5, p.y) * (1.0 - held))
|
|
res = Finish.over(res, vec4(0.102, 0.106, 0.125, 1.0) * Finish.cover(d, px) * (1.0 - held))
|
|
res = gl_led(res, p, c, vec2(1.5 - px, 1.5 - px), held * (1.0 - disabled), 0.6, px, vec3(0.94, 0.984, 1.0), vec3(0.31, 0.506, 0.702))
|
|
return res
|
|
}
|
|
|
|
// The bar slider: a 4 point well under its label row, the lit fill, and a
|
|
// 14 point round cap riding it.
|
|
mod.widgets.SliderFlat.clip_x = false
|
|
mod.widgets.SliderFlat.clip_y = false
|
|
mod.widgets.SliderFlat.draw_bg.vertex = fn() {
|
|
let m = 6.0
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
|
|
}
|
|
mod.widgets.SliderFlat.draw_bg.pixel = fn() {
|
|
let m = 6.0
|
|
let px = gl_px(self.draw_pass.dpi_factor)
|
|
let o = self.rect_pos
|
|
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
let rad = 7.0
|
|
let cy = gl_snap(20.0 + (self.rect_size.y - 20.0) * 0.5, o.y, px)
|
|
let lo = vec2(gl_snap(0.0, o.x, px), cy - 2.0)
|
|
let hi = vec2(gl_snap(self.rect_size.x, o.x, px), cy + 2.0)
|
|
var res = gl_well(p, lo, hi, 2.0, px, vec3(0.141, 0.149, 0.176), 0.0, self.disabled, 1.0)
|
|
let span = self.rect_size.x - rad * 2.0
|
|
let hx = rad + self.slide_pos * span
|
|
let ox = rad + self.origin_pos * span
|
|
let f0 = mix(lo.x + px, min(ox, hx), self.arc_origin)
|
|
let f1 = mix(hx, max(ox, hx), self.arc_origin)
|
|
res = gl_fill(res, p, vec2(f0, lo.y + px), vec2(f1, hi.y - px), vec2(lo.x + px, hi.x - px), px, self.disabled)
|
|
let held = max(self.drag, self.focus)
|
|
return gl_handle(res, p, vec2(hx, cy), rad, px, self.hover, held, self.disabled)
|
|
}
|
|
|
|
// A slider's value is a text input, and the field row's 24 point minimum
|
|
// pushed it half a row below its label: here it is only as tall as its line.
|
|
mod.widgets.SliderMinimal.text_input.min_height = 0.0
|
|
mod.widgets.SliderMinimal.text_input.padding = mod.turtle.Inset{left: 0.0 right: 0.0 top: 0.0 bottom: 0.0}
|
|
mod.widgets.SliderFlat.text_input.min_height = 0.0
|
|
mod.widgets.SliderFlat.text_input.padding = mod.turtle.Inset{left: 0.0 right: 0.0 top: 0.0 bottom: 0.0}
|
|
mod.widgets.Slider.text_input.min_height = 0.0
|
|
mod.widgets.Slider.text_input.padding = mod.turtle.Inset{left: 0.0 right: 0.0 top: 0.0 bottom: 0.0}
|
|
mod.widgets.SliderRoundFlat.text_input.min_height = 0.0
|
|
mod.widgets.SliderRoundFlat.text_input.padding = mod.turtle.Inset{left: 0.0 right: 0.0 top: 0.0 bottom: 0.0}
|
|
mod.widgets.SliderRound.text_input.min_height = 0.0
|
|
mod.widgets.SliderRound.text_input.padding = mod.turtle.Inset{left: 0.0 right: 0.0 top: 0.0 bottom: 0.0}
|
|
// A dial's value box stands in the 20 point legend row above the ring: at
|
|
// the field row's 24 points it hung over the ring's bezel, so here it is
|
|
// its line and two points either side, and clears the ring.
|
|
mod.widgets.RotaryFlat.text_input.min_height = 0.0
|
|
mod.widgets.RotaryFlat.text_input.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 2.0 bottom: 2.0}
|
|
mod.widgets.Rotary.text_input.min_height = 0.0
|
|
mod.widgets.Rotary.text_input.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 2.0 bottom: 2.0}
|
|
mod.widgets.RotaryGradientY.text_input.min_height = 0.0
|
|
mod.widgets.RotaryGradientY.text_input.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 2.0 bottom: 2.0}
|
|
|
|
// The minimal slider: only the slot and the lit value; a slim cap shows
|
|
// under the hand.
|
|
mod.widgets.SliderMinimal.draw_bg.pixel = fn() {
|
|
let px = gl_px(self.draw_pass.dpi_factor)
|
|
let o = self.rect_pos
|
|
let p = self.pos * self.rect_size
|
|
let cy = gl_snap(self.offset_y + (self.rect_size.y - self.offset_y) * 0.5, o.y, px)
|
|
// Four device pixels: the outline, a core row, a body row, the outline.
|
|
let lo = vec2(gl_snap(0.0, o.x, px), cy - px * 2.0)
|
|
let hi = vec2(gl_snap(self.rect_size.x, o.x, px), cy + px * 2.0)
|
|
var res = gl_well(p, lo, hi, 1.0, px, vec3(0.141, 0.149, 0.176), 0.0, self.disabled, 1.0)
|
|
let lo2 = min(self.origin_pos, self.slide_pos)
|
|
let hi2 = max(self.origin_pos, self.slide_pos)
|
|
let f0 = mix(0.0, lo2, self.arc_origin) * self.rect_size.x
|
|
let f1 = mix(self.slide_pos, hi2, self.arc_origin) * self.rect_size.x
|
|
res = gl_fill(res, p, vec2(max(f0, lo.x + px), lo.y + px), vec2(min(f1, hi.x - px), hi.y - px), vec2(lo.x + px, hi.x - px), px, self.disabled)
|
|
let hx = self.slide_pos * self.rect_size.x
|
|
let show = max(self.hover, self.drag)
|
|
let cap = gl_cap(p, vec2(hx - 3.0, cy - 5.0), vec2(hx + 3.0, cy + 5.0), 1.5, px, 0.0, 0.0, self.disabled, 0.0, 1.0, vec3(0.0, 0.0, 0.0), vec4(0.196, 0.204, 0.227, 1.0), 1.0)
|
|
return Finish.over(res, cap * show)
|
|
}
|
|
|
|
// The round slider: a label column, then the bar slider's 4 point slot with
|
|
// the lit fill and the round cap. The slot ends short of the right edge, so
|
|
// the value the widget writes there stands on the ground, not on the track.
|
|
mod.widgets.SliderRoundFlat.clip_x = false
|
|
mod.widgets.SliderRoundFlat.clip_y = false
|
|
mod.widgets.SliderRoundFlat.draw_bg.vertex = fn() {
|
|
let m = 6.0
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
|
|
}
|
|
mod.widgets.SliderRoundFlat.draw_bg.pixel = fn() {
|
|
let m = 6.0
|
|
let px = gl_px(self.draw_pass.dpi_factor)
|
|
let o = self.rect_pos
|
|
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
let rad = 7.0
|
|
let cy = gl_snap(self.rect_size.y * 0.5, o.y, px)
|
|
let x0 = gl_snap(self.label_size, o.x, px)
|
|
let lo = vec2(x0, cy - 2.0)
|
|
let hi = vec2(gl_snap(self.rect_size.x - 37.0, o.x, px), cy + 2.0)
|
|
var res = gl_well(p, lo, hi, 2.0, px, vec3(0.141, 0.149, 0.176), 0.0, self.disabled, 1.0)
|
|
let span = hi.x - lo.x - rad * 2.0
|
|
let hx = lo.x + rad + self.slide_pos * span
|
|
res = gl_fill(res, p, vec2(lo.x + px, lo.y + px), vec2(hx, hi.y - px), vec2(lo.x + px, hi.x - px), px, self.disabled)
|
|
let held = max(self.drag, self.focus)
|
|
return gl_handle(res, p, vec2(hx, cy), rad, px, self.hover, held, self.disabled)
|
|
}
|
|
|
|
// The fader: the slider bench's cove cap with a white line ("Cove cap, white
|
|
// line", a wedge), drawn by the cap engine (mod.sdf.Cap) in this sheet's
|
|
// charcoal. A narrow level ridge over the middle sixth of the cap carries a
|
|
// thin line painted across it in the LED's blue; concave coves sweep from the
|
|
// ridge down to a thin brim at either end; across, the top runs level to the
|
|
// sides and drops straight over a sharp edge. It is metal under a plain sky:
|
|
// it shows the sky and the ground reflected, tinted by the charcoal, bright
|
|
// where a cove faces the light and dark where it turns away, and the painted
|
|
// line catches the highlight almost white. It rides a 5 point slot lit in the
|
|
// LED's blue from the stop (or from the origin) to the value, the light's
|
|
// halo round the lit stretch beside the cap, and it throws a firm shadow down
|
|
// and a little to the right. The shape and the material are his bench preset
|
|
// number for number: no perspective, no ribs, no cut, no scale, no well. The
|
|
// inks are this sheet's where it has them.
|
|
mod.widgets.SliderFader.cap_size = 26.0
|
|
mod.widgets.SliderFader.track_inset = 8.0
|
|
mod.widgets.SliderFader.draw_bg.track_size = uniform(5.0)
|
|
mod.widgets.SliderFader.draw_bg.cap_cross = uniform(0.44)
|
|
mod.widgets.SliderFader.clip_x = false
|
|
mod.widgets.SliderFader.clip_y = false
|
|
// The material, live in the tweaker: the key light in screen space (x right,
|
|
// y down, z toward the viewer) and its intensity; a strong, broad highlight
|
|
// and a high gloss sheen; the rim toward grazing; a light occlusion held
|
|
// close to the foot (the higher the reach, the closer it hugs the ground);
|
|
// the plain sky's reflection and how close the eye sits over the cap (0 far
|
|
// above); full metal, so the reflection and the highlight take the body's
|
|
// charcoal; turned grain and self-light, both off; the exposure in stops and
|
|
// the highlight roll-off; the cast shadow's strength and its softest edge
|
|
// (held to the cap's height), the contact shadow's strength and the slot's
|
|
// inner shadow.
|
|
mod.widgets.SliderFader.draw_bg.cap_light = uniform(vec3(-0.22, -0.72, 0.66))
|
|
mod.widgets.SliderFader.draw_bg.cap_light_intensity = uniform(0.95)
|
|
mod.widgets.SliderFader.draw_bg.cap_specular = uniform(1.0)
|
|
mod.widgets.SliderFader.draw_bg.cap_roughness = uniform(0.78)
|
|
mod.widgets.SliderFader.draw_bg.cap_gloss = uniform(0.82)
|
|
mod.widgets.SliderFader.draw_bg.cap_rim = uniform(0.9)
|
|
mod.widgets.SliderFader.draw_bg.cap_ao = uniform(0.22)
|
|
mod.widgets.SliderFader.draw_bg.cap_ao_reach = uniform(2.1)
|
|
mod.widgets.SliderFader.draw_bg.cap_reflection = uniform(0.68)
|
|
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(0.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(6.0)
|
|
mod.widgets.SliderFader.draw_bg.cap_contact = uniform(0.5)
|
|
mod.widgets.SliderFader.draw_bg.cap_inner = uniform(0.7)
|
|
// The inks. The cap is this sheet's charcoal cap face (its disabled face
|
|
// when disabled) on the window's ground. The painted line is the LED's blue,
|
|
// the sheet's value colour, and so is the lit value, which takes the
|
|
// widget's own `val_color`; the slot takes the widget's own `color`, the
|
|
// darkest of the sheet's well inks. The light is his warm white and the
|
|
// shade his grey: the dark theme under this sheet sets them to pure white
|
|
// and black, which would shade the metal harder than he drew it. Focus
|
|
// rings the cap in the LED's rim blue.
|
|
// The body is his charcoal (#1c2527 in his Splash), a step darker than the sheet's outset, so the metal reads near-black with the teal of the bench.
|
|
mod.widgets.SliderFader.draw_bg.cap_body = uniform(#1c2527)
|
|
mod.widgets.SliderFader.draw_bg.cap_body_disabled = uniform(theme.color_outset_disabled)
|
|
mod.widgets.SliderFader.draw_bg.cap_ground = uniform(theme.color_bg_app)
|
|
mod.widgets.SliderFader.draw_bg.cap_light_ink = uniform(#f4f2ee)
|
|
mod.widgets.SliderFader.draw_bg.cap_shadow_ink = uniform(#46474c)
|
|
mod.widgets.SliderFader.draw_bg.cap_mark_ink = uniform(theme.color_val)
|
|
mod.widgets.SliderFader.draw_bg.cap_focus_ink = uniform(theme.color_focus)
|
|
|
|
// The cove cap's solid at `q`, a point in the cap's own frame (x along the
|
|
// track, y across it), `hs` the cap's half size and `r` its corner: the
|
|
// height 0..1 in .x, its gradient per point in .yz, and in .w the
|
|
// footprint's distance, negative inside. The curves are the bench's, handles
|
|
// resolved. Along, read from the centre to one end and mirrored: level over
|
|
// the middle sixth, then a cove falling through 0.86, 0.46, 0.19 and 0.07 to
|
|
// a thin brim of 0.05 just short of the end, and nothing at it. Across:
|
|
// level to 0.94 of the half width, then straight down. The edge reaches the
|
|
// full height within 0.06 of the edge width: a sharp edge. His plan curve is
|
|
// level at 1, which is no plan. Shape: corner 0.32 and edge width 0.1 of the
|
|
// smaller half size, blend 0.01, 15 points tall. No ribs and no cut; the
|
|
// mark is paint (1) across the cap (0) at its middle, 0.5 points wide over
|
|
// 0.66 of its width, which leaves the solid as it is.
|
|
let gl_cove_solid = fn(q: vec2, hs: vec2, r: float) -> vec4 {
|
|
let fp = Cap.sd_cap(q, hs, r, vec2(1.0, 0.0))
|
|
let along = Cap.mirror(Cap.curve(abs(q.x) / hs.x, 8.0, vec2(0.0, 1.0), vec2(0.0533, 0.0), vec2(0.0, 0.0), vec2(0.16, 1.0), vec2(0.0, 0.0), vec2(-0.01, 0.0386), vec2(0.19, 0.86), vec2(0.01, -0.0386), vec2(-0.0367, 0.0847), vec2(0.3, 0.46), vec2(0.0367, -0.0847), vec2(-0.06, 0.0557), vec2(0.48, 0.19), vec2(0.06, -0.0557), vec2(-0.0733, 0.0223), vec2(0.72, 0.07), vec2(0.0733, -0.0223), vec2(-0.02, 0.0), vec2(0.94, 0.05), vec2(0.02, 0.0), vec2(0.0, 0.0), vec2(1.0, 0.0)), q.x)
|
|
let across = Cap.curve(abs(q.y) / hs.y, 3.0, vec2(0.0, 1.0), vec2(0.1567, 0.0), vec2(0.0, 0.0), vec2(0.94, 1.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 0.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 0.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 0.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 0.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 0.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 0.0))
|
|
let edge = Cap.curve(Cap.edge_x(fp.x, hs, 0.1), 3.0, vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(0.06, 1.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 1.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 1.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 1.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 1.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 1.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 1.0))
|
|
let f = Cap.field(q, hs, fp, along, across, edge, vec2(1.0, 0.0), vec4(0.32, 0.1, 0.01, 15.0), vec4(0.0, 0.0, 0.0, 0.0), vec4(1.0, 0.5, 0.35, 0.0), vec4(0.0, 0.5, 0.66, 0.0), vec4(0.0, 0.0, 0.0, 0.0))
|
|
return vec4(f.x, f.y, f.z, fp.x)
|
|
}
|
|
|
|
// How far the fader's quad reaches past its rect, in points: the throw of
|
|
// the cap's cast shadow from its 15 point height along the key light (about
|
|
// 12 points from this light), the blur at the shadow's tip (the engine holds
|
|
// it between 0.35 and 1 times the height), two points for the contact
|
|
// shadow's tail, and the glow's reach when the cap emits. The cap itself
|
|
// never leaves the rect: it is no wider than the fader and stops at the
|
|
// track's ends.
|
|
let gl_fader_room = fn(lgt: vec3, blur: float, glo: float) -> float {
|
|
let ln = normalize(lgt)
|
|
return 15.0 * 0.7 * length(ln.xy) / max(ln.z, 0.25) + clamp(blur, 5.25, 15.0) + 24.0 * step(0.001, glo) + 2.0
|
|
}
|
|
|
|
// The fader laid over the ground without painting it: the share of the
|
|
// ground it darkens away and the light it puts back, premultiplied, exact
|
|
// for any colour. Cap.on_ground takes the ground away by the darkest
|
|
// channel's share but puts back only what each channel has above the
|
|
// ground, so a colour below the ground in one channel and above it in
|
|
// another comes out short in the channels above it: the slot's dark floor
|
|
// and the cap's shadow under the LED's halo lost up to 50 levels of blue.
|
|
let gl_fader_on_ground = fn(col: vec3, gnd: vec3) -> vec4 {
|
|
let dk = max(gnd - col, vec3(0.0, 0.0, 0.0)) / max(gnd, vec3(0.01, 0.01, 0.01))
|
|
let k = clamp(max(dk.x, max(dk.y, dk.z)), 0.0, 1.0)
|
|
return vec4(max(col - gnd * (1.0 - k), vec3(0.0, 0.0, 0.0)), k)
|
|
}
|
|
|
|
mod.widgets.SliderFader.draw_bg.vertex = fn() {
|
|
let m = gl_fader_room(self.cap_light, self.cap_shadow_blur, self.cap_glow)
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
|
|
}
|
|
mod.widgets.SliderFader.draw_bg.pixel = fn() {
|
|
let m = gl_fader_room(self.cap_light, self.cap_shadow_blur, self.cap_glow)
|
|
let px = gl_px(self.draw_pass.dpi_factor)
|
|
let sz = self.rect_size
|
|
let o = self.rect_pos
|
|
let p = self.pos * (sz + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
let v = self.is_vertical
|
|
let along = mix(sz.x, sz.y, v)
|
|
let across = mix(sz.y, sz.x, v)
|
|
let inset = self.inset_px
|
|
let travel = max(along - inset * 2.0 - self.cap_px, 1.0)
|
|
let foot = inset + self.cap_px * 0.5
|
|
// Track space: `ab.x` along the track from the value's zero (up the
|
|
// screen when the fader stands), `ab.y` across it.
|
|
let ab = vec2(mix(p.x, sz.y - p.y, v), mix(p.y, p.x, v))
|
|
let dis = self.disabled
|
|
let awake = 1.0 - dis
|
|
let ground = self.cap_ground.rgb
|
|
let light_ink = self.cap_light_ink.rgb
|
|
let shadow_ink = self.cap_shadow_ink.rgb
|
|
// The key light on screen for the cap's shading, and turned into track
|
|
// space for everything laid out along the track.
|
|
let l = normalize(self.cap_light)
|
|
let lt = Cap.light(l, v)
|
|
// The cap's half size: cap_size along the track, cap_cross of the
|
|
// fader's width across, held to 4 points and to the width as the widget
|
|
// holds it.
|
|
let hs = vec2(self.cap_px * 0.5, min(max(across * self.cap_cross, 4.0), across) * 0.5)
|
|
// The slot's middle on the device pixel grid, so its two walls
|
|
// antialias alike, and the cap's lower end on it, so the cap keeps one
|
|
// shape as it moves; 26 points are an odd 39 pixels at 1.5, so the thin
|
|
// line at its middle then falls on one pixel column instead of two dim
|
|
// ones.
|
|
let mid = gl_snap(across * 0.5, mix(o.y, o.x, v), px)
|
|
let end_free = foot + self.slide_pos * travel - hs.x
|
|
let se = Finish.snap(mix(o.x + end_free, o.y + sz.y - end_free, v), px)
|
|
let at = mix(se - o.x, o.y + sz.y - se, v) + hs.x
|
|
|
|
// The ground: the slot, its ends rounded by half its half width, held
|
|
// track_inset off the fader's ends, and the value on its floor. A value
|
|
// that starts at the stop starts at the slot's end, so no unlit length
|
|
// shows below the cap's first position. Lit, the value is the LED: the
|
|
// engine draws a lit fill at 1.2 times the ink it is handed, which takes
|
|
// the LED's blue past full and the roll-off spreads the excess into the
|
|
// other channels, so it is handed the LED divided by that and its core
|
|
// lands on the sheet's value colour, less what the roll-off takes off
|
|
// its blue; the same ink lights the halo and the cap's end
|
|
// wall, as the engine pairs them. Disabled, the LED is out: a dark bar
|
|
// under the slot's shade and no halo.
|
|
let tw = min(self.track_size, across)
|
|
let tlen = max(along - inset * 2.0, 1.0)
|
|
let tbox = vec4(inset + tlen * 0.5, mid, tlen * 0.5, tw * 0.5)
|
|
var lo = foot + self.fill_lo * travel
|
|
var hi = foot + self.fill_hi * travel
|
|
if self.fill_lo < 0.0005 {
|
|
lo = inset
|
|
}
|
|
if self.fill_hi > 0.9995 {
|
|
hi = along - inset
|
|
}
|
|
let lit = step(0.5, awake)
|
|
let led = mix(self.val_color.rgb / 1.2, self.val_color_disabled.rgb, dis)
|
|
let track_col = Cap.slot(ground, ab, tbox, tw * 0.25, 1.0 + lit, lo, hi, at, hs.x, lt, px, 0.0, 0.0, self.color.rgb, led, light_ink, shadow_ink, self.cap_inner, self.cap_ao)
|
|
// The halo's faint tail fades out over the margin by the distance from
|
|
// the rect, so the quad's edge never shows on the ground as a box.
|
|
let past = length(max(abs(p - sz * 0.5) - sz * 0.5, vec2(0.0, 0.0)))
|
|
var col = mix(ground, track_col, 1.0 - smoothstep(0.0, m, past))
|
|
|
|
// The cap, worked out only where it, its shadow or its glow can reach.
|
|
// Held, it sinks half a point, so its shadow shortens.
|
|
let q = ab - vec2(at, mid)
|
|
let r = 0.32 * min(hs.x, hs.y)
|
|
let height = 15.0
|
|
let press = self.drag * awake
|
|
let rise = height - 0.5 * press
|
|
// Disabled, the cap takes this sheet's disabled face.
|
|
let body = mix(self.cap_body.rgb, self.cap_body_disabled.rgb, dis)
|
|
var capc = ground
|
|
var cov = 0.0
|
|
var shd = 0.0
|
|
var halo = 0.0
|
|
var ring = 0.0
|
|
let far = abs(q) - hs
|
|
if max(far.x, far.y) < m {
|
|
let s = gl_cove_solid(q, hs, r)
|
|
let n = Cap.normal(s.yz, height, v)
|
|
let eye = Cap.view(q, hs, self.cap_reflection_perspective, v)
|
|
// The line is painted where the cap's own point lies, as the bench
|
|
// paints it. Held, it lights in the LED's blue, this sheet's sign of
|
|
// a held cap: the engine lights a mark at 1.15 times its ink, so the
|
|
// ink is divided by that as the press comes on and the lit line
|
|
// shows the LED's blue. A disabled cap is matte, its line the dark
|
|
// LED.
|
|
let paint = Cap.mark_line(q, hs, vec4(1.0, 0.5, 0.35, 0.0), vec4(0.0, 0.5, 0.66, 0.0), px)
|
|
let mark_ink = mix(self.cap_mark_ink.rgb / mix(1.0, 1.15, press), self.val_color_disabled.rgb, dis)
|
|
capc = Cap.shade(n, l, eye, s.x, body, paint, paint * press, 1.0, self.cap_specular * awake, self.cap_roughness, self.cap_gloss * awake, self.cap_rim * awake, self.cap_ao, self.cap_ao_reach, self.cap_reflection, self.cap_metal, self.cap_light_intensity, mark_ink, light_ink, shadow_ink, ground)
|
|
if self.cap_spun > 0.001 {
|
|
let grain = Cap.spun(q, hs, lt.xy) * self.cap_spun * (1.0 - paint)
|
|
capc = Cap.in_shadow(Cap.in_light(capc, clamp(grain, 0.0, 1.0) * 0.55, light_ink), clamp(-grain, 0.0, 1.0) * 0.42, shadow_ink)
|
|
}
|
|
if self.cap_glow > 0.001 {
|
|
capc = Cap.emit(capc, s.x, body, light_ink, self.cap_glow)
|
|
}
|
|
// Under the hand the face lifts by what this sheet's small caps lift
|
|
// (gl_cap's 0.04), laid over the shading so it reads the same on the
|
|
// lit and the shaded cove. A held or a disabled cap does not lift,
|
|
// as in gl_cap.
|
|
capc = capc + vec3(0.04, 0.04, 0.044) * self.hover * (1.0 - press) * awake * (1.0 - paint)
|
|
// The lit value still showing beside the cap lights the end wall
|
|
// that faces it, never the line.
|
|
capc = capc + led * Cap.fill_light(q, hs, s.yz * height, lo, hi, at, tw) * lit * (1.0 - paint)
|
|
cov = Cap.cover(s.w, px)
|
|
// Under it: the cast shadow, thrown from the top outline the bench
|
|
// gives for this cap (its narrow ridge, almost its full width), and
|
|
// the contact shadow round its foot. A disabled cap sits on the
|
|
// ground as this sheet seats its disabled caps: no cast shadow, a
|
|
// weaker contact.
|
|
let cast_sh = Cap.shadow(q, hs, r, vec3(0.2235, 0.9529, 1.0), lt, rise, self.cap_shadow_blur, self.cap_shadow, px) * awake
|
|
let contact_sh = Cap.contact(s.w, max(rise * 0.3, 1.0), self.cap_contact * (1.0 - 0.6 * dis))
|
|
shd = clamp(cast_sh + contact_sh, 0.0, 1.0)
|
|
halo = Cap.glow(s.w, hs, self.cap_glow)
|
|
// Focus: this sheet's ring of the LED's rim blue just outside the
|
|
// outline, and a two point breath of it.
|
|
ring = clamp(Finish.ring_out(s.w, 0.0, px, px) * 0.9 + Finish.glow(s.w - px, 0.7) * 0.3 * (1.0 - cov), 0.0, 1.0) * self.focus * awake
|
|
}
|
|
col = mix(col, col * shadow_ink, shd * (1.0 - cov))
|
|
col = col + body * halo * 0.9 * (1.0 - cov)
|
|
col = mix(col, capc, cov)
|
|
col = mix(col, self.cap_focus_ink.rgb, ring)
|
|
// Toned, both the fader and the ground alike, and laid over the ground.
|
|
let ev = pow(2.0, self.cap_exposure)
|
|
return gl_fader_on_ground(Cap.tone(col, ev, self.cap_rolloff), Cap.tone(ground, ev, self.cap_rolloff))
|
|
}
|
|
|
|
// ---------------------------------------------------------------- knobs
|
|
|
|
// A knob: a well ring round a raised round cap, the value lit in the ring
|
|
// as a thin arc without a halo, and an LED bar on the cap for a pointer.
|
|
// Angles run from straight down, clockwise, the gap at the bottom.
|
|
let gl_knob = fn(p: vec2, c: vec2, rr: float, px: float, slide: float, origin: float, arc_origin: float, gap: float, hover: float, held: float, focus: float, disabled: float) -> vec4 {
|
|
// The ring scales with a small knob and stops at a 1.5 point gap and a
|
|
// 4 point width, so a large dial stays slim instead of a heavy donut.
|
|
let rc = max(rr / 1.32, rr - 5.5)
|
|
let ring_in = rc + min(0.08 * rc, 1.5)
|
|
let ring_out = ring_in + min(0.24 * rc, 4.0)
|
|
let ring_mid = (ring_in + ring_out) * 0.5
|
|
let ring_w = ring_out - ring_in
|
|
let dr = Finish.sd_ring(p, c, ring_mid, ring_w)
|
|
let q = p - c
|
|
let rl = length(q)
|
|
// The ring well: lip on the ground under its lower outer edge, the
|
|
// black outline both sides, a dark fill.
|
|
var res = vec4(0.28, 0.30, 0.34, 1.0) * Finish.ring_out(rl - ring_out, 0.0, px, px) * step(c.y + ring_out * 0.3, p.y)
|
|
var col = vec3(0.141, 0.149, 0.176)
|
|
col = mix(col, vec3(0.039, 0.039, 0.047), Finish.band(dr, 0.0, px, px))
|
|
let ra = Finish.cover(dr, px)
|
|
res = Finish.over(res, vec4(col * ra, ra))
|
|
// The value arc, lit like the lamp: 1.5 points on the middle of the
|
|
// ring, a pale core, the accent at its edges and a soft halo on the
|
|
// ring's floor round it. Angles from straight down, clockwise.
|
|
let one = PI / 180.0
|
|
let sweep = 2.0 * PI - gap * one
|
|
let start = gap * one * 0.5
|
|
let ve = start + sweep * slide
|
|
let oe = start + sweep * origin
|
|
let a0 = mix(start, min(oe, ve), arc_origin)
|
|
let a1 = mix(ve, max(oe, ve), arc_origin)
|
|
var ang = atan2(-q.x, q.y)
|
|
if ang < 0.0 {
|
|
ang = ang + 2.0 * PI
|
|
}
|
|
let ac = clamp(ang, a0, a1)
|
|
let ap = c + vec2(-sin(ac), cos(ac)) * ring_mid
|
|
let da = length(p - ap)
|
|
let live = step(0.002, a1 - a0)
|
|
let ink = mix(vec3(0.227, 0.655, 0.965), vec3(0.227, 0.29, 0.388), disabled)
|
|
let floor_in = Finish.cover(dr + px, px)
|
|
let xa = max(da - 0.75, 0.0)
|
|
let ga = (0.30 * exp(-(xa * xa) / (2.0 * 0.55 * 0.55)) + 0.14 * exp(-(xa * xa) / (2.0 * 1.5 * 1.5))) * floor_in * (1.0 - Finish.cover(da - 0.75, px)) * (1.0 - disabled) * live
|
|
res = Finish.over(res, vec4(0.227, 0.655, 0.965, 1.0) * ga)
|
|
let acol = mix(mix(vec3(0.227, 0.655, 1.0), vec3(1.0, 1.0, 1.0), 0.72 * (1.0 - disabled)), ink, smoothstep(0.1, 0.7, da))
|
|
res = Finish.over(res, vec4(acol, 1.0) * Finish.cover(da - 0.75, px) * live)
|
|
// The cap.
|
|
res = Finish.over(res, gl_cap(p, c - vec2(rc, rc), c + vec2(rc, rc), rc, px, hover, 0.0, disabled, focus, 1.0, vec3(0.0, 0.0, 0.0), vec4(0.196, 0.204, 0.227, 1.0), 1.0))
|
|
// A hint of reflection on the lacquered cap: a soft pale spot up and to
|
|
// the left, inside the outline.
|
|
let sp = p - (c + vec2(-0.3 * rc, -0.42 * rc))
|
|
let rs = 0.3 * rc
|
|
let refl = exp(-dot(sp, sp) / max(2.0 * rs * rs, 0.001)) * 0.05 * Finish.cover(rl - rc + px * 2.0, px) * (1.0 - disabled)
|
|
res = Finish.over(res, vec4(0.80, 0.83, 0.88, 1.0) * refl)
|
|
// The pointer: an LED bar 0.3D long from 0.12D inside the rim.
|
|
let dir = vec2(-sin(ve), cos(ve))
|
|
let per = vec2(-dir.y, dir.x)
|
|
let lp = vec2(dot(q, dir), dot(q, per))
|
|
let len = 0.6 * rc
|
|
let tip = rc - 0.24 * rc
|
|
// Lit, a two device pixel core and a one pixel rim, no halo at rest;
|
|
// a held knob breathes the lamp's halo.
|
|
res = gl_led(res, lp, vec2(tip - len * 0.5, 0.0), vec2(len * 0.5, px), 1.0 - disabled, 0.5 * held, px, vec3(0.94, 0.984, 1.0), vec3(0.31, 0.506, 0.702))
|
|
return res
|
|
}
|
|
|
|
mod.widgets.RotaryKnob.clip_x = false
|
|
mod.widgets.RotaryKnob.clip_y = false
|
|
mod.widgets.RotaryKnob.draw_bg.vertex = fn() {
|
|
let m = 5.0
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
|
|
}
|
|
mod.widgets.RotaryKnob.draw_bg.pixel = fn() {
|
|
let m = 5.0
|
|
let px = gl_px(self.draw_pass.dpi_factor)
|
|
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
let dia = min(self.rect_size.x, self.rect_size.y)
|
|
let c = vec2(Finish.snap(self.rect_pos.x + self.rect_size.x * 0.5, px) - self.rect_pos.x, Finish.snap(self.rect_pos.y + self.rect_size.y - dia * 0.5, px) - self.rect_pos.y)
|
|
return gl_knob(p, c, dia * 0.5 - 1.0, px, self.slide_pos, self.origin_pos, self.arc_origin, self.gap, self.hover, self.drag, self.focus, self.disabled)
|
|
}
|
|
mod.widgets.RotaryFlat.clip_x = false
|
|
mod.widgets.RotaryFlat.clip_y = false
|
|
mod.widgets.RotaryFlat.draw_bg.vertex = fn() {
|
|
let m = 5.0
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
|
|
}
|
|
mod.widgets.RotaryFlat.draw_bg.pixel = fn() {
|
|
let m = 5.0
|
|
let px = gl_px(self.draw_pass.dpi_factor)
|
|
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
let dia = min(self.rect_size.x, self.rect_size.y - 22.0)
|
|
let c = vec2(Finish.snap(self.rect_pos.x + self.rect_size.x * 0.5, px) - self.rect_pos.x, Finish.snap(self.rect_pos.y + 20.0 + dia * 0.5, px) - self.rect_pos.y)
|
|
return gl_knob(p, c, dia * 0.5 - 1.0, px, self.slide_pos, self.origin_pos, self.arc_origin, self.gap, self.hover, self.drag, self.focus, self.disabled)
|
|
}
|
|
|
|
// ---------------------------------------------------------------- fields
|
|
|
|
// A field well that keeps inside its own box: a text field clips what it
|
|
// holds, so its right outline takes the box's last whole column and its lip
|
|
// the last whole row. Focus lights a lamp along the foot of the well: a
|
|
// pale line with the lamp's rim colour under it and a soft halo rising on
|
|
// the floor; the outline takes a little of the blue.
|
|
let gl_field = fn(p: vec2, size: vec2, o: vec2, px: float, fill: vec3, hover: float, focus: float, disabled: float) -> vec4 {
|
|
let lo = vec2(gl_snap(0.0, o.x, px), gl_snap(0.0, o.y, px))
|
|
// The box's far edges rounded DOWN to a whole device pixel; the bottom
|
|
// outline one pixel above the last row, the lip on it.
|
|
let hi = vec2(gl_snap(size.x - px * 0.49, o.x, px), gl_snap(size.y - px * 0.49, o.y, px) - px)
|
|
let c = (lo + hi) * 0.5
|
|
let d = Material.sd_box(p, c, (hi - lo) * 0.5, 2.0)
|
|
// Under the hand the floor lifts 0.025 and the outline takes a fifth of
|
|
// the lamp's blue.
|
|
var f = mix(fill + vec3(0.025, 0.025, 0.027) * hover, vec3(0.180, 0.188, 0.220), disabled)
|
|
var res = gl_well(p, lo, hi, 2.0, px, f, 0.0, disabled, 1.0)
|
|
let fo = focus * (1.0 - disabled)
|
|
let ho = hover * (1.0 - disabled) * (1.0 - focus)
|
|
res = Finish.over(res, vec4(0.31, 0.506, 0.702, 1.0) * Finish.band(d, 0.0, px, px) * (0.45 * fo + 0.2 * ho))
|
|
// The foot lamp: the two rows above the bottom outline, inset past the
|
|
// corners.
|
|
let y1 = hi.y - px
|
|
let infloor = Finish.cover(d + px, px) * step(lo.x + 3.0, p.x) * step(p.x, hi.x - 3.0)
|
|
let dy = max(y1 - px - p.y, 0.0)
|
|
let g = (0.34 * exp(-(dy * dy) / (2.0 * 0.7 * 0.7)) + 0.14 * exp(-(dy * dy) / (2.0 * 2.0 * 2.0))) * step(p.y, y1 - px) * infloor
|
|
res = Finish.over(res, vec4(0.31, 0.506, 0.702, 1.0) * g * fo)
|
|
let rim = step(y1 - px, p.y) * step(p.y, y1) * infloor
|
|
let core = step(y1 - px * 2.0, p.y) * step(p.y, y1 - px) * infloor
|
|
res = Finish.over(res, vec4(0.31, 0.506, 0.702, 1.0) * rim * fo)
|
|
res = Finish.over(res, vec4(0.84, 0.94, 1.0, 1.0) * core * fo)
|
|
return res
|
|
}
|
|
|
|
// Text fields: a flat well 24 points tall, -0.10 against the ground. An
|
|
// input with no fill of its own (the one a slider, a combo box or a well
|
|
// holds) draws nothing.
|
|
mod.widgets.TextInputFlat.draw_bg.pixel = fn() {
|
|
if self.color.a < 0.01 {
|
|
return vec4(0.0, 0.0, 0.0, 0.0)
|
|
}
|
|
let px = gl_px(self.draw_pass.dpi_factor)
|
|
return gl_field(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, vec3(0.141, 0.149, 0.176), self.hover, self.focus, self.disabled)
|
|
}
|
|
mod.widgets.TextInputFlat.padding = mod.turtle.Inset{left: 8.0 right: 8.0 top: 3.0 bottom: 3.0}
|
|
mod.widgets.TextInputFlat.min_height = 24.0
|
|
mod.widgets.TextInputFlat.draw_cursor.color = #f0fbff
|
|
mod.widgets.TextInputFlat.draw_text.color = #d4d7de
|
|
mod.widgets.TextInputFlat.draw_text.color_empty = #6a6c74
|
|
mod.widgets.TextInputFlat.draw_text.color_disabled = #5c5e66
|
|
|
|
// The number field: the same well, its stepper a pair of small caps joined
|
|
// by an engraved line.
|
|
mod.widgets.NumberField.draw_bg.pixel = fn() {
|
|
let px = gl_px(self.draw_pass.dpi_factor)
|
|
return gl_field(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, vec3(0.141, 0.149, 0.176), self.hover, self.focus, self.disabled)
|
|
}
|
|
mod.widgets.NumberField.spin_width = 14.0
|
|
mod.widgets.NumberSpin.width = 14.0
|
|
// The widget's own filled triangles are hidden; the face strokes a 5 x 3
|
|
// point chevron in a 1 dp line of #9a9ca4 on each cap instead.
|
|
mod.widgets.NumberSpin.draw_text.color = #00000000
|
|
mod.widgets.NumberSpin.draw_text.color_disabled = #00000000
|
|
mod.widgets.NumberSpin.draw_bg.pixel = fn() {
|
|
let px = gl_px(self.draw_pass.dpi_factor)
|
|
let o = self.rect_pos
|
|
let p = self.pos * self.rect_size
|
|
// The stepper lies inside the field's well: its own floor is the
|
|
// field's, set off by an engraved divider (dark, then light) and split
|
|
// by an engraved line across the middle. The field's outline and lip
|
|
// stay the field's.
|
|
let x0 = gl_snap(0.0, o.x, px)
|
|
let xr = gl_snap(self.rect_size.x - px * 0.49, o.x, px) - px
|
|
let yt = gl_snap(0.0, o.y, px) + px
|
|
let yb = gl_snap(self.rect_size.y - px * 0.49, o.y, px) - px * 2.0
|
|
let mid = gl_snap(self.rect_size.y * 0.5, o.y, px)
|
|
let live = 1.0 - self.disabled
|
|
let inx = step(x0 + px * 2.0, p.x) * step(p.x, xr)
|
|
let upper = inx * step(yt, p.y) * step(p.y, mid - px)
|
|
let lower = inx * step(mid + px, p.y) * step(p.y, yb)
|
|
// Under the hand a half lifts a little; pressed, it sinks with a shade
|
|
// under its top edge.
|
|
var res = vec4(1.0, 1.0, 1.0, 1.0) * 0.035 * (upper * max(0.0, self.hot) + lower * max(0.0, -self.hot)) * live
|
|
let su = 0.45 * (1.0 - smoothstep(0.0, 3.0, p.y - yt)) * upper * max(0.0, self.down)
|
|
let sd = 0.45 * (1.0 - smoothstep(0.0, 3.0, p.y - mid - px)) * lower * max(0.0, -self.down)
|
|
res = Finish.over(res, vec4(0.0, 0.0, 0.0, 1.0) * (su + sd) * live)
|
|
let vin = step(yt + 2.0, p.y) * step(p.y, yb - 2.0)
|
|
let dark = step(x0, p.x) * step(p.x, x0 + px) * vin
|
|
let light = step(x0 + px, p.x) * step(p.x, x0 + px * 2.0) * vin
|
|
let hin = step(x0 + px * 2.0 + 2.0, p.x) * step(p.x, xr - 2.0)
|
|
let hdark = step(mid - px, p.y) * step(p.y, mid) * hin
|
|
let hlight = step(mid, p.y) * step(p.y, mid + px) * hin
|
|
res = Finish.over(res, vec4(0.039, 0.039, 0.047, 1.0) * (dark + hdark))
|
|
res = Finish.over(res, vec4(0.271, 0.278, 0.310, 1.0) * (light + hlight) * 0.8)
|
|
let ink = mix(vec3(0.604, 0.612, 0.643), vec3(0.36, 0.37, 0.4), self.disabled)
|
|
let cx = (x0 + px * 2.0 + xr) * 0.5
|
|
let cu = (yt + mid) * 0.5
|
|
let cd = (mid + yb) * 0.5
|
|
let du = min(gl_seg(p, vec2(cx - 2.5, cu + 1.5), vec2(cx, cu - 1.5)), gl_seg(p, vec2(cx, cu - 1.5), vec2(cx + 2.5, cu + 1.5)))
|
|
let dd = min(gl_seg(p, vec2(cx - 2.5, cd - 1.5), vec2(cx, cd + 1.5)), gl_seg(p, vec2(cx, cd + 1.5), vec2(cx + 2.5, cd - 1.5)))
|
|
let k = min(du, dd) - px * 0.5
|
|
return Finish.over(res, vec4(ink, 1.0) * Finish.cover(k, px))
|
|
}
|
|
|
|
// The drop-down: a button cap with an 18 point chevron zone at its right,
|
|
// behind an engraved divider.
|
|
mod.widgets.DropDownFlat.padding = mod.turtle.Inset{left: 8.0 right: 26.0 top: 3.0 bottom: 3.0}
|
|
mod.widgets.DropDownFlat.clip_x = false
|
|
mod.widgets.DropDownFlat.clip_y = false
|
|
mod.widgets.DropDownFlat.draw_bg.vertex = fn() {
|
|
let m = 7.0
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
|
|
}
|
|
// The engraved divider and the chevron, laid on a cap or a well.
|
|
let gl_chevron_zone = fn(dst: vec4, p: vec2, lo: vec2, hi: vec2, px: float, disabled: float) -> vec4 {
|
|
let dx = hi.x - 18.0
|
|
let inside = step(lo.y + 4.0, p.y) * step(p.y, hi.y - 4.0)
|
|
let dark = step(dx - px, p.x) * step(p.x, dx) * inside
|
|
let light = step(dx, p.x) * step(p.x, dx + px) * inside
|
|
var res = Finish.over(dst, vec4(0.039, 0.039, 0.047, 1.0) * dark)
|
|
res = Finish.over(res, vec4(0.271, 0.278, 0.310, 1.0) * light * 0.8)
|
|
// The chevron: 7 x 4 points, a 1.5 point stroke, a dark line under it.
|
|
let cc = vec2(dx + 9.0, (lo.y + hi.y) * 0.5)
|
|
let sd = Sdf2d.viewport(p - vec2(0.0, px))
|
|
sd.move_to(cc.x - 3.5, cc.y - 2.0)
|
|
sd.line_to(cc.x, cc.y + 2.0)
|
|
sd.line_to(cc.x + 3.5, cc.y - 2.0)
|
|
sd.stroke(vec4(0.0, 0.0, 0.0, 0.5), 0.75)
|
|
res = Finish.over(res, sd.result)
|
|
let sdf = Sdf2d.viewport(p)
|
|
sdf.move_to(cc.x - 3.5, cc.y - 2.0)
|
|
sdf.line_to(cc.x, cc.y + 2.0)
|
|
sdf.line_to(cc.x + 3.5, cc.y - 2.0)
|
|
sdf.stroke(vec4(mix(vec3(0.604, 0.612, 0.643), vec3(0.36, 0.37, 0.4), disabled), 1.0), 0.75)
|
|
return Finish.over(res, sdf.result)
|
|
}
|
|
mod.widgets.DropDownFlat.draw_bg.pixel = fn() {
|
|
let m = 7.0
|
|
let px = gl_px(self.draw_pass.dpi_factor)
|
|
let o = self.rect_pos
|
|
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
let lo = vec2(gl_snap(0.0, o.x, px), gl_snap(0.0, o.y, px))
|
|
let hi = vec2(gl_snap(self.rect_size.x, o.x, px), gl_snap(self.rect_size.y, o.y, px))
|
|
var res = gl_cap(p, lo, hi, 2.0, px, self.hover, max(self.down, self.active), self.disabled, self.focus, 1.0, vec3(0.0, 0.0, 0.0), vec4(0.196, 0.204, 0.227, 1.0), 0.0)
|
|
return gl_chevron_zone(res, p, lo, hi, px, self.disabled)
|
|
}
|
|
|
|
// The combo box: a text well, its list opened from the same chevron zone.
|
|
mod.widgets.ComboBoxFlat.padding = mod.turtle.Inset{left: 8.0 right: 26.0 top: 3.0 bottom: 3.0}
|
|
mod.widgets.ComboBoxFlat.input.min_height = 0.0
|
|
mod.widgets.ComboBoxFlat.draw_bg.pixel = fn() {
|
|
let px = gl_px(self.draw_pass.dpi_factor)
|
|
let p = self.pos * self.rect_size
|
|
let res = gl_field(p, self.rect_size, self.rect_pos, px, vec3(0.141, 0.149, 0.176), self.hover, self.focus, self.disabled)
|
|
let lo = vec2(0.0, 0.0)
|
|
let hi = vec2(gl_snap(self.rect_size.x - px * 0.49, self.rect_pos.x, px), gl_snap(self.rect_size.y - px * 0.49, self.rect_pos.y, px) - px)
|
|
return gl_chevron_zone(res, p, lo, hi, px, self.disabled)
|
|
}
|
|
|
|
// Open lists: a flat dark face, a 1 dp top line, rows that lift 0.03 under
|
|
// the hand, and the chosen row filled with the accent -- the one fill in
|
|
// this family.
|
|
let gl_menu = fn(p: vec2, size: vec2, px: float) -> vec4 {
|
|
let lo = vec2(0.0, 0.0)
|
|
let hi = size
|
|
let c = (lo + hi) * 0.5
|
|
let d = Material.sd_box(p, c, (hi - lo) * 0.5, 3.0)
|
|
var col = mix(vec3(0.173, 0.184, 0.216), vec3(0.157, 0.167, 0.198), p.y / max(size.y, 1.0))
|
|
col = mix(col, vec3(0.25, 0.26, 0.29), Finish.band(d, px, px * 2.0, px) * step(p.y, c.y) * step(3.0, p.y))
|
|
col = mix(col, vec3(0.039, 0.043, 0.055), Finish.band(d, 0.0, px, px))
|
|
let a = Finish.cover(d, px)
|
|
return vec4(col * a, a)
|
|
}
|
|
let gl_row = fn(p: vec2, size: vec2, px: float, hover: float, active: float, disabled: float) -> vec4 {
|
|
let top = 1.0 - step(px, p.y)
|
|
var col = vec4(1.0, 1.0, 1.0, 1.0) * 0.035 * hover * (1.0 - disabled)
|
|
let sel = vec3(0.227, 0.498, 0.753)
|
|
let fill = vec4(mix(sel, vec3(0.45, 0.66, 0.86), top), 1.0)
|
|
return mix(col, fill, active)
|
|
}
|
|
// The open list is clipped to its own box by the popup that holds it, so
|
|
// its shadow cannot be drawn from here.
|
|
mod.widgets.PopupMenuFlat.draw_bg.pixel = fn() {
|
|
let px = gl_px(self.draw_pass.dpi_factor)
|
|
return gl_menu(self.pos * self.rect_size, self.rect_size, px)
|
|
}
|
|
mod.widgets.PopupMenuItem.draw_bg.pixel = fn() {
|
|
let px = gl_px(self.draw_pass.dpi_factor)
|
|
return gl_row(self.pos * self.rect_size, self.rect_size, px, self.hover, self.active, self.disabled)
|
|
}
|
|
mod.widgets.PopupMenuItem.draw_text.color_active = #ffffff
|
|
mod.widgets.ComboBoxFlat.draw_popup_bg.pixel = fn() {
|
|
let px = gl_px(self.draw_pass.dpi_factor)
|
|
return gl_menu(self.pos * self.rect_size, self.rect_size, px)
|
|
}
|
|
mod.widgets.ComboBoxFlat.draw_item.pixel = fn() {
|
|
let px = gl_px(self.draw_pass.dpi_factor)
|
|
return gl_row(self.pos * self.rect_size, self.rect_size, px, self.hover, self.active, 0.0)
|
|
}
|
|
mod.widgets.ComboBoxFlat.draw_item_text.color_active = #ffffff
|
|
|
|
// ---------------------------------------------------------------- tabs
|
|
|
|
// The segmented control: the whole strip one raised cap; the chosen answer
|
|
// sinks, flat and tinted toward the light, its legend lit and a lamp under
|
|
// it.
|
|
mod.widgets.SegmentedControlFlat.draw_bg.vertex = fn() {
|
|
let m = 7.0
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
|
|
}
|
|
mod.widgets.SegmentedControlFlat.draw_bg.pixel = fn() {
|
|
let m = 7.0
|
|
let px = gl_px(self.draw_pass.dpi_factor)
|
|
let o = self.rect_pos
|
|
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
let lo = vec2(gl_snap(0.0, o.x, px), gl_snap(0.0, o.y, px))
|
|
let hi = vec2(gl_snap(self.rect_size.x, o.x, px), gl_snap(self.rect_size.y, o.y, px))
|
|
return gl_cap(p, lo, hi, 2.0, px, 0.0, 0.0, 0.0, 0.0, 1.0, vec3(0.0, 0.0, 0.0), vec4(0.196, 0.204, 0.227, 1.0), 1.0)
|
|
}
|
|
// The sunk segment: no top line, a shade falling from the top, the face
|
|
// tinted #2c3643, and a 1 dp tinted edge either side where it meets its
|
|
// neighbours.
|
|
let gl_sunk = fn(p: vec2, lo: vec2, hi: vec2, px: float, hover: float) -> vec4 {
|
|
let hh = hi.y - lo.y
|
|
let t = clamp((p.y - lo.y) / max(hh, 1.0), 0.0, 1.0)
|
|
var col = mix(vec3(0.173, 0.212, 0.263), vec3(0.133, 0.157, 0.192), t) + vec3(0.01, 0.01, 0.01) * hover
|
|
col = mix(col, vec3(0.0, 0.0, 0.0), 0.5 * (1.0 - smoothstep(0.0, 0.2 * hh, p.y - lo.y)))
|
|
let edge = (1.0 - step(lo.x + px, p.x)) + step(hi.x - px, p.x)
|
|
col = mix(col, vec3(0.349, 0.412, 0.471), edge * 0.8)
|
|
let inside = step(lo.x, p.x) * step(p.x, hi.x) * step(lo.y, p.y) * step(p.y, hi.y)
|
|
return vec4(col, 1.0) * inside
|
|
}
|
|
// The chosen answer is a push button pressed in: Day, Week and Month are
|
|
// three caps and the chosen one is sunk deep between the others. Its well
|
|
// runs from under the strip's top outline to its bottom outline, black
|
|
// under the top edge and shaded along both walls, and the neighbours' ends
|
|
// stand lit over it. The strip lays the answers 2 points in from its edge;
|
|
// the quad grows so the well can reach the outline and the neighbours' ends.
|
|
mod.widgets.SegmentedControlFlat.draw_pill.vertex = fn() {
|
|
let m = 9.0
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
|
|
}
|
|
// On the column (its pill's `radius`, unused by this face, set to -1 as the
|
|
// mark) the well keeps to its own segment's height -- the answers above and
|
|
// below are the neighbours -- and reaches the strip's side outlines instead.
|
|
mod.widgets.SegmentedControlFlat.draw_pill.pixel = fn() {
|
|
let m = 9.0
|
|
let v = 1.0 - step(0.0, self.radius)
|
|
let hz = 1.0 - v
|
|
let px = gl_px(self.draw_pass.dpi_factor)
|
|
let o = self.rect_pos
|
|
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
let lo = vec2(gl_snap(-2.0 * v, o.x, px) + px * v, gl_snap(-2.0 * hz, o.y, px) + px * hz)
|
|
let hi = vec2(gl_snap(self.rect_size.x + 2.0 * v, o.x, px) - px * v, gl_snap(self.rect_size.y + 2.0 * hz, o.y, px) - px * hz)
|
|
let hh = hi.y - lo.y
|
|
let t = clamp((p.y - lo.y) / max(hh, 1.0), 0.0, 1.0)
|
|
// The floor, dark and a little blue.
|
|
var col = mix(vec3(0.173, 0.212, 0.263), vec3(0.133, 0.157, 0.192), t)
|
|
// The inner shadow: black right under the top edge, gone in about
|
|
// seven rows; along each wall a shade gone in four; a darker last row.
|
|
let top = exp(-max(p.y - lo.y, 0.0) / (3.0 * px))
|
|
let wl = 0.75 * exp(-max(p.x - lo.x, 0.0) / (2.0 * px))
|
|
let wr = 0.75 * exp(-max(hi.x - px - p.x, 0.0) / (2.0 * px))
|
|
let foot = 0.35 * step(hi.y - px, p.y)
|
|
let dk = 1.0 - (1.0 - top) * (1.0 - wl) * (1.0 - wr) * (1.0 - foot)
|
|
col = mix(col, vec3(0.0, 0.0, 0.0), dk)
|
|
let inside = step(lo.x, p.x) * (1.0 - step(hi.x, p.x)) * step(lo.y, p.y) * (1.0 - step(hi.y, p.y))
|
|
// Pressed in, the chosen cap throws no shadow: under it the ground (with
|
|
// the window's grain) comes back a row under the outline, and only the
|
|
// neighbours' shadows reach in at the sides.
|
|
let below = step(hi.y + px, p.y) * step(lo.x, p.x) * (1.0 - step(hi.x, p.x))
|
|
let bx = smoothstep(0.0, 4.0 * px, p.x - lo.x) * smoothstep(0.0, 4.0 * px, hi.x - p.x)
|
|
let contact = 0.8 * exp(-max(p.y - hi.y - px, 0.0) / (0.9 * px))
|
|
// (The grain as `gl_grain` further down reads it, in window space.)
|
|
let gi = floor((p + o) / px)
|
|
let gj = gi + vec2(Finish.hash(gi) * 419.0, Finish.hash(gi + vec2(5.31, 9.17)) * 263.0)
|
|
let g = (Finish.hash(gj) - Finish.hash(gj + vec2(17.13, 3.71))) * 0.02
|
|
var res = vec4(mix(vec3(0.224, 0.231, 0.267) + vec3(g, g, g), vec3(0.0, 0.0, 0.0), contact), 1.0) * below * bx * hz
|
|
res = Finish.over(res, vec4(col, 1.0) * inside)
|
|
// The neighbours' ends standing over the well: a lit column on each,
|
|
// and a fainter one behind it on the left.
|
|
let ey = step(lo.y + px, p.y) * (1.0 - step(hi.y - px, p.y))
|
|
let el = step(lo.x - px, p.x) * (1.0 - step(lo.x, p.x))
|
|
let el2 = step(lo.x - px * 2.0, p.x) * (1.0 - step(lo.x - px, p.x))
|
|
let er = step(hi.x, p.x) * (1.0 - step(hi.x + px, p.x))
|
|
res = Finish.over(res, vec4(0.294, 0.310, 0.345, 1.0) * (el + er) * ey * hz)
|
|
res = Finish.over(res, vec4(0.259, 0.267, 0.314, 1.0) * el2 * ey * 0.6 * hz)
|
|
// On the column the answer below stands over the well's foot: its top
|
|
// line, between the side outlines.
|
|
let eb = step(hi.y, p.y) * (1.0 - step(hi.y + px, p.y)) * step(lo.x + px, p.x) * (1.0 - step(hi.x - px, p.x))
|
|
res = Finish.over(res, vec4(0.294, 0.310, 0.345, 1.0) * eb * v)
|
|
// The lamp, centred under the word: the widget centres the word by its
|
|
// advance, which sets the ink about a point left of the middle, so the
|
|
// lamp stands a point left of it too. The core's top row 8.4 points
|
|
// under the segment's middle: five rows of lit floor under it before the
|
|
// well's foot, three rows of well between a descender and the lamp's
|
|
// pale top row. Round it a broad soft pool on the floor.
|
|
let c = vec2((lo.x + hi.x) * 0.5 - 1.0, self.rect_size.y * 0.5)
|
|
let y0 = gl_snap(c.y + 8.4167, o.y, px)
|
|
let q = vec2((p.x - c.x) / max((hi.x - lo.x) * 0.3, 1.0), (p.y - (y0 + px)) / 3.2)
|
|
let pool = exp(-dot(q, q)) * 0.16 * inside
|
|
res = Finish.over(res, vec4(0.405, 0.593, 0.752, 1.0) * pool)
|
|
let xs = vec2(gl_snap(c.x - 5.3333, o.x, px), gl_snap(c.x + 5.3333, o.x, px))
|
|
let lamp = gl_lamp(res, p, xs, y0, px, 1.0, 1.0, vec3(0.910, 0.918, 0.922), vec3(0.405, 0.593, 0.752))
|
|
res = mix(res, lamp, inside)
|
|
// The light reaches the foot of the well and spills over its bottom
|
|
// edge: the last row of the floor, the outline and the row under it.
|
|
let ex = max(max(xs.x - p.x, p.x - xs.y), 0.0) / px
|
|
let gx = exp(-(ex * ex) / (2.0 * 7.0 * 7.0)) * step(lo.x, p.x) * (1.0 - step(hi.x, p.x))
|
|
let r0 = step(hi.y - px, p.y) * (1.0 - step(hi.y, p.y))
|
|
let r1 = step(hi.y, p.y) * (1.0 - step(hi.y + px, p.y))
|
|
let r2 = step(hi.y + px, p.y) * (1.0 - step(hi.y + px * 2.0, p.y))
|
|
let spill = (0.34 * r0 + (0.22 * r1 + 0.10 * r2) * hz) * gx
|
|
return Finish.over(res, vec4(0.56, 0.66, 0.76, 1.0) * spill)
|
|
}
|
|
// Cap labels at the family's 8 points, the chosen one included.
|
|
mod.widgets.SegmentedControlFlat.draw_text.text_style.font_size = 8.0
|
|
mod.widgets.SegmentedControlFlat.draw_text_selected.text_style.font_size = 8.0
|
|
mod.widgets.SegmentedControlFlat.draw_text.color = #c4c7ce
|
|
mod.widgets.SegmentedControlFlat.draw_text_selected.color = #fafafa
|
|
mod.widgets.SegmentedControlVertical.draw_pill.radius = -1.0
|
|
|
|
// Tabs: joined segments of one strip. Each quiet tab is a raised segment
|
|
// whose joint with the next is engraved, dark then light; the tab in use
|
|
// sinks like the chosen segment, its word lit and the lamp under it.
|
|
mod.widgets.Tabs.height = 26.0
|
|
mod.widgets.Tabs.clip_x = false
|
|
mod.widgets.Tabs.clip_y = false
|
|
let gl_tab = fn(pos: vec2, size: vec2, o: vec2, dpi: float, hover: float, active: float) -> vec4 {
|
|
let m = 7.0
|
|
let px = gl_px(dpi)
|
|
let p = pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
let lo = vec2(gl_snap(0.0, o.x, px), gl_snap(0.0, o.y, px))
|
|
let hi = vec2(gl_snap(size.x, o.x, px), gl_snap(size.y, o.y, px))
|
|
let inx = step(lo.x, p.x) * step(p.x, hi.x)
|
|
var res = gl_cap(p, lo - vec2(4.0, 0.0), hi + vec2(4.0, 0.0), 0.0, px, hover, active, 0.0, 0.0, 1.0, vec3(0.0, 0.0, 0.0), vec4(0.196, 0.204, 0.227, 1.0), 0.0) * inx
|
|
res = mix(res, Finish.over(res, gl_sunk(p, lo + vec2(0.0, px), hi - vec2(0.0, px), px, hover)), active)
|
|
// The joint: the black outline on the left, just outside the box, and a
|
|
// light line inside it; the dark line inside the right edge.
|
|
let left_out = step(lo.x - px, p.x) * (1.0 - step(lo.x, p.x))
|
|
let left_in = step(lo.x, p.x) * (1.0 - step(lo.x + px, p.x))
|
|
let right_in = step(hi.x - px, p.x) * (1.0 - step(hi.x, p.x))
|
|
let iny = step(lo.y, p.y) * step(p.y, hi.y)
|
|
res = Finish.over(res, vec4(0.039, 0.039, 0.047, 1.0) * (left_out + right_in) * iny)
|
|
res = Finish.over(res, vec4(0.29, 0.30, 0.33, 1.0) * left_in * iny * step(lo.y + px, p.y) * step(p.y, hi.y - px) * (1.0 - active))
|
|
// Under the sunk tab only, a 1 dp lip #474c56 on the ground.
|
|
let lip = step(hi.y, p.y) * (1.0 - step(hi.y + px, p.y)) * inx
|
|
res = Finish.over(res, vec4(0.278, 0.298, 0.337, 1.0) * lip * active)
|
|
// The tab in use carries the chosen segment's lamp under its word. The
|
|
// strip sets its words flush left 10 points in and the face cannot know
|
|
// how long a word is, so the lamp is centred on the first 27 points of
|
|
// it -- under a one-word name such as Mixer, Effects or Notes it stands
|
|
// in the middle; under a longer word it stays under its first letters.
|
|
// The core's top row 7.75 points under the tab's middle, as on a
|
|
// segment.
|
|
let lx = lo.x + 23.5
|
|
let xs = vec2(gl_snap(lx - 5.3333, o.x, px), gl_snap(lx + 5.3333, o.x, px))
|
|
return gl_lamp(res, p, xs, gl_snap((lo.y + hi.y) * 0.5 + 7.75, o.y, px), px, active, 1.0, vec3(0.910, 0.918, 0.922), vec3(0.405, 0.593, 0.752))
|
|
}
|
|
mod.widgets.Tabs.draw_tab.vertex = fn() {
|
|
let m = 7.0
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
|
|
}
|
|
mod.widgets.Tabs.draw_tab_hover.vertex = fn() {
|
|
let m = 7.0
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
|
|
}
|
|
mod.widgets.Tabs.draw_tab_active.vertex = fn() {
|
|
let m = 7.0
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
|
|
}
|
|
mod.widgets.Tabs.draw_bg.pixel = fn() {
|
|
return vec4(0.0, 0.0, 0.0, 0.0)
|
|
}
|
|
mod.widgets.Tabs.draw_tab.pixel = fn() {
|
|
return gl_tab(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, 0.0, 0.0)
|
|
}
|
|
mod.widgets.Tabs.draw_tab_hover.pixel = fn() {
|
|
return gl_tab(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, 1.0, 0.0)
|
|
}
|
|
mod.widgets.Tabs.draw_tab_active.pixel = fn() {
|
|
return gl_tab(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, 0.0, 1.0)
|
|
}
|
|
mod.widgets.Tabs.draw_sep.pixel = fn() {
|
|
return vec4(0.0, 0.0, 0.0, 0.0)
|
|
}
|
|
mod.widgets.Tabs.draw_mark.color = #9a9ca4
|
|
mod.widgets.Tabs.draw_text.color = #9a9ca4
|
|
// The tab in use answers like the chosen segment: its ink #d4d7de in
|
|
// semibold, in the sunk blue well; quiet tabs grey regular.
|
|
mod.widgets.Tabs.draw_text_active.color = #d4d7de
|
|
mod.widgets.Tabs.draw_text.text_style.font_size = 8.0
|
|
mod.widgets.Tabs.draw_text_active.text_style = mod.theme.font_bold{font_size: 8.0}
|
|
|
|
// ---------------------------------------------------------------- feedback
|
|
|
|
// The progress bar: an 8 point well; the value is the lit bar of the
|
|
// sliders, a pale core row over a body row in the middle of the floor, its
|
|
// halo on the floor round it.
|
|
mod.widgets.ProgressBarFlat.height = 8.0
|
|
mod.widgets.ProgressBarFlat.draw_bg.pixel = fn() {
|
|
let px = gl_px(self.draw_pass.dpi_factor)
|
|
let o = self.rect_pos
|
|
let p = self.pos * self.rect_size
|
|
let th = min(self.thickness, self.rect_size.y)
|
|
let y0 = gl_snap((self.rect_size.y - th) * 0.5, o.y, px)
|
|
let x0 = gl_snap(self.rect_size.x * self.start, o.x, px)
|
|
let x1 = gl_snap(self.rect_size.x * self.end, o.x, px)
|
|
let lo = vec2(x0, y0)
|
|
let hi = vec2(x1, y0 + gl_snap(th, 0.0, px) - px)
|
|
var res = vec4(0.0, 0.0, 0.0, 0.0)
|
|
if self.track > 0.5 {
|
|
res = gl_well(p, lo, hi, 2.0, px, vec3(0.141, 0.149, 0.176), 0.0, 0.0, 1.0)
|
|
}
|
|
let fill = self.fill_color()
|
|
let ins = 2.0
|
|
let fl = vec2(lo.x + ins, lo.y + ins)
|
|
let span = hi.x - lo.x - ins * 2.0
|
|
var f0 = fl.x
|
|
var f1 = fl.x + span * clamp((self.value - self.start) / max(self.end - self.start, 0.001), 0.0, 1.0)
|
|
if self.value < 0.0 {
|
|
// Still: the sweep would need the pass clock, and a window at rest
|
|
// reads no clock, so the unknown is a segment standing in the
|
|
// middle with its ends fading.
|
|
f0 = fl.x + span * 0.3
|
|
f1 = fl.x + span * 0.7
|
|
}
|
|
// The fill is lit like the lamp: a pale core row over a row of the
|
|
// role colour in the middle of the well's floor, the halo on the floor
|
|
// round it and past its ends.
|
|
var fade = fill.a * step(0.001, f1 - f0)
|
|
if self.value < 0.0 {
|
|
fade = fade * smoothstep(f0, f0 + span * 0.12, p.x) * smoothstep(f1, f1 - span * 0.12, p.x)
|
|
}
|
|
let lb = gl_lit_bar(vec4(0.0, 0.0, 0.0, 0.0), p, vec2(f0, lo.y + px), vec2(f1, hi.y - px), vec2(lo.x + px, hi.x - px), px, fill.rgb, 0.0)
|
|
res = Finish.over(res, lb * fade)
|
|
return res * self.opacity
|
|
}
|
|
mod.widgets.ProgressBarFlat.draw_bg.color_primary = #3baeff
|
|
|
|
// The level meter: cells of 3 dp and a 1 dp black gap on black glass in a
|
|
// slim well; green, then yellow toward the top, red at the end; unlit cells
|
|
// stand at a fifth of the lit. The clip lamp is an LED. 8 points thick.
|
|
mod.widgets.LevelMeter.height = 8.0
|
|
mod.widgets.LevelMeterColumn.width = 8.0
|
|
mod.widgets.LevelMeter.draw_bg.pixel = fn() {
|
|
let px = gl_px(self.draw_pass.dpi_factor)
|
|
let o = self.rect_pos
|
|
let sz = self.rect_size
|
|
let p = self.pos * sz
|
|
let v = self.vertical
|
|
let along = mix(sz.x, sz.y, v)
|
|
let across = mix(sz.y, sz.x, v)
|
|
let a = mix(p.x, sz.y - p.y, v)
|
|
let b = mix(p.y, p.x, v)
|
|
let lamp_room = self.lamp * (across + 3.0)
|
|
let bar = max(4.0, along - lamp_room)
|
|
// The window: black glass in a 1 dp outline with its lip.
|
|
let wlo = mix(vec2(0.0, 0.0), vec2(0.0, sz.y - bar), v)
|
|
let whi = mix(vec2(bar, sz.y), vec2(sz.x, sz.y), v)
|
|
let lo = vec2(gl_snap(wlo.x, o.x, px), gl_snap(wlo.y, o.y, px))
|
|
let hi = vec2(gl_snap(whi.x, o.x, px), gl_snap(whi.y, o.y, px) - px)
|
|
var res = gl_well(p, lo, hi, 1.0, px, vec3(0.03, 0.035, 0.045), 0.0, 0.0, 1.0)
|
|
let pad = 2.0 * px
|
|
let span = max(1.0, bar - pad * 2.0)
|
|
let pitch = px * 4.0
|
|
let cell = step(fract((a - pad) / pitch), 0.75)
|
|
let share = clamp((a - pad) / span, 0.0, 1.0)
|
|
let inb = step(pad, a) * step(a, bar - pad) * step(pad, b) * step(b, across - pad) * step(p.y, hi.y - px)
|
|
var lit_col = vec3(0.498, 0.788, 0.227)
|
|
lit_col = mix(lit_col, vec3(0.902, 0.788, 0.251), step(self.hot_at, share))
|
|
lit_col = mix(lit_col, vec3(0.941, 0.318, 0.227), step(self.hot_to, share))
|
|
// The first device pixel of each cell a touch lighter and paler.
|
|
let edge = step(fract((a - pad) / pitch), 0.25)
|
|
lit_col = mix(lit_col, mix(lit_col, vec3(0.85, 0.87, 0.8), 0.35), edge)
|
|
let on = step(share, self.level) * step(0.001, self.level)
|
|
let unlit = vec3(0.145, 0.153, 0.176)
|
|
let col = mix(unlit, lit_col, on)
|
|
res = Finish.over(res, vec4(col, 1.0) * cell * inb * self.opacity)
|
|
// The hold: one cell at full strength.
|
|
let hold_at = pad + span * self.hold
|
|
let hc = step(abs(a - hold_at), pitch * 0.5) * step(0.002, self.hold)
|
|
res = Finish.over(res, vec4(mix(vec3(0.498, 0.788, 0.227), vec3(0.941, 0.318, 0.227), step(self.hot_to, self.hold)), 1.0) * hc * cell * inb)
|
|
// The lamp.
|
|
if self.lamp > 0.5 {
|
|
let lc = mix(vec2(bar + 3.0 + across * 0.5, sz.y * 0.5), vec2(sz.x * 0.5, across * 0.5), v)
|
|
// A small red lamp, 3.5 points: a white-hot core, a red rim and a
|
|
// soft red halo when lit; a dark lens when out.
|
|
let rl = clamp(across * 0.5 - 1.5, 1.0, 1.75)
|
|
res = gl_led(res, p, lc, vec2(rl, rl), step(0.5, self.over), 0.45, px, vec3(1.0, 0.957, 0.941), vec3(0.941, 0.318, 0.227))
|
|
}
|
|
return res * self.opacity
|
|
}
|
|
|
|
// ---------------------------------------------------------------- surfaces
|
|
|
|
// The scroll bar: an 8 point well, the thumb a 6 point cap inset 1 point,
|
|
// no gloss, no glow.
|
|
mod.widgets.ScrollBar.bar_size = 8.0
|
|
mod.widgets.ScrollBar.bar_side_margin = 1.0
|
|
mod.widgets.ScrollBar.draw_bg.pixel = fn() {
|
|
let px = gl_px(self.draw_pass.dpi_factor)
|
|
let o = self.rect_pos
|
|
let sz = self.rect_size
|
|
let p = self.pos * sz
|
|
let v = self.is_vertical
|
|
let lo = vec2(gl_snap(0.0, o.x, px), gl_snap(0.0, o.y, px))
|
|
let hi = vec2(gl_snap(sz.x, o.x, px), gl_snap(sz.y, o.y, px))
|
|
var res = gl_well(p, lo, hi, 2.0, px, vec3(0.141, 0.149, 0.176), 0.0, 0.0, 0.0)
|
|
let t0 = mix(sz.x * self.norm_scroll, sz.y * self.norm_scroll, v)
|
|
let t1 = t0 + mix(sz.x * self.norm_handle, sz.y * self.norm_handle, v)
|
|
let tlo = mix(vec2(t0, 1.0), vec2(1.0, t0), v)
|
|
let thi = mix(vec2(t1, sz.y - 1.0), vec2(sz.x - 1.0, t1), v)
|
|
let slo = vec2(gl_snap(tlo.x, o.x, px), gl_snap(tlo.y, o.y, px))
|
|
let shi = vec2(gl_snap(thi.x, o.x, px), gl_snap(thi.y, o.y, px))
|
|
res = Finish.over(res, gl_cap(p, slo, shi, 1.5, px, max(self.hover, self.drag), 0.0, 0.0, 0.0, 0.0, vec3(0.0, 0.0, 0.0), vec4(0.196, 0.204, 0.227, 1.0), 0.0))
|
|
return res * self.opacity
|
|
}
|
|
|
|
// The housing's grain: monochrome, one value per device pixel, sd 0.008.
|
|
// The library's hash nearly repeats every 97 device pixels (97 x 0.1031 is
|
|
// almost whole), which showed as a lattice over a wide ground; each pixel
|
|
// is first sent to a far cell picked by the hash of its own, and the grain
|
|
// is read there, so no shift lines the pattern up again.
|
|
let gl_grain = fn(p: vec2, o: vec2, px: float) -> float {
|
|
let i = floor((p + o) / px)
|
|
let j = i + vec2(Finish.hash(i) * 419.0, Finish.hash(i + vec2(5.31, 9.17)) * 263.0)
|
|
return (Finish.hash(j) - Finish.hash(j + vec2(17.13, 3.71))) * 0.02
|
|
}
|
|
|
|
// A raised panel: a soft drop that stays within the gap to its neighbours,
|
|
// the black outline, a face that falls 0.02 top to bottom with the ground's
|
|
// grain, and one top line.
|
|
let gl_panel = fn(p: vec2, size: vec2, o: vec2, px: float, r: float, hover: float) -> vec4 {
|
|
let lo = vec2(gl_snap(0.0, o.x, px), gl_snap(0.0, o.y, px))
|
|
let hi = vec2(gl_snap(size.x, o.x, px), gl_snap(size.y, o.y, px))
|
|
let c = (lo + hi) * 0.5
|
|
let h = (hi - lo) * 0.5
|
|
let d = Material.sd_box(p, c, h, r)
|
|
// Two falls: a firm one close under the edge and a soft one that
|
|
// carries the panel off the ground for about 10 device pixels below
|
|
// it; to the sides both are gone within 5 points, half the gap.
|
|
let sh1 = Finish.drop(p, c, h - vec2(1.0, 0.5), r, vec2(0.0, 1.5), 1.4) * 0.5
|
|
let sh2 = Finish.drop(p, c, h - vec2(3.5, 2.0), r, vec2(0.0, 3.5), 3.2) * 0.4
|
|
let sh = 1.0 - (1.0 - sh1) * (1.0 - sh2)
|
|
let contact = Finish.drop(p, c, h, r, vec2(0.0, px), px * 0.6) * 0.7
|
|
var res = vec4(0.0, 0.0, 0.0, 1.0 - (1.0 - sh) * (1.0 - contact))
|
|
let t = clamp((p.y - lo.y) / max(hi.y - lo.y, 1.0), 0.0, 1.0)
|
|
// The face starts at the ground's own value and falls 0.035, so its top
|
|
// edge crosses over the ground the way a cap's does.
|
|
var col = mix(vec3(0.220, 0.227, 0.263), vec3(0.188, 0.196, 0.227), t) + vec3(0.02, 0.02, 0.02) * hover
|
|
col = col + vec3(1.0, 1.0, 1.0) * gl_grain(p, o, px)
|
|
col = mix(col, vec3(0.345, 0.353, 0.384), Finish.band(d, px, px * 2.0, px) * step(p.y, lo.y + r + px * 2.0) * step(lo.x + r * 0.5, p.x) * step(p.x, hi.x - r * 0.5))
|
|
col = mix(col, vec3(0.055, 0.059, 0.071), Finish.band(d, 0.0, px, px))
|
|
let a = Finish.cover(d, px)
|
|
res = Finish.over(res, vec4(col * a, a))
|
|
return res
|
|
}
|
|
// A tray: the same box cut into the ground instead, with its lip below.
|
|
let gl_tray = fn(p: vec2, size: vec2, o: vec2, px: float, r: float) -> vec4 {
|
|
let lo = vec2(gl_snap(0.0, o.x, px), gl_snap(0.0, o.y, px))
|
|
let hi = vec2(gl_snap(size.x, o.x, px), gl_snap(size.y, o.y, px) - px)
|
|
let c = (lo + hi) * 0.5
|
|
let d = Material.sd_box(p, c, (hi - lo) * 0.5, r)
|
|
var res = gl_well(p, lo, hi, r, px, vec3(0.165, 0.173, 0.204), 0.0, 0.0, 1.0)
|
|
// The ground grain at half strength.
|
|
let a = Finish.cover(d + px, px)
|
|
let g = gl_grain(p, o, px) * 0.5 * a
|
|
// The rim of the ground shades the top of the tray: black at 0.4 under
|
|
// the top outline, gone 4 points down.
|
|
let shade = 0.4 * (1.0 - smoothstep(0.0, 4.0, p.y - lo.y - px)) * a
|
|
return vec4(res.rgb * (1.0 - shade) + vec3(g, g, g) * res.a, res.a)
|
|
}
|
|
|
|
mod.widgets.PanelView.clip_x = false
|
|
mod.widgets.PanelView.clip_y = false
|
|
mod.widgets.PanelView.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 12.0 bottom: 12.0}
|
|
mod.widgets.PanelView.draw_bg.vertex = fn() {
|
|
let m = 12.0
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
|
|
}
|
|
mod.widgets.PanelView.draw_bg.pixel = fn() {
|
|
let m = 12.0
|
|
let px = gl_px(self.draw_pass.dpi_factor)
|
|
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
if self.sunken > 0.5 {
|
|
return gl_tray(p, self.rect_size, self.rect_pos, px, 4.0)
|
|
}
|
|
return gl_panel(p, self.rect_size, self.rect_pos, px, 4.0, 0.0)
|
|
}
|
|
|
|
// The rounded view keeps the colour its caller gives it, square to the
|
|
// family: a true 2 point corner and, when it is opaque, a 1 dp outline.
|
|
mod.widgets.RoundedView.draw_bg.pixel = fn() {
|
|
let px = gl_px(self.draw_pass.dpi_factor)
|
|
let p = self.pos * self.rect_size
|
|
let lo = vec2(self.border_inset.x, self.border_inset.y)
|
|
let hi = self.rect_size - vec2(self.border_inset.z, self.border_inset.w)
|
|
let c = (lo + hi) * 0.5
|
|
let h = max((hi - lo) * 0.5, vec2(0.5, 0.5))
|
|
let r = min(2.0 * max(1.0, self.border_radius), min(h.x, h.y))
|
|
let d = Material.sd_box(p, c, h, r)
|
|
var fill = self.color
|
|
if self.color_2.x > -0.5 {
|
|
fill = mix(self.color, self.color_2, mix(self.pos.y, self.pos.x, self.gradient_fill_horizontal))
|
|
}
|
|
var ink = self.border_color
|
|
if self.border_size < 0.01 {
|
|
ink = vec4(0.039, 0.039, 0.047, fill.a)
|
|
}
|
|
let a = Finish.cover(d, px)
|
|
var col = mix(fill.rgb, ink.rgb, Finish.band(d, 0.0, max(self.border_size, px), px) * ink.a)
|
|
return vec4(col * a * fill.a, a * fill.a) + vec4(ink.rgb * ink.a, ink.a) * Finish.band(d, 0.0, max(self.border_size, px), px) * a * (1.0 - fill.a)
|
|
}
|
|
|
|
// The card: a raised panel of its own; a pressable card lifts under the
|
|
// hand.
|
|
mod.widgets.Card.clip_x = false
|
|
mod.widgets.Card.clip_y = false
|
|
mod.widgets.Card.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 12.0 bottom: 12.0}
|
|
mod.widgets.Card.draw_bg.vertex = fn() {
|
|
let m = 12.0
|
|
return self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
|
|
}
|
|
mod.widgets.Card.draw_bg.pixel = fn() {
|
|
let m = 12.0
|
|
let px = gl_px(self.draw_pass.dpi_factor)
|
|
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
return gl_panel(p, self.rect_size, self.rect_pos, px, 4.0, self.hover)
|
|
}
|
|
|
|
// List rows: 20 points, the text 10 points in; under the hand the row
|
|
// lifts 0.03. The chosen row is a shallow well -- the chosen segment's blue
|
|
// floor, a dark line and a short shade under its top edge, a light lip on
|
|
// its foot -- with a short lamp standing at its left.
|
|
mod.widgets.ListItem.height = 20.0
|
|
mod.widgets.ListItemOne.height = 20.0
|
|
mod.widgets.ListItem.padding = mod.turtle.Inset{left: 10.0 right: 8.0 top: 0.0 bottom: 0.0}
|
|
mod.widgets.ListItemOne.padding = mod.turtle.Inset{left: 10.0 right: 8.0 top: 0.0 bottom: 0.0}
|
|
mod.widgets.ListItem.draw_bg.pixel = fn() {
|
|
let px = gl_px(self.draw_pass.dpi_factor)
|
|
let o = self.rect_pos
|
|
let p = self.pos * self.rect_size
|
|
let live = 1.0 - self.disabled
|
|
var res = vec4(1.0, 1.0, 1.0, 1.0) * (0.03 * self.hover + 0.02 * self.down) * live
|
|
let y0 = gl_snap(0.0, o.y, px)
|
|
let y1 = gl_snap(self.rect_size.y, o.y, px)
|
|
let top = p.y - y0
|
|
let t = clamp(top / max(y1 - y0, 1.0), 0.0, 1.0)
|
|
var col = mix(vec3(0.161, 0.192, 0.235), vec3(0.137, 0.161, 0.200), t)
|
|
col = mix(col, vec3(0.0, 0.0, 0.0), 0.35 * (1.0 - smoothstep(0.0, 4.0, top - px)))
|
|
col = mix(col, vec3(0.039, 0.039, 0.047), 1.0 - step(px, top))
|
|
col = mix(col, vec3(0.278, 0.298, 0.337), step(y1 - px, p.y))
|
|
var sel = vec4(col, 1.0)
|
|
let lc = vec2(gl_snap(3.5, o.x, px) - px * 0.5, (y0 + y1) * 0.5)
|
|
sel = gl_led(sel, p, lc, vec2(px * 0.5 + 0.01, max((y1 - y0) * 0.22, 2.0)), live, 0.8, px, vec3(0.88, 0.96, 1.0), vec3(0.31, 0.506, 0.702))
|
|
res = mix(res, Finish.over(sel, res), self.selected)
|
|
return res
|
|
}
|
|
mod.widgets.ListItem.draw_text.color = #d4d7de
|
|
mod.widgets.ListItem.draw_text_2.color = #7c7e86
|
|
|
|
// A chip is a small seated cap; its role colour only tints the face. Its
|
|
// label is a cap label: 8 points.
|
|
mod.widgets.ChipFlat.draw_text.text_style.font_size = 8.0
|
|
mod.widgets.ChipFlat.draw_bg.pixel = fn() {
|
|
let px = gl_px(self.draw_pass.dpi_factor)
|
|
let o = self.rect_pos
|
|
let p = self.pos * self.rect_size
|
|
let lo = vec2(gl_snap(0.0, o.x, px), gl_snap(0.0, o.y, px))
|
|
let hi = vec2(gl_snap(self.rect_size.x, o.x, px), gl_snap(self.rect_size.y, o.y, px) - px)
|
|
let l = Finish.lum(self.color.rgb)
|
|
let tint = (self.color.rgb - vec3(l, l, l)) * 0.35 * self.color.a
|
|
var res = gl_cap(p, lo, hi, 2.0, px, self.hover, self.down, 0.0, 0.0, 0.0, tint, vec4(0.196, 0.204, 0.227, 1.0), 0.0)
|
|
if self.keyed > 0.0 {
|
|
let d = Material.sd_box(p, (lo + hi) * 0.5, (hi - lo) * 0.5, 2.0)
|
|
res = Finish.over(res, vec4(0.31, 0.506, 0.702, 1.0) * Finish.band(d, 0.0, px, px) * self.keyed)
|
|
}
|
|
return res
|
|
}
|
|
|
|
// ---------------------------------------------------------------- ink
|
|
|
|
// Words sit embossed-dark: a 1 dp shadow of black at half strength directly
|
|
// under every glyph of a label. Read from the glyph one device pixel above,
|
|
// so it costs one more sample and nothing else.
|
|
// On a cap the label also sinks with it: held, the glyphs are read one
|
|
// device pixel higher in the atlas, so they stand one pixel lower in the
|
|
// well. Small labels come from the grayscale atlas, large ones from the
|
|
// multi-channel one; both paths carry the shadow and the nudge.
|
|
mod.widgets.ButtonFlat.draw_text.pixel = fn() {
|
|
let nudge = dFdy(self.t.xy) * step(0.5, self.down)
|
|
let c = self.get_color()
|
|
let dxt = length(dFdx(self.t))
|
|
let dyt = length(dFdy(self.t))
|
|
if self.texture_index < 0.5 {
|
|
let tex_size = self.grayscale_texture.size()
|
|
let scale = (dxt + dyt) * tex_size.x * 0.5
|
|
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
|
|
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
|
|
let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
|
|
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
|
|
let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
|
|
}
|
|
if self.texture_index > 1.5 && self.texture_index < 2.5 {
|
|
let tex_size = self.msdf_texture.size()
|
|
let scale = (dxt + dyt) * tex_size.x * 0.5
|
|
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
|
|
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
|
|
let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
|
|
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
|
|
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
|
|
}
|
|
return self.sample_text_pixel()
|
|
}
|
|
// A template made from another before this sheet ran keeps its own copy
|
|
// of the label's draw, so each cap template takes the literal.
|
|
mod.widgets.Button.draw_text.pixel = fn() {
|
|
let nudge = dFdy(self.t.xy) * step(0.5, self.down)
|
|
let c = self.get_color()
|
|
let dxt = length(dFdx(self.t))
|
|
let dyt = length(dFdy(self.t))
|
|
if self.texture_index < 0.5 {
|
|
let tex_size = self.grayscale_texture.size()
|
|
let scale = (dxt + dyt) * tex_size.x * 0.5
|
|
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
|
|
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
|
|
let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
|
|
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
|
|
let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
|
|
}
|
|
if self.texture_index > 1.5 && self.texture_index < 2.5 {
|
|
let tex_size = self.msdf_texture.size()
|
|
let scale = (dxt + dyt) * tex_size.x * 0.5
|
|
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
|
|
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
|
|
let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
|
|
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
|
|
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
|
|
}
|
|
return self.sample_text_pixel()
|
|
}
|
|
mod.widgets.ButtonPrimary.draw_text.pixel = fn() {
|
|
let nudge = dFdy(self.t.xy) * step(0.5, self.down)
|
|
let c = self.get_color()
|
|
let dxt = length(dFdx(self.t))
|
|
let dyt = length(dFdy(self.t))
|
|
if self.texture_index < 0.5 {
|
|
let tex_size = self.grayscale_texture.size()
|
|
let scale = (dxt + dyt) * tex_size.x * 0.5
|
|
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
|
|
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
|
|
let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
|
|
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
|
|
let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
|
|
}
|
|
if self.texture_index > 1.5 && self.texture_index < 2.5 {
|
|
let tex_size = self.msdf_texture.size()
|
|
let scale = (dxt + dyt) * tex_size.x * 0.5
|
|
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
|
|
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
|
|
let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
|
|
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
|
|
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
|
|
}
|
|
return self.sample_text_pixel()
|
|
}
|
|
mod.widgets.ButtonSecondary.draw_text.pixel = fn() {
|
|
let nudge = dFdy(self.t.xy) * step(0.5, self.down)
|
|
let c = self.get_color()
|
|
let dxt = length(dFdx(self.t))
|
|
let dyt = length(dFdy(self.t))
|
|
if self.texture_index < 0.5 {
|
|
let tex_size = self.grayscale_texture.size()
|
|
let scale = (dxt + dyt) * tex_size.x * 0.5
|
|
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
|
|
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
|
|
let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
|
|
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
|
|
let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
|
|
}
|
|
if self.texture_index > 1.5 && self.texture_index < 2.5 {
|
|
let tex_size = self.msdf_texture.size()
|
|
let scale = (dxt + dyt) * tex_size.x * 0.5
|
|
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
|
|
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
|
|
let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
|
|
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
|
|
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
|
|
}
|
|
return self.sample_text_pixel()
|
|
}
|
|
mod.widgets.ButtonTertiary.draw_text.pixel = fn() {
|
|
let nudge = dFdy(self.t.xy) * step(0.5, self.down)
|
|
let c = self.get_color()
|
|
let dxt = length(dFdx(self.t))
|
|
let dyt = length(dFdy(self.t))
|
|
if self.texture_index < 0.5 {
|
|
let tex_size = self.grayscale_texture.size()
|
|
let scale = (dxt + dyt) * tex_size.x * 0.5
|
|
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
|
|
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
|
|
let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
|
|
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
|
|
let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
|
|
}
|
|
if self.texture_index > 1.5 && self.texture_index < 2.5 {
|
|
let tex_size = self.msdf_texture.size()
|
|
let scale = (dxt + dyt) * tex_size.x * 0.5
|
|
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
|
|
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
|
|
let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
|
|
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
|
|
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
|
|
}
|
|
return self.sample_text_pixel()
|
|
}
|
|
mod.widgets.ButtonDanger.draw_text.pixel = fn() {
|
|
let nudge = dFdy(self.t.xy) * step(0.5, self.down)
|
|
let c = self.get_color()
|
|
let dxt = length(dFdx(self.t))
|
|
let dyt = length(dFdy(self.t))
|
|
if self.texture_index < 0.5 {
|
|
let tex_size = self.grayscale_texture.size()
|
|
let scale = (dxt + dyt) * tex_size.x * 0.5
|
|
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
|
|
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
|
|
let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
|
|
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
|
|
let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
|
|
}
|
|
if self.texture_index > 1.5 && self.texture_index < 2.5 {
|
|
let tex_size = self.msdf_texture.size()
|
|
let scale = (dxt + dyt) * tex_size.x * 0.5
|
|
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
|
|
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
|
|
let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
|
|
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
|
|
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
|
|
}
|
|
return self.sample_text_pixel()
|
|
}
|
|
mod.widgets.ButtonOutline.draw_text.pixel = fn() {
|
|
let nudge = dFdy(self.t.xy) * step(0.5, self.down)
|
|
let c = self.get_color()
|
|
let dxt = length(dFdx(self.t))
|
|
let dyt = length(dFdy(self.t))
|
|
if self.texture_index < 0.5 {
|
|
let tex_size = self.grayscale_texture.size()
|
|
let scale = (dxt + dyt) * tex_size.x * 0.5
|
|
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
|
|
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
|
|
let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
|
|
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
|
|
let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
|
|
}
|
|
if self.texture_index > 1.5 && self.texture_index < 2.5 {
|
|
let tex_size = self.msdf_texture.size()
|
|
let scale = (dxt + dyt) * tex_size.x * 0.5
|
|
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
|
|
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
|
|
let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
|
|
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
|
|
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
|
|
}
|
|
return self.sample_text_pixel()
|
|
}
|
|
mod.widgets.ButtonFlatter.draw_text.pixel = fn() {
|
|
let nudge = dFdy(self.t.xy) * step(0.5, self.down)
|
|
let c = self.get_color()
|
|
let dxt = length(dFdx(self.t))
|
|
let dyt = length(dFdy(self.t))
|
|
if self.texture_index < 0.5 {
|
|
let tex_size = self.grayscale_texture.size()
|
|
let scale = (dxt + dyt) * tex_size.x * 0.5
|
|
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
|
|
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
|
|
let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
|
|
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
|
|
let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
|
|
}
|
|
if self.texture_index > 1.5 && self.texture_index < 2.5 {
|
|
let tex_size = self.msdf_texture.size()
|
|
let scale = (dxt + dyt) * tex_size.x * 0.5
|
|
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
|
|
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
|
|
let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
|
|
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
|
|
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
|
|
}
|
|
return self.sample_text_pixel()
|
|
}
|
|
mod.widgets.ButtonXs.draw_text.pixel = fn() {
|
|
let nudge = dFdy(self.t.xy) * step(0.5, self.down)
|
|
let c = self.get_color()
|
|
let dxt = length(dFdx(self.t))
|
|
let dyt = length(dFdy(self.t))
|
|
if self.texture_index < 0.5 {
|
|
let tex_size = self.grayscale_texture.size()
|
|
let scale = (dxt + dyt) * tex_size.x * 0.5
|
|
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
|
|
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
|
|
let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
|
|
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
|
|
let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
|
|
}
|
|
if self.texture_index > 1.5 && self.texture_index < 2.5 {
|
|
let tex_size = self.msdf_texture.size()
|
|
let scale = (dxt + dyt) * tex_size.x * 0.5
|
|
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
|
|
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
|
|
let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
|
|
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
|
|
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
|
|
}
|
|
return self.sample_text_pixel()
|
|
}
|
|
mod.widgets.ButtonSm.draw_text.pixel = fn() {
|
|
let nudge = dFdy(self.t.xy) * step(0.5, self.down)
|
|
let c = self.get_color()
|
|
let dxt = length(dFdx(self.t))
|
|
let dyt = length(dFdy(self.t))
|
|
if self.texture_index < 0.5 {
|
|
let tex_size = self.grayscale_texture.size()
|
|
let scale = (dxt + dyt) * tex_size.x * 0.5
|
|
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
|
|
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
|
|
let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
|
|
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
|
|
let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
|
|
}
|
|
if self.texture_index > 1.5 && self.texture_index < 2.5 {
|
|
let tex_size = self.msdf_texture.size()
|
|
let scale = (dxt + dyt) * tex_size.x * 0.5
|
|
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
|
|
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
|
|
let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
|
|
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
|
|
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
|
|
}
|
|
return self.sample_text_pixel()
|
|
}
|
|
mod.widgets.ButtonLg.draw_text.pixel = fn() {
|
|
let nudge = dFdy(self.t.xy) * step(0.5, self.down)
|
|
let c = self.get_color()
|
|
let dxt = length(dFdx(self.t))
|
|
let dyt = length(dFdy(self.t))
|
|
if self.texture_index < 0.5 {
|
|
let tex_size = self.grayscale_texture.size()
|
|
let scale = (dxt + dyt) * tex_size.x * 0.5
|
|
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
|
|
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
|
|
let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
|
|
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
|
|
let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
|
|
}
|
|
if self.texture_index > 1.5 && self.texture_index < 2.5 {
|
|
let tex_size = self.msdf_texture.size()
|
|
let scale = (dxt + dyt) * tex_size.x * 0.5
|
|
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
|
|
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
|
|
let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
|
|
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
|
|
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
|
|
}
|
|
return self.sample_text_pixel()
|
|
}
|
|
mod.widgets.ButtonXl.draw_text.pixel = fn() {
|
|
let nudge = dFdy(self.t.xy) * step(0.5, self.down)
|
|
let c = self.get_color()
|
|
let dxt = length(dFdx(self.t))
|
|
let dyt = length(dFdy(self.t))
|
|
if self.texture_index < 0.5 {
|
|
let tex_size = self.grayscale_texture.size()
|
|
let scale = (dxt + dyt) * tex_size.x * 0.5
|
|
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
|
|
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
|
|
let main = self.sdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
|
|
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
|
|
let s = self.sdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
|
|
}
|
|
if self.texture_index > 1.5 && self.texture_index < 2.5 {
|
|
let tex_size = self.msdf_texture.size()
|
|
let scale = (dxt + dyt) * tex_size.x * 0.5
|
|
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
|
|
let pm = clamp(self.t.xy - nudge, self.t_min + half_texel, self.t_max - half_texel)
|
|
let main = self.msdf(scale, pm, c) * vec4(c.rgb * c.a, c.a)
|
|
let ps = clamp(self.t.xy - nudge - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
|
|
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
|
|
}
|
|
return self.sample_text_pixel()
|
|
}
|
|
|
|
// The same shadow on every label that stands on the ground or a cap; one
|
|
// literal per template, as the handbook advises. A lit legend (the tab or
|
|
// segment in use) takes a 2 dp halo of the LED's glow instead.
|
|
mod.widgets.Label.draw_text.pixel = fn() {
|
|
let main = self.sample_text_pixel()
|
|
if self.texture_index > 1.5 && self.texture_index < 2.5 {
|
|
let dxt = length(dFdx(self.t))
|
|
let dyt = length(dFdy(self.t))
|
|
let tex_size = self.msdf_texture.size()
|
|
let scale = (dxt + dyt) * tex_size.x * 0.5
|
|
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
|
|
let ps = clamp(self.t.xy - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
|
|
let c = self.get_color()
|
|
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
|
|
}
|
|
return main
|
|
}
|
|
mod.widgets.CheckBoxFlat.draw_text.pixel = fn() {
|
|
let main = self.sample_text_pixel()
|
|
if self.texture_index > 1.5 && self.texture_index < 2.5 {
|
|
let dxt = length(dFdx(self.t))
|
|
let dyt = length(dFdy(self.t))
|
|
let tex_size = self.msdf_texture.size()
|
|
let scale = (dxt + dyt) * tex_size.x * 0.5
|
|
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
|
|
let ps = clamp(self.t.xy - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
|
|
let c = self.get_color()
|
|
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
|
|
}
|
|
return main
|
|
}
|
|
mod.widgets.RadioButtonFlat.draw_text.pixel = fn() {
|
|
let main = self.sample_text_pixel()
|
|
if self.texture_index > 1.5 && self.texture_index < 2.5 {
|
|
let dxt = length(dFdx(self.t))
|
|
let dyt = length(dFdy(self.t))
|
|
let tex_size = self.msdf_texture.size()
|
|
let scale = (dxt + dyt) * tex_size.x * 0.5
|
|
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
|
|
let ps = clamp(self.t.xy - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
|
|
let c = self.get_color()
|
|
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
|
|
}
|
|
return main
|
|
}
|
|
mod.widgets.DropDownFlat.draw_text.pixel = fn() {
|
|
let main = self.sample_text_pixel()
|
|
if self.texture_index > 1.5 && self.texture_index < 2.5 {
|
|
let dxt = length(dFdx(self.t))
|
|
let dyt = length(dFdy(self.t))
|
|
let tex_size = self.msdf_texture.size()
|
|
let scale = (dxt + dyt) * tex_size.x * 0.5
|
|
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
|
|
let ps = clamp(self.t.xy - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
|
|
let c = self.get_color()
|
|
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
|
|
}
|
|
return main
|
|
}
|
|
mod.widgets.SegmentedControlFlat.draw_text.pixel = fn() {
|
|
let main = self.sample_text_pixel()
|
|
if self.texture_index > 1.5 && self.texture_index < 2.5 {
|
|
let dxt = length(dFdx(self.t))
|
|
let dyt = length(dFdy(self.t))
|
|
let tex_size = self.msdf_texture.size()
|
|
let scale = (dxt + dyt) * tex_size.x * 0.5
|
|
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
|
|
let ps = clamp(self.t.xy - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
|
|
let c = self.get_color()
|
|
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
|
|
}
|
|
return main
|
|
}
|
|
mod.widgets.SegmentedControlFlat.draw_text_selected.pixel = fn() {
|
|
let main = self.sample_text_pixel()
|
|
if self.texture_index > 1.5 && self.texture_index < 2.5 {
|
|
let dxt = length(dFdx(self.t))
|
|
let dyt = length(dFdy(self.t))
|
|
let tex_size = self.msdf_texture.size()
|
|
let scale = (dxt + dyt) * tex_size.x * 0.5
|
|
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
|
|
let ex = dFdx(self.t.xy) * 1.5
|
|
let ey = dFdy(self.t.xy) * 1.5
|
|
let k = vec4(0.0, 0.0, 0.0, 1.0)
|
|
var g = self.msdf(scale, clamp(self.t.xy + ex, self.t_min + half_texel, self.t_max - half_texel), k)
|
|
g = g + self.msdf(scale, clamp(self.t.xy - ex, self.t_min + half_texel, self.t_max - half_texel), k)
|
|
g = g + self.msdf(scale, clamp(self.t.xy + ey, self.t_min + half_texel, self.t_max - half_texel), k)
|
|
g = g + self.msdf(scale, clamp(self.t.xy - ey, self.t_min + half_texel, self.t_max - half_texel), k)
|
|
let c = self.get_color()
|
|
let a = clamp(g * 0.25, 0.0, 1.0) * 0.4 * c.a
|
|
return main + vec4(0.22 * a, 0.38 * a, 0.56 * a, a) * (1.0 - main.a)
|
|
}
|
|
return main
|
|
}
|
|
mod.widgets.Tabs.draw_text.pixel = fn() {
|
|
let main = self.sample_text_pixel()
|
|
if self.texture_index > 1.5 && self.texture_index < 2.5 {
|
|
let dxt = length(dFdx(self.t))
|
|
let dyt = length(dFdy(self.t))
|
|
let tex_size = self.msdf_texture.size()
|
|
let scale = (dxt + dyt) * tex_size.x * 0.5
|
|
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
|
|
let ps = clamp(self.t.xy - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
|
|
let c = self.get_color()
|
|
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
|
|
}
|
|
return main
|
|
}
|
|
mod.widgets.Tabs.draw_text_active.pixel = fn() {
|
|
let main = self.sample_text_pixel()
|
|
if self.texture_index > 1.5 && self.texture_index < 2.5 {
|
|
let dxt = length(dFdx(self.t))
|
|
let dyt = length(dFdy(self.t))
|
|
let tex_size = self.msdf_texture.size()
|
|
let scale = (dxt + dyt) * tex_size.x * 0.5
|
|
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
|
|
let ex = dFdx(self.t.xy) * 1.5
|
|
let ey = dFdy(self.t.xy) * 1.5
|
|
let k = vec4(0.0, 0.0, 0.0, 1.0)
|
|
var g = self.msdf(scale, clamp(self.t.xy + ex, self.t_min + half_texel, self.t_max - half_texel), k)
|
|
g = g + self.msdf(scale, clamp(self.t.xy - ex, self.t_min + half_texel, self.t_max - half_texel), k)
|
|
g = g + self.msdf(scale, clamp(self.t.xy + ey, self.t_min + half_texel, self.t_max - half_texel), k)
|
|
g = g + self.msdf(scale, clamp(self.t.xy - ey, self.t_min + half_texel, self.t_max - half_texel), k)
|
|
let c = self.get_color()
|
|
let a = clamp(g * 0.25, 0.0, 1.0) * 0.4 * c.a
|
|
return main + vec4(0.22 * a, 0.38 * a, 0.56 * a, a) * (1.0 - main.a)
|
|
}
|
|
return main
|
|
}
|
|
mod.widgets.SliderMinimal.draw_text.pixel = fn() {
|
|
let main = self.sample_text_pixel()
|
|
if self.texture_index > 1.5 && self.texture_index < 2.5 {
|
|
let dxt = length(dFdx(self.t))
|
|
let dyt = length(dFdy(self.t))
|
|
let tex_size = self.msdf_texture.size()
|
|
let scale = (dxt + dyt) * tex_size.x * 0.5
|
|
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
|
|
let ps = clamp(self.t.xy - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
|
|
let c = self.get_color()
|
|
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0))
|
|
return main + vec4(0.0, 0.0, 0.0, s * 0.5 * c.a) * (1.0 - main.a)
|
|
}
|
|
return main
|
|
}
|
|
|
|
// ---------------------------------------------------------------- field row
|
|
|
|
// A ROW OF FIELDS IS ONE HEIGHT. Left to themselves the library's fields each
|
|
// come out at a size of their own -- in the widget catalogue's toolbar the
|
|
// search box, the number beside it and the drop down after it stood at three
|
|
// different heights -- and a sheet that reshapes the text box alone widens
|
|
// that gap instead of closing it. So the box metrics are stated ONCE here and
|
|
// handed to each field below, and a field added to the library later joins
|
|
// the row by being named here rather than by having these numbers copied
|
|
// into it.
|
|
// Fields and buttons share the one 24 point row.
|
|
let field_height = 24.0
|
|
let field_pad_v = 3.0
|
|
let field_radius = 1.0
|
|
// The outline is one device pixel; the faces below draw it themselves.
|
|
let field_border = 0.34
|
|
|
|
// The room a Fit field takes. A minimum height rather than a height,
|
|
// because a line of text is shorter than a row, and the padding with it:
|
|
// where a field is Fit, the padding IS its height, and two fields padded
|
|
// differently part company again the moment the type grows.
|
|
let field_room = fn(w) {
|
|
w.min_height = field_height
|
|
w.padding.top = field_pad_v
|
|
w.padding.bottom = field_pad_v
|
|
}
|
|
// The box a field draws. Given the draw object and not the widget, because
|
|
// not every field draws its own box, and because writing a corner onto a
|
|
// draw struct that has no corner would add a slot its shader never reads.
|
|
let field_face = fn(d) {
|
|
d.border_radius = field_radius
|
|
d.border_size = field_border
|
|
}
|
|
// A field that states a frame and lays its own parts out inside it: a number
|
|
// with a stepper, a date, a time. The height is all these take. A padding
|
|
// here would NOT hold the line off the box -- each of them measures its
|
|
// parts against the turtle's whole rect and draws them from the content
|
|
// origin, so the padding displaces the line instead of insetting it, and the
|
|
// number came out sitting on the bottom edge with its arrows split around it.
|
|
let field_frame = fn(w) {
|
|
w.min_height = field_height
|
|
}
|
|
|
|
field_room(mod.widgets.TextInput)
|
|
field_face(mod.widgets.TextInput.draw_bg)
|
|
field_room(mod.widgets.ComboBox)
|
|
field_face(mod.widgets.ComboBox.draw_bg)
|
|
field_room(mod.widgets.DropDown)
|
|
field_face(mod.widgets.DropDown.draw_bg)
|
|
field_room(mod.widgets.DropDown2)
|
|
field_face(mod.widgets.DropDown2.draw_bg)
|
|
field_room(mod.widgets.FieldWell)
|
|
field_face(mod.widgets.FieldWell.draw_bg)
|
|
field_room(mod.widgets.TagField)
|
|
field_face(mod.widgets.TagField.draw_bg)
|
|
field_frame(mod.widgets.NumberField)
|
|
// The number's box strokes its own hairline and declares no border size.
|
|
mod.widgets.NumberField.draw_bg.border_radius = field_radius
|
|
// The drag-number's corner is written into its own shader, so it takes the
|
|
// room and keeps the box it draws.
|
|
field_frame(mod.widgets.ValueInput)
|
|
field_frame(mod.widgets.DateField)
|
|
field_frame(mod.widgets.TimeField)
|
|
// A picker is a date field in an anchor: the field inside fills the frame.
|
|
field_frame(mod.widgets.DatePicker)
|
|
field_frame(mod.widgets.DateRangePicker)
|
|
// The chrome-less input a well holds must impose no height of its own. It is
|
|
// a TextInput, so it inherits the minimum above, and a minimum as tall as the
|
|
// whole field, applied inside a well already that tall, pushed the line out
|
|
// through the bottom of the box.
|
|
mod.widgets.WellInput.min_height = 0.0
|
|
|
|
// ---------------------------------------------------------------- rules
|
|
|
|
// A rule is engraved: one device pixel of the outline's black over one of
|
|
// light, across the middle of its room, no shadow.
|
|
mod.widgets.Hr.draw_bg.pixel = fn() {
|
|
let px = gl_px(self.draw_pass.dpi_factor)
|
|
let p = self.pos * self.rect_size
|
|
let y0 = gl_snap(self.rect_size.y * 0.5 - px, self.rect_pos.y, px)
|
|
let dark = step(y0, p.y) * step(p.y, y0 + px)
|
|
let light = step(y0 + px, p.y) * step(p.y, y0 + px * 2.0)
|
|
return vec4(0.039, 0.039, 0.047, 1.0) * dark + vec4(0.271, 0.278, 0.310, 1.0) * light
|
|
}
|
|
mod.widgets.Vr.draw_bg.pixel = fn() {
|
|
let px = gl_px(self.draw_pass.dpi_factor)
|
|
let p = self.pos * self.rect_size
|
|
let x0 = gl_snap(self.rect_size.x * 0.5 - px, self.rect_pos.x, px)
|
|
let dark = step(x0, p.x) * step(p.x, x0 + px)
|
|
let light = step(x0 + px, p.x) * step(p.x, x0 + px * 2.0)
|
|
return vec4(0.039, 0.039, 0.047, 1.0) * dark + vec4(0.271, 0.278, 0.310, 1.0) * light
|
|
}
|
|
|
|
// ---------------------------------------------------------------- ground
|
|
|
|
// Plain views stop clipping so a shadow can fall past their edge; scroll
|
|
// views keep clipping their content.
|
|
mod.widgets.View.clip_x = false
|
|
mod.widgets.View.clip_y = false
|
|
mod.widgets.Window.pass.clear_color = #393b44
|
|
// The window's own ground: #393b44 with the family's grain, monochrome, one
|
|
// value per device pixel, sd 0.008, peaks inside +/-0.02. The panels carry
|
|
// the same grain in the same window space, so it runs on under them.
|
|
mod.widgets.Window.show_bg = true
|
|
mod.widgets.Window.draw_bg.pixel = fn() {
|
|
let px = gl_px(self.draw_pass.dpi_factor)
|
|
let p = self.pos * self.rect_size
|
|
let g = gl_grain(p, self.rect_pos, px)
|
|
return vec4(vec3(0.224, 0.231, 0.267) + vec3(g, g, g), 1.0)
|
|
}
|
|
|
|
// ---------------------------------------------------------------- badge
|
|
|
|
// A count is a small seated dark cap, never a light pill: face #2c2f37,
|
|
// the black outline, the one top line, ink #d4d7de.
|
|
mod.widgets.BadgePalette.neutral = #2c2f37
|
|
mod.widgets.BadgePalette.on_neutral = #d4d7de
|
|
mod.widgets.BadgeFlat.shape = mod.widgets.BadgeShape.Rounded
|
|
mod.widgets.BadgeFlat.radius = 2.0
|
|
mod.widgets.BadgeFlat.draw_bg.pixel = fn() {
|
|
let px = gl_px(self.draw_pass.dpi_factor)
|
|
let o = self.rect_pos
|
|
let p = self.pos * self.rect_size
|
|
// The box's edges rounded DOWN to a device pixel, so the bottom and
|
|
// right outline rows stay inside the quad.
|
|
let lo = vec2(gl_snap(0.0, o.x, px), gl_snap(0.0, o.y, px))
|
|
let hi = vec2(gl_snap(self.rect_size.x - px * 0.49, o.x, px), gl_snap(self.rect_size.y - px * 0.49, o.y, px))
|
|
let c = (lo + hi) * 0.5
|
|
let h = (hi - lo) * 0.5
|
|
let r = min(2.0, min(h.x, h.y))
|
|
let d = Material.sd_box(p, c, h, r)
|
|
let t = clamp((p.y - lo.y) / max(hi.y - lo.y, 1.0), 0.0, 1.0)
|
|
var col = self.color.rgb + vec3(0.012, 0.012, 0.014) * (1.0 - t)
|
|
col = mix(col, vec3(0.416, 0.424, 0.455), Finish.band(d, px, px * 2.0, px) * step(p.y, lo.y + r + px * 2.0) * step(lo.x + r * 0.5, p.x) * step(p.x, hi.x - r * 0.5) * self.color.a)
|
|
col = mix(col, vec3(0.039, 0.039, 0.047), Finish.band(d, 0.0, px, px))
|
|
let fa = max(self.color.a, Finish.band(d, 0.0, px, px))
|
|
let a = Finish.cover(d, px) * fa
|
|
return vec4(col * a, a)
|
|
}
|
|
|
|
// ---------------------------------------------------------------- states
|
|
|
|
// Every held state is written as a plain value that cuts in at once (the
|
|
// library writes some as snap() values, which a state held as a default --
|
|
// the states page -- never applies). Live, the cut is the same as the snap.
|
|
mod.widgets.ButtonFlat.animator.hover.on.from = {all: Snap}
|
|
mod.widgets.ButtonFlat.animator.hover.on.apply = {draw_bg: {down: 0.0 hover: 1.0} draw_text: {down: 0.0 hover: 1.0}}
|
|
mod.widgets.ButtonFlat.animator.hover.down.from = {all: Snap}
|
|
mod.widgets.ButtonFlat.animator.hover.down.apply = {draw_bg: {down: 1.0 hover: 1.0} draw_text: {down: 1.0 hover: 1.0}}
|
|
mod.widgets.CheckBoxFlat.animator.hover.down.from = {all: Snap}
|
|
mod.widgets.CheckBoxFlat.animator.hover.down.apply = {draw_bg: {down: 1.0 hover: 1.0} draw_text: {down: 1.0 hover: 1.0}}
|
|
mod.widgets.RadioButtonFlat.animator.hover.down.from = {all: Snap}
|
|
mod.widgets.RadioButtonFlat.animator.hover.down.apply = {draw_bg: {down: 1.0 hover: 1.0} draw_text: {down: 1.0 hover: 1.0}}
|
|
mod.widgets.TextInputFlat.animator.hover.on.from = {all: Snap}
|
|
mod.widgets.TextInputFlat.animator.hover.on.apply = {draw_bg: {down: 0.0 hover: 1.0} draw_text: {down: 0.0 hover: 1.0}}
|
|
mod.widgets.TextInputFlat.animator.hover.down.from = {all: Snap}
|
|
mod.widgets.TextInputFlat.animator.hover.down.apply = {draw_bg: {down: 1.0 hover: 1.0} draw_text: {down: 1.0 hover: 1.0}}
|
|
mod.widgets.DropDownFlat.animator.hover.on.from = {all: Snap}
|
|
mod.widgets.DropDownFlat.animator.hover.on.apply = {draw_bg: {down: 0.0 hover: 1.0} draw_text: {down: 0.0 hover: 1.0}}
|
|
mod.widgets.DropDownFlat.animator.hover.down.from = {all: Snap}
|
|
mod.widgets.DropDownFlat.animator.hover.down.apply = {draw_bg: {down: 1.0 hover: 1.0} draw_text: {down: 1.0 hover: 1.0}}
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------- 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. 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 = 0.0
|
|
mod.widgets.RangeSliderFlat.handle_size = 14.0
|
|
mod.widgets.RangeSliderFlat.draw_bg.vertex = fn() {
|
|
let m = 6.0
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
|
|
}
|
|
mod.widgets.RangeSliderFlat.draw_bg.pixel = fn() {
|
|
let m = 6.0
|
|
let px = gl_px(self.draw_pass.dpi_factor)
|
|
let o = self.rect_pos
|
|
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
let rad = 7.0
|
|
let cy = gl_snap(self.label_px + (self.rect_size.y - self.label_px) * 0.5, o.y, px)
|
|
let lo = vec2(gl_snap(0.0, o.x, px), cy - 2.0)
|
|
let hi = vec2(gl_snap(self.rect_size.x, o.x, px), cy + 2.0)
|
|
var res = gl_well(p, lo, hi, 2.0, px, vec3(0.141, 0.149, 0.176), 0.0, self.disabled, 1.0)
|
|
let span = self.rect_size.x - rad * 2.0
|
|
let h0 = rad + self.start_pos * span
|
|
let h1 = rad + self.end_pos * span
|
|
res = gl_fill(res, p, vec2(h0, lo.y + px), vec2(h1, hi.y - px), vec2(lo.x + px, hi.x - px), px, self.disabled)
|
|
res = gl_handle(res, p, vec2(h0, cy), rad, px, self.hot_start, max(self.drag * self.hot_start, self.focus), self.disabled)
|
|
return gl_handle(res, p, vec2(h1, cy), rad, px, self.hot_end, max(self.drag * self.hot_end, self.focus), self.disabled)
|
|
}
|
|
|
|
// ---------------------------------------------------------------- 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.draw_bg.size = uniform(18.0)
|
|
mod.widgets.ToggleSlide.draw_bg.size = uniform(18.0)
|
|
mod.widgets.ToggleRocker.draw_bg.pill_aspect = uniform(1.89)
|
|
mod.widgets.ToggleSlide.draw_bg.pill_aspect = uniform(1.89)
|
|
mod.widgets.ToggleRocker.label_walk.margin.left = 34.0
|
|
mod.widgets.ToggleSlide.label_walk.margin.left = 34.0
|
|
mod.widgets.ToggleRocker.draw_bg.pixel = 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 gl_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 gl_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 gl_led_lamp_hue = fn(ink: vec3, vivid: float) -> vec3 {
|
|
let f = gl_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 gl_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 = gl_led_lamp_full(ink)
|
|
let hue = gl_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 = gl_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 glossy dark ring with its own soft shadow.
|
|
// The halo's room is held in the lamp's own box (the library reserves a
|
|
// lens thickness either side once a halo is asked for), so the light is
|
|
// never cut at its edge.
|
|
mod.widgets.Lamp.halo = 0.3
|
|
mod.widgets.Lamp.halo_reach = 1.0
|
|
mod.widgets.Lamp.draw_bg.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 = gl_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.224, 0.231, 0.267), max(room - 0.5, 2.0), vec3(0.20, 0.21, 0.245), 1.0)
|
|
return res * self.opacity
|
|
}
|
|
true
|