All outline text draws with Slug curves on every platform (SDF/MSDF stays selectable per size); a frame never presents a blank glyph and retained draw lists keep theirs. One coverage model for vector feathering, widget families split out of the core crate, fewer steady-state allocations, video keeps its aspect (Contain/Cover), render and SSAO pass unification. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2619 lines
128 KiB
Text
2619 lines
128 KiB
Text
use mod.prelude.widgets_internal.*
|
|
|
|
// A segment display on a pale glass: the face of a digital wristwatch and
|
|
// the screen of a handheld game console with a dot-matrix screen.
|
|
// The glass is flat and holds four shades and no more: ink, a dark and a
|
|
// light middle, and the pale ground; anything between them is a dither,
|
|
// never a veil. Every mark on it is ink, the unlit segments stay faintly
|
|
// there as ghosts, and the ink floats a hair above the reflector, so it
|
|
// throws a faint shadow down and to the left. Keys are pixel-framed tiles
|
|
// or inverse legends; groups are glass windows set in a dark frame with a
|
|
// light lip under it. A state is a flag, a filled segment or an inverse
|
|
// legend. Nothing glows.
|
|
mod.widgets.ButtonFlat.padding = mod.turtle.Inset{left: 10.0 right: 10.0 top: 2.5 bottom: 2.5}
|
|
mod.widgets.ButtonFlat.min_height = 24.0
|
|
mod.widgets.ButtonFlat.spacing = 6.0
|
|
mod.widgets.Button.draw_bg.border_radius = 1.5
|
|
mod.widgets.Button.draw_bg.border_size = 0.34
|
|
// A ROW OF FIELDS IS ONE HEIGHT: the box metrics are stated once here and
|
|
// handed to each field below. Buttons stand at the same 24.
|
|
let field_height = 24.0
|
|
let field_pad_v = 2.5
|
|
let field_radius = 1.5
|
|
let field_border = 0.34
|
|
|
|
// The room a Fit field takes: where a field is Fit, the padding IS its
|
|
// height.
|
|
let field_room = fn(w) {
|
|
w.min_height = field_height
|
|
w.padding.top = field_pad_v
|
|
w.padding.bottom = field_pad_v
|
|
}
|
|
// The box a field draws. Given the draw object and not the widget, because
|
|
// not every field draws its own box.
|
|
let field_face = fn(d) {
|
|
d.border_radius = field_radius
|
|
d.border_size = field_border
|
|
}
|
|
// A field that states a frame and lays its own parts out inside it takes
|
|
// the height alone: a padding would displace its line.
|
|
let field_frame = fn(w) {
|
|
w.min_height = field_height
|
|
}
|
|
|
|
field_room(mod.widgets.TextInput)
|
|
field_face(mod.widgets.TextInput.draw_bg)
|
|
field_room(mod.widgets.ComboBox)
|
|
field_face(mod.widgets.ComboBox.draw_bg)
|
|
field_room(mod.widgets.DropDown)
|
|
field_face(mod.widgets.DropDown.draw_bg)
|
|
field_room(mod.widgets.DropDown2)
|
|
field_face(mod.widgets.DropDown2.draw_bg)
|
|
field_room(mod.widgets.FieldWell)
|
|
field_face(mod.widgets.FieldWell.draw_bg)
|
|
field_room(mod.widgets.TagField)
|
|
field_face(mod.widgets.TagField.draw_bg)
|
|
field_frame(mod.widgets.NumberField)
|
|
mod.widgets.NumberField.draw_bg.border_radius = field_radius
|
|
field_frame(mod.widgets.ValueInput)
|
|
field_frame(mod.widgets.DateField)
|
|
field_frame(mod.widgets.TimeField)
|
|
field_frame(mod.widgets.DatePicker)
|
|
field_frame(mod.widgets.DateRangePicker)
|
|
// The chrome-less input a well holds must impose no height of its own.
|
|
mod.widgets.WellInput.min_height = 0.0
|
|
mod.widgets.Window.pass.clear_color = #9ca388
|
|
|
|
// Plain views stop clipping, so a frame, a lip, an ink shadow or a focus
|
|
// ring may fall into the gap beside a control. Scroll views keep theirs.
|
|
mod.widgets.View.clip_x = false
|
|
mod.widgets.View.clip_y = false
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// SHARED PARTS. Distances in points, negative inside, y down; `px` is one
|
|
// device pixel in points; `o` the quad's origin in the window.
|
|
|
|
// The four shades of the glass, the ghost between the two palest, the lit
|
|
// glass of an inverted legend, and the dark frame of a window.
|
|
let lcd_ink = fn() -> vec3 { return vec3(0.161, 0.169, 0.125) }
|
|
let lcd_dark = fn() -> vec3 { return vec3(0.337, 0.353, 0.275) }
|
|
let lcd_mid = fn() -> vec3 { return vec3(0.478, 0.502, 0.396) }
|
|
let lcd_ghost = fn() -> vec3 { return vec3(0.553, 0.576, 0.482) }
|
|
let lcd_glass = fn() -> vec3 { return vec3(0.635, 0.663, 0.557) }
|
|
// The page is the glass a step down; a window's glass a step up from it.
|
|
let lcd_ground = fn() -> vec3 { return vec3(0.612, 0.639, 0.533) }
|
|
let lcd_wglass = fn() -> vec3 { return vec3(0.682, 0.710, 0.604) }
|
|
let lcd_lit = fn() -> vec3 { return vec3(0.714, 0.741, 0.635) }
|
|
let lcd_frame = fn() -> vec3 { return vec3(0.110, 0.114, 0.129) }
|
|
let lcd_red = fn() -> vec3 { return vec3(0.639, 0.149, 0.110) }
|
|
|
|
let lcd_g3 = fn(v: float) -> vec3 { return vec3(v, v, v) }
|
|
|
|
// A box of true radius r.
|
|
let lcd_rbox = fn(p: vec2, c: vec2, h: vec2, r: float) -> float {
|
|
let q = abs(p - c) - h + vec2(r, r)
|
|
return min(max(q.x, q.y), 0.0) + length(max(q, vec2(0.0, 0.0))) - r
|
|
}
|
|
|
|
// Premultiplied source-over.
|
|
let lcd_over = fn(dst: vec4, src: vec4) -> vec4 {
|
|
return src + dst * (1.0 - src.a)
|
|
}
|
|
|
|
// A box snapped to whole device pixels in window space, in the quad's
|
|
// local points: .xy the low corner, .zw the high one.
|
|
let lcd_snapbox = fn(o: vec2, lo: vec2, hi: vec2, px: float) -> vec4 {
|
|
let a = vec2(Finish.snap(o.x + lo.x, px), Finish.snap(o.y + lo.y, px)) - o
|
|
let b = vec2(Finish.snap(o.x + hi.x, px), Finish.snap(o.y + hi.y, px)) - o
|
|
return vec4(a.x, a.y, b.x, b.y)
|
|
}
|
|
|
|
let lcd_grow = fn(rp: vec2, rs: vec2, m: float) -> vec4 {
|
|
return vec4(rp.x - m, rp.y - m, rs.x + 2.0 * m, rs.y + 2.0 * m)
|
|
}
|
|
|
|
// The centre of the device pixel p lies in, in local points: shapes tested
|
|
// there come out as hard pixel art, stairs and all.
|
|
let lcd_pix = fn(p: vec2, o: vec2, px: float) -> vec2 {
|
|
return (floor((p + o) / px + vec2(0.001, 0.001)) + vec2(0.5, 0.5)) * px - o
|
|
}
|
|
|
|
// Dithers, counted in window pixels: a checker (half), and one pixel in
|
|
// four (quarter).
|
|
let lcd_checker = fn(p: vec2, o: vec2, px: float) -> float {
|
|
let q = floor((p + o) / px + vec2(0.001, 0.001))
|
|
return step(0.5, fract((q.x + q.y) * 0.5))
|
|
}
|
|
let lcd_quarter = fn(p: vec2, o: vec2, px: float) -> float {
|
|
let q = floor((p + o) / px + vec2(0.001, 0.001))
|
|
return step(0.5, fract(q.x * 0.5)) * step(0.5, fract(q.y * 0.5))
|
|
}
|
|
|
|
// A straight segment from a to b: the distance to its axis.
|
|
let lcd_seg = fn(p: vec2, a: vec2, b: vec2) -> float {
|
|
let pa = p - a
|
|
let ba = b - a
|
|
let t = clamp(dot(pa, ba) / max(dot(ba, ba), 0.0001), 0.0, 1.0)
|
|
return length(pa - ba * t)
|
|
}
|
|
|
|
// The shadow ink throws on the reflector, given the mark's distance at p
|
|
// moved back by the throw (-0.75, +1.0): a blur of a point and a half,
|
|
// about a seventh of the ink at the mark, gone within five device pixels.
|
|
let lcd_inkshade = fn(ds: float, amt: float) -> float {
|
|
return (1.0 - smoothstep(-1.0, 2.5, ds)) * amt
|
|
}
|
|
let lcd_throw = fn() -> vec2 { return vec2(-0.75, 1.0) }
|
|
|
|
// A dashed ring one device pixel wide, `gap` points outside a box edge:
|
|
// two device pixels on, two off, counted in window pixels along the side.
|
|
// It is the focus mark of every control.
|
|
let lcd_dash_ring = fn(d: float, p: vec2, c: vec2, h: vec2, o: vec2, gap: float, px: float) -> float {
|
|
let ring = Finish.band(-(d - gap - px), 0.0, px, px)
|
|
let side = step(abs(p.y - c.y) - h.y, abs(p.x - c.x) - h.x)
|
|
let t = mix(p.x + o.x, p.y + o.y, side)
|
|
let on = step(fract(floor(t / px + 0.01) * 0.25), 0.3)
|
|
return ring * on
|
|
}
|
|
|
|
// The glass in window points: the base, a sheen lighter toward the upper
|
|
// right, a one-device-pixel grain that never repeats, and the pixel matrix
|
|
// of the panel, a dot every second device pixel each way.
|
|
let lcd_glass_at = fn(base: vec3, w: vec2, px: float, sheen: float) -> vec3 {
|
|
var col = base * (1.0 + sheen)
|
|
col = col + lcd_g3(Finish.grain(w / px, 0.022))
|
|
let cell = floor(w / px + vec2(0.01, 0.01))
|
|
let dot = step(fract(cell.x / 2.0), 0.1) * step(fract(cell.y / 2.0), 0.1)
|
|
col = col - lcd_g3(0.03 * dot)
|
|
return col
|
|
}
|
|
|
|
// A 5 by 7 dot-matrix letter: O, N or F (0, 1, 2), one bit per row in each
|
|
// of five columns, the top row the lowest bit. `q` counts device pixels
|
|
// from the letter's top left.
|
|
let lcd_letter = fn(ch: float, q: vec2) -> float {
|
|
let col = q.x
|
|
var c0 = 62.0
|
|
var c1 = 65.0
|
|
var c2 = 65.0
|
|
var c3 = 65.0
|
|
var c4 = 62.0
|
|
if ch > 0.5 {
|
|
c0 = 127.0
|
|
c1 = 4.0
|
|
c2 = 8.0
|
|
c3 = 16.0
|
|
c4 = 127.0
|
|
}
|
|
if ch > 1.5 {
|
|
c0 = 127.0
|
|
c1 = 9.0
|
|
c2 = 9.0
|
|
c3 = 9.0
|
|
c4 = 1.0
|
|
}
|
|
let v = c0 * step(col, 0.5) + c1 * step(0.5, col) * step(col, 1.5) + c2 * step(1.5, col) * step(col, 2.5) + c3 * step(2.5, col) * step(col, 3.5) + c4 * step(3.5, col) * step(col, 4.5)
|
|
let bit = step(0.5, fract(floor(v / pow(2.0, q.y) + 0.001) * 0.5))
|
|
return bit * step(0.0, q.x) * step(q.x, 4.5) * step(0.0, q.y) * step(q.y, 6.5)
|
|
}
|
|
// A word of dot-matrix letters, "OFF" (0) or "ON" (1), set with its top
|
|
// left at `at` (a window-snapped point); coverage at p, in whole pixels.
|
|
let lcd_word = fn(p: vec2, o: vec2, at: vec2, px: float, word: float) -> float {
|
|
let q = floor((p + o) / px + vec2(0.001, 0.001)) - floor((at + o) / px + vec2(0.5, 0.5))
|
|
let k = floor(q.x / 6.0)
|
|
let lq = vec2(q.x - k * 6.0, q.y)
|
|
let n = mix(3.0, 2.0, word)
|
|
// OFF: O F F; ON: O N
|
|
let ch = mix(mix(0.0, 2.0, step(0.5, k)), mix(0.0, 1.0, step(0.5, k)), word)
|
|
return lcd_letter(ch, lq) * step(0.0, k) * step(k, n - 0.5)
|
|
}
|
|
|
|
// A small seven-segment numeral row: the scale numbers 0 25 50 75 100 (k
|
|
// = 0..4), each set on its tick, the first flush left and the last flush
|
|
// right. Returns coverage, and .y the distance to the nearest number's
|
|
// extent (for the rule that joins them).
|
|
let lcd_scale_numbers = fn(p: vec2, x0: float, x1: float, y0: float, dh: float, px: float) -> vec2 {
|
|
let span = max(x1 - x0, 1.0)
|
|
let k = clamp(floor((p.x - x0) / (span * 0.25) + 0.5), 0.0, 4.0)
|
|
let ax = x0 + k * span * 0.25
|
|
let n = mix(mix(1.0, 2.0, step(0.5, k)), 3.0, step(3.5, k))
|
|
let adv = dh * 0.68
|
|
let cw = dh * 0.5
|
|
let w = (n - 1.0) * adv + cw
|
|
let align = mix(mix(0.0, 0.5, step(0.5, k)), 1.0, step(3.5, k))
|
|
let left = ax - w * align
|
|
let j = floor((p.x - left) / adv)
|
|
let u = (p.x - left - j * adv) / cw
|
|
// the digits of 0, 25, 50, 75, 100
|
|
var dg = 0.0
|
|
if k > 0.5 && k < 1.5 {
|
|
dg = mix(2.0, 5.0, step(0.5, j))
|
|
}
|
|
if k > 1.5 && k < 2.5 {
|
|
dg = mix(5.0, 0.0, step(0.5, j))
|
|
}
|
|
if k > 2.5 && k < 3.5 {
|
|
dg = mix(7.0, 5.0, step(0.5, j))
|
|
}
|
|
if k > 3.5 {
|
|
dg = mix(1.0, 0.0, step(0.5, j))
|
|
}
|
|
let uv = vec2(u, (p.y - y0) / dh)
|
|
let inside = step(0.0, j) * step(j, n - 0.5) * step(0.0, u) * step(u, 1.0)
|
|
let a = Finish.seg7(uv, dg, px / dh) * inside
|
|
let ext = max(left - p.x, p.x - (left + w))
|
|
return vec2(a, ext)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// BUTTONS. One face for the family; the template picks the kind:
|
|
// 0 a cell drawn in ink on the glass, framed by `frame` (0 none, 1 a
|
|
// one-pixel outline, 2 a dashed outline, 3 an ink rule under the word);
|
|
// 1 a pixel-framed tile, the frame two device pixels of ink with stepped
|
|
// corners; 2 the inverse tile, filled with ink, lit glass letters;
|
|
// 3 the warning tile, its frame and word printed in red on the glass,
|
|
// filled red while held; 4 a tile framed twice.
|
|
// Under the pointer a glass face lays the checker dither; pressed, it
|
|
// inverts; an inverse face turns back to glass.
|
|
// The quad is grown 5 points for the ink shadow and the focus ring.
|
|
let lcd_button_face = fn(p: vec2, sb: vec4, o: vec2, px: float, kind: float, frame: float, hover: float, down: float, focus: float, disabled: float) -> vec4 {
|
|
let live = 1.0 - disabled
|
|
let c = (sb.xy + sb.zw) * 0.5
|
|
let h = (sb.zw - sb.xy) * 0.5
|
|
let held = down * live
|
|
let pq = lcd_pix(p, o, px)
|
|
let tile = step(0.5, kind)
|
|
// the stepped outline of a tile, tested on pixel centres; a cell is
|
|
// square
|
|
let k = mix(0.01, 3.0 * px, tile)
|
|
let dq = Finish.sd_chamfer(pq, c, h, k)
|
|
let d = mix(lcd_rbox(p, c, h, 0.01), dq, tile)
|
|
let inside = mix(Finish.cover(d, px), step(dq, 0.0), tile)
|
|
let inverse = step(1.5, kind) * step(kind, 2.5)
|
|
let red = step(2.5, kind) * step(kind, 3.5)
|
|
let under = step(2.5, frame) * (1.0 - tile)
|
|
// what is filled: the inverse tile at rest, any glass face while held;
|
|
// the inverse tile turns to glass while held
|
|
let solid = mix(held, 1.0 - held, inverse)
|
|
// the rule under the word of the tonal cell: the label's width and two
|
|
// points either side, three points under its foot
|
|
let uy = Finish.snap(o.y + c.y + 6.5, px) - o.y
|
|
let ux0 = sb.x + 8.0
|
|
let ux1 = sb.z - 8.0
|
|
let uc = vec2((ux0 + ux1) * 0.5, uy + px * 0.5)
|
|
let uh = vec2(max((ux1 - ux0) * 0.5, 1.0), px * 0.5)
|
|
let ul = step(ux0, p.x) * step(p.x, ux1) * step(uy, p.y) * step(p.y, uy + px) * under * (1.0 - solid)
|
|
var out = vec4(0.0, 0.0, 0.0, 0.0)
|
|
// the ink's shadow on the reflector: of the fill, of the frame, of the
|
|
// rule; a held key lies flat
|
|
let th = lcd_throw()
|
|
let ds = Finish.sd_chamfer(p - th, c, h, k)
|
|
let framed = max(tile, step(0.5, frame) * step(frame, 2.5))
|
|
let shf = lcd_inkshade(ds, 0.14) * solid * (1.0 - inside) * (1.0 - held)
|
|
let shl = lcd_inkshade(abs(ds + px) - px, 0.14) * framed * (1.0 - solid) * (1.0 - inside)
|
|
let shu = lcd_inkshade(lcd_rbox(p - th, uc, uh, 0.01), 0.14) * under * (1.0 - solid) * (1.0 - ul)
|
|
let sh = max(max(shf, shl), shu) * live
|
|
out = vec4(lcd_ink() * sh, sh)
|
|
// the band under the pointer: a checker of the light middle
|
|
let chk = lcd_checker(p, o, px)
|
|
let ba = inside * chk * hover * live * (1.0 - solid)
|
|
out = lcd_over(out, vec4(lcd_mid() * ba, ba))
|
|
// the fill: ink, or on the warning tile the printed red
|
|
var fcol = mix(lcd_ink(), lcd_dark(), 0.35 * hover * live * inverse)
|
|
fcol = mix(fcol, lcd_red(), red)
|
|
let fa = inside * solid
|
|
let fill = mix(fcol, lcd_mid(), disabled * inverse)
|
|
out = lcd_over(out, vec4(fill * fa, fa))
|
|
// the frame
|
|
var line = 0.0
|
|
// a tile: two device pixels of stepped ink
|
|
line = max(line, step(dq, 0.0) * step(-2.0 * px, dq) * tile * (1.0 - inverse))
|
|
// twice: an inner line two pixels further in
|
|
line = max(line, step(dq, -3.0 * px) * step(-4.0 * px, dq) * step(3.5, kind))
|
|
// a cell: one device pixel, solid or dashed
|
|
var cl = Finish.band(d, 0.0, px, px) * step(0.5, frame) * step(frame, 2.5) * (1.0 - tile)
|
|
let side = step(abs(p.y - c.y) - h.y + 1.0, abs(p.x - c.x) - h.x + 1.0)
|
|
let t = mix(p.x + o.x, p.y + o.y, side)
|
|
let dash = step(fract(floor(t / px + 0.01) * 0.25), 0.3)
|
|
cl = cl * mix(1.0, dash, step(1.5, frame))
|
|
line = max(line, cl)
|
|
line = max(line, ul)
|
|
// the inverse tile's frame shows while it is held (turned to glass)
|
|
line = max(line, step(dq, 0.0) * step(-2.0 * px, dq) * inverse * held)
|
|
let lcol = mix(mix(lcd_ink(), lcd_red(), red), lcd_mid(), disabled)
|
|
let la = line * (1.0 - solid * (1.0 - inverse))
|
|
out = lcd_over(out, vec4(lcol * la, la))
|
|
// the inverse tile under the pointer: a glass keyline two pixels in
|
|
let kl = step(dq, -2.0 * px) * step(-3.0 * px, dq) * inverse * hover * live * (1.0 - held)
|
|
out = lcd_over(out, vec4(lcd_lit() * kl, kl))
|
|
// focus: a dashed ink ring two points out
|
|
let kf = lcd_dash_ring(d, p, c, h, o, 2.0, px) * focus * live
|
|
out = lcd_over(out, vec4(lcd_ink() * kf, kf))
|
|
return out
|
|
}
|
|
|
|
// The wait spinner the stock face drew, which this one must draw too: eight
|
|
// segments round the middle of the face, the lit one walking on the
|
|
// button's own animation clock, the rest at ghost.
|
|
let lcd_wait = fn(p: vec2, c: vec2, px: float, anim: float, ink: vec3, ghost: vec3) -> vec4 {
|
|
let v = p - c
|
|
var th = atan2(v.x, -v.y)
|
|
if th < 0.0 {
|
|
th = th + 2.0 * PI
|
|
}
|
|
let k = floor(th / (PI * 0.25) + 0.5)
|
|
let ang = k * PI * 0.25
|
|
let dir = vec2(sin(ang), -cos(ang))
|
|
let along = clamp(dot(v, dir), 2.5, 5.5)
|
|
let dd = length(v - dir * along) - 0.75
|
|
let a = Finish.cover(dd, px)
|
|
let head = floor(fract(anim) * 8.0)
|
|
let age = fract((head - k) / 8.0 + 1.0)
|
|
let lit = max(1.0 - age * 3.0, 0.0)
|
|
let col = mix(ghost, ink, lit)
|
|
return vec4(col * a, a)
|
|
}
|
|
|
|
let lcd_button = fn(pos: vec2, size: vec2, o: vec2, px: float, kind: float, frame: float, hover: float, down: float, focus: float, disabled: float, loading: float, anim: float, m: float) -> vec4 {
|
|
let p = pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
let sb = lcd_snapbox(o, vec2(0.0, 0.0), size, px)
|
|
var out = lcd_button_face(p, sb, o, px, kind, frame, hover, down, focus, disabled)
|
|
if loading > 0.001 {
|
|
let c = (sb.xy + sb.zw) * 0.5
|
|
let dark = step(1.5, kind) * step(kind, 2.5)
|
|
let ink = mix(lcd_ink(), lcd_lit(), dark)
|
|
let ghost = mix(lcd_ghost(), lcd_dark(), dark)
|
|
let w = lcd_wait(p, c, px, anim, ink, ghost)
|
|
out = lcd_over(out, w * loading)
|
|
}
|
|
return out
|
|
}
|
|
|
|
mod.widgets.ButtonFlat.draw_bg.kind = uniform(0.0)
|
|
mod.widgets.ButtonFlat.draw_bg.frame = uniform(0.0)
|
|
mod.widgets.ButtonFlat.draw_bg.vertex = fn() {
|
|
let g = lcd_grow(self.rect_pos, self.rect_size, 5.0)
|
|
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
|
|
}
|
|
mod.widgets.ButtonFlat.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
return self.host_over(lcd_button(self.pos, self.rect_size, self.rect_pos, px, self.kind, self.frame, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 5.0), 5.0)
|
|
}
|
|
mod.widgets.ButtonFlat.clip_x = false
|
|
mod.widgets.ButtonFlat.clip_y = false
|
|
|
|
// Inks. On the glass: ink, turned to the lit glass when the face inverts.
|
|
mod.widgets.ButtonFlat.draw_text.color = #292b20
|
|
mod.widgets.ButtonFlat.draw_text.color_hover = #292b20
|
|
mod.widgets.ButtonFlat.draw_text.color_down = #b6bda2
|
|
mod.widgets.ButtonFlat.draw_text.color_focus = #292b20
|
|
mod.widgets.ButtonFlat.draw_text.color_disabled = #6c725c
|
|
mod.widgets.ButtonFlat.draw_text.text_style = mod.theme.font_label{}
|
|
mod.widgets.ButtonFlat.draw_icon.color = #292b20
|
|
mod.widgets.ButtonFlat.icon_walk.width = 14.0
|
|
|
|
// The tiles: the standard key and everything cut from it. They are handed
|
|
// a draw object of their own, since most of them share the flat button's.
|
|
let lcd_tile_bg = mod.widgets.ButtonFlat.draw_bg{
|
|
kind: 1.0
|
|
frame: 0.0
|
|
}
|
|
let lcd_tiled = fn(w) {
|
|
w.draw_bg = lcd_tile_bg
|
|
}
|
|
lcd_tiled(mod.widgets.Button)
|
|
lcd_tiled(mod.widgets.ButtonGradientX)
|
|
lcd_tiled(mod.widgets.ButtonGradientY)
|
|
lcd_tiled(mod.widgets.ButtonIcon)
|
|
lcd_tiled(mod.widgets.ButtonGradientXIcon)
|
|
lcd_tiled(mod.widgets.ButtonGradientYIcon)
|
|
lcd_tiled(mod.widgets.ButtonXs)
|
|
lcd_tiled(mod.widgets.ButtonSm)
|
|
lcd_tiled(mod.widgets.ButtonLg)
|
|
lcd_tiled(mod.widgets.ButtonXl)
|
|
lcd_tiled(mod.widgets.ButtonCompound)
|
|
|
|
// The primary: the inverse tile, the one filled key of a row.
|
|
mod.widgets.ButtonPrimary.draw_bg.kind = 2.0
|
|
mod.widgets.ButtonPrimary.draw_text.color = #b6bda2
|
|
mod.widgets.ButtonPrimary.draw_text.color_hover = #c6cdb2
|
|
mod.widgets.ButtonPrimary.draw_text.color_down = #292b20
|
|
mod.widgets.ButtonPrimary.draw_text.color_focus = #b6bda2
|
|
mod.widgets.ButtonPrimary.draw_text.color_disabled = #3a3d2e
|
|
mod.widgets.ButtonPrimary.draw_text.text_style = mod.theme.font_bold{}
|
|
mod.widgets.ButtonPrimary.draw_icon.color = #b6bda2
|
|
|
|
// The second action: the tile framed twice.
|
|
mod.widgets.ButtonSecondary.draw_bg.kind = 4.0
|
|
mod.widgets.ButtonSecondary.draw_text.color = #292b20
|
|
mod.widgets.ButtonSecondary.draw_text.color_hover = #292b20
|
|
mod.widgets.ButtonSecondary.draw_text.color_down = #b6bda2
|
|
mod.widgets.ButtonSecondary.draw_text.color_focus = #292b20
|
|
mod.widgets.ButtonSecondary.draw_text.color_disabled = #6c725c
|
|
mod.widgets.ButtonSecondary.draw_text.text_style = mod.theme.font_label{}
|
|
mod.widgets.ButtonSecondary.draw_icon.color = #292b20
|
|
|
|
// The tonal action: a bare word with an ink rule under it; the pointer
|
|
// lays the dither behind it, a press inverts it.
|
|
mod.widgets.ButtonTertiary.draw_bg.kind = 0.0
|
|
mod.widgets.ButtonTertiary.draw_bg.frame = 3.0
|
|
mod.widgets.ButtonTertiary.draw_text.color = #292b20
|
|
mod.widgets.ButtonTertiary.draw_text.color_hover = #292b20
|
|
mod.widgets.ButtonTertiary.draw_text.color_down = #b6bda2
|
|
mod.widgets.ButtonTertiary.draw_text.color_focus = #292b20
|
|
mod.widgets.ButtonTertiary.draw_text.color_disabled = #6c725c
|
|
mod.widgets.ButtonTertiary.draw_text.text_style = mod.theme.font_label{}
|
|
mod.widgets.ButtonTertiary.draw_icon.color = #292b20
|
|
|
|
// The destructive action: the pixel tile with its frame and its word
|
|
// printed in red on the glass, the one colour of the sheet and only where
|
|
// it means a warning; held, the tile fills with the red.
|
|
mod.widgets.ButtonDanger.draw_bg.kind = 3.0
|
|
mod.widgets.ButtonDanger.draw_bg.frame = 0.0
|
|
mod.widgets.ButtonDanger.draw_text.color = #a3261c
|
|
mod.widgets.ButtonDanger.draw_text.color_hover = #a3261c
|
|
mod.widgets.ButtonDanger.draw_text.color_down = #b6bda2
|
|
mod.widgets.ButtonDanger.draw_text.color_focus = #a3261c
|
|
mod.widgets.ButtonDanger.draw_text.color_disabled = #6c725c
|
|
mod.widgets.ButtonDanger.draw_text.text_style = mod.theme.font_label{}
|
|
mod.widgets.ButtonDanger.draw_icon.color = #a3261c
|
|
|
|
// The outline: the unit box of the panel, one device pixel of ink round a
|
|
// glass cell; dashed, the same line broken.
|
|
mod.widgets.ButtonOutline.draw_bg.kind = 0.0
|
|
mod.widgets.ButtonOutline.draw_bg.frame = 1.0
|
|
mod.widgets.ButtonOutline.draw_text.color = #292b20
|
|
mod.widgets.ButtonOutline.draw_text.color_hover = #292b20
|
|
mod.widgets.ButtonOutline.draw_text.color_down = #b6bda2
|
|
mod.widgets.ButtonOutline.draw_text.color_focus = #292b20
|
|
mod.widgets.ButtonOutline.draw_text.color_disabled = #6c725c
|
|
mod.widgets.ButtonOutline.draw_text.text_style = mod.theme.font_label{}
|
|
mod.widgets.ButtonOutline.draw_icon.color = #292b20
|
|
mod.widgets.ButtonDashed.draw_bg.frame = 2.0
|
|
mod.widgets.ButtonDashed.draw_bg.border_dash = 0.0
|
|
|
|
// The flatter faces: bare ink until the pointer comes.
|
|
mod.widgets.ButtonFlatter.draw_bg.kind = 0.0
|
|
mod.widgets.ButtonFlatter.draw_bg.frame = 0.0
|
|
|
|
// The size rungs keep the family's padding; a small key is 20 tall, a
|
|
// large one 28.
|
|
mod.widgets.ButtonSm.padding = mod.turtle.Inset{left: 8.0 right: 8.0 top: 0.0 bottom: 0.0}
|
|
mod.widgets.ButtonSm.min_height = 20.0
|
|
mod.widgets.ButtonXs.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 0.0 bottom: 0.0}
|
|
mod.widgets.ButtonXs.min_height = 17.0
|
|
mod.widgets.ButtonLg.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 0.0 bottom: 0.0}
|
|
mod.widgets.ButtonLg.min_height = 28.0
|
|
mod.widgets.ButtonXl.padding = mod.turtle.Inset{left: 16.0 right: 16.0 top: 0.0 bottom: 0.0}
|
|
mod.widgets.ButtonXl.min_height = 32.0
|
|
mod.widgets.ButtonIcon.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 3.0 bottom: 3.0}
|
|
|
|
// HELD STATES. The stock tracks write the pointer's hover and the press as
|
|
// snapped values, and a snapped value never arrives when a state is held
|
|
// from the start (a still page, a restored screen). The same states with
|
|
// plain values and a snapped entry look and move the same under a live
|
|
// pointer, and hold.
|
|
let lcd_held_states = fn(w) {
|
|
w.animator.hover.on.from = {all: Snap}
|
|
w.animator.hover.on.apply = {draw_bg: {down: 0.0, hover: 1.0} draw_text: {down: 0.0, hover: 1.0}}
|
|
w.animator.hover.down.from = {all: Snap}
|
|
w.animator.hover.down.apply = {draw_bg: {down: 1.0, hover: 1.0} draw_text: {down: 1.0, hover: 1.0}}
|
|
}
|
|
lcd_held_states(mod.widgets.ButtonFlat)
|
|
lcd_held_states(mod.widgets.CheckBoxFlat)
|
|
lcd_held_states(mod.widgets.ToggleFlat)
|
|
lcd_held_states(mod.widgets.RadioButtonFlat)
|
|
lcd_held_states(mod.widgets.RadioButtonTabFlat)
|
|
lcd_held_states(mod.widgets.TextInputFlat)
|
|
lcd_held_states(mod.widgets.DropDownFlat)
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// CHECK BOXES. A 12 point box of one device pixel of ink; inside it the
|
|
// mark, a square segment 2.5 points in, at ghost when off and ink when on;
|
|
// mixed, a bar; an error hatches the box and doubles its line. Held, the
|
|
// whole box inverts.
|
|
mod.widgets.CheckBoxFlat.padding = mod.turtle.Inset{left: 0.0 right: 0.0 top: 5.0 bottom: 5.0}
|
|
mod.widgets.CheckBoxFlat.label_walk.margin = mod.turtle.Inset{left: 19.0 right: 0.0 top: 0.0 bottom: 0.0}
|
|
mod.widgets.CheckBoxFlat.draw_bg.size = uniform(12.0)
|
|
mod.widgets.CheckBoxFlat.draw_bg.border_size = uniform(0.34)
|
|
mod.widgets.CheckBoxFlat.draw_bg.border_radius = uniform(0.0)
|
|
mod.widgets.CheckBoxFlat.draw_text.color_error = #292b20
|
|
|
|
// The mark box of a check box, radio or toggle: where it stands in the quad.
|
|
let lcd_markbox = fn(size: vec2, o: vec2, w: float, hgt: float, at_end: float, px: float) -> vec4 {
|
|
let x = (size.x - w) * at_end
|
|
let y = size.y * 0.5 - hgt * 0.5
|
|
return lcd_snapbox(o, vec2(x, y), vec2(x + w, y + hgt), px)
|
|
}
|
|
|
|
mod.widgets.CheckBoxFlat.draw_bg.vertex = fn() {
|
|
let g = lcd_grow(self.rect_pos, self.rect_size, 5.0)
|
|
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
|
|
}
|
|
mod.widgets.CheckBoxFlat.draw_bg.pixel = fn() {
|
|
let m = 5.0
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
let o = self.rect_pos
|
|
let live = 1.0 - self.disabled
|
|
let held = self.down * live
|
|
let sb = lcd_markbox(self.rect_size, o, self.size, self.size, self.mark_at_end, px)
|
|
let c = (sb.xy + sb.zw) * 0.5
|
|
let h = (sb.zw - sb.xy) * 0.5
|
|
let d = lcd_rbox(p, c, h, 0.01)
|
|
let inside = Finish.cover(d, px)
|
|
let on = self.active * (1.0 - self.mixed)
|
|
var out = vec4(0.0, 0.0, 0.0, 0.0)
|
|
// the ink's shadow: the line's, or the held box's
|
|
let ds = lcd_rbox(p - lcd_throw(), c, h, 0.01)
|
|
let sh = mix(lcd_inkshade(abs(ds) - px * 0.5, 0.14), lcd_inkshade(ds, 0.14), held) * (1.0 - inside) * live
|
|
out = vec4(lcd_ink() * sh, sh)
|
|
// the pointer lays the light dither inside
|
|
let chk = lcd_checker(p, o, px)
|
|
let ba = inside * chk * self.hover * live * (1.0 - held)
|
|
out = lcd_over(out, vec4(lcd_mid() * ba, ba))
|
|
// an error hatches the box: one pixel on the diagonal every three
|
|
let hatch = Finish.stripes(p + o, 0.785398, 3.0 * px, 0.34, px) * inside * self.error * (1.0 - held)
|
|
out = lcd_over(out, vec4(lcd_dark() * hatch, hatch))
|
|
// held, the box inverts
|
|
let ia = inside * held
|
|
out = lcd_over(out, vec4(lcd_ink() * ia, ia))
|
|
// the line, doubled by an error
|
|
var line = Finish.band(d, 0.0, px, px)
|
|
line = max(line, Finish.ring_out(d, px, 2.0 * px, px) * self.error)
|
|
let lcol = mix(lcd_ink(), lcd_mid(), self.disabled)
|
|
let la = line
|
|
out = lcd_over(out, vec4(lcol * la, la))
|
|
// the mark: a square segment, or the bar of a mixed box; unlit, a ghost
|
|
let inner = h - vec2(2.5, 2.5)
|
|
let mk = Finish.cover(lcd_rbox(p, c, inner, 0.01), px) * (1.0 - self.mixed)
|
|
let bar = Finish.cover(lcd_rbox(p, c, vec2(inner.x, 1.0), 0.01), px) * self.mixed
|
|
let lit = max(mk * on, bar)
|
|
let ghost = mk * (1.0 - on) * (1.0 - self.mixed)
|
|
let gcol = mix(lcd_ghost(), vec3(0.604, 0.631, 0.529), self.disabled)
|
|
let ga = ghost * (1.0 - held)
|
|
out = lcd_over(out, vec4(gcol * ga, ga))
|
|
let mcol = mix(mix(lcd_ink(), lcd_mid(), self.disabled), lcd_lit(), held)
|
|
out = lcd_over(out, vec4(mcol * lit, lit))
|
|
// focus: the dashed ring two points out
|
|
let kf = lcd_dash_ring(d, p, c, h, o, 2.0, px) * self.focus * live
|
|
out = lcd_over(out, vec4(lcd_ink() * kf, kf))
|
|
return out
|
|
}
|
|
mod.widgets.CheckBoxFlat.clip_x = false
|
|
mod.widgets.CheckBoxFlat.clip_y = false
|
|
|
|
// TOGGLES. A flag pair: two cells in one ink box, OFF and ON in dot-matrix
|
|
// capitals; the word that holds is an inverse legend, the other stands at
|
|
// ghost. Pressed, the other cell darkens toward its turn.
|
|
mod.widgets.ToggleFlat.label_walk.margin = mod.turtle.Inset{left: 41.0 right: 0.0 top: 0.0 bottom: 0.0}
|
|
mod.widgets.ToggleFlat.padding = mod.turtle.Inset{left: 0.0 right: 0.0 top: 4.0 bottom: 4.0}
|
|
mod.widgets.ToggleFlat.draw_bg.size = uniform(16.0)
|
|
mod.widgets.ToggleFlat.draw_bg.pill_aspect = uniform(2.125)
|
|
mod.widgets.ToggleFlat.draw_bg.knob_inset = uniform(1.0)
|
|
mod.widgets.ToggleFlat.draw_bg.vertex = fn() {
|
|
let g = lcd_grow(self.rect_pos, self.rect_size, 5.0)
|
|
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
|
|
}
|
|
mod.widgets.ToggleFlat.draw_bg.pixel = fn() {
|
|
let m = 5.0
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
let o = self.rect_pos
|
|
let live = 1.0 - self.disabled
|
|
let held = self.down * live
|
|
let tw = self.size * self.pill_aspect
|
|
let sb = lcd_markbox(self.rect_size, o, tw, self.size, self.mark_at_end, px)
|
|
let c = (sb.xy + sb.zw) * 0.5
|
|
let h = (sb.zw - sb.xy) * 0.5
|
|
let d = lcd_rbox(p, c, h, 0.01)
|
|
let inside = Finish.cover(d, px)
|
|
// which word holds: follows the pointer while dragged
|
|
let t = step(0.5, mix(self.active, self.drag_pos, self.drag))
|
|
let mid = Finish.snap(o.x + c.x - px, px) - o.x
|
|
let in_on = step(mid, p.x)
|
|
let cur = mix(1.0 - in_on, in_on, t)
|
|
var out = vec4(0.0, 0.0, 0.0, 0.0)
|
|
let ds = lcd_rbox(p - lcd_throw(), c, h, 0.01)
|
|
let sh = lcd_inkshade(abs(ds) - px * 0.5, 0.14) * (1.0 - inside) * live
|
|
out = vec4(lcd_ink() * sh, sh)
|
|
// the other cell: dithered under the pointer, darker while pressed
|
|
let chk = lcd_checker(p, o, px)
|
|
let other = inside * (1.0 - cur)
|
|
let oa = other * chk * max(self.hover * live, held)
|
|
let ocol = mix(lcd_mid(), lcd_dark(), held)
|
|
out = lcd_over(out, vec4(ocol * oa, oa))
|
|
// the cell that holds: an ink fill (a light middle while disabled)
|
|
let fa = inside * cur
|
|
let fcol = mix(lcd_ink(), lcd_mid(), self.disabled)
|
|
out = lcd_over(out, vec4(fcol * fa, fa))
|
|
// the box and the rule between the cells
|
|
let rule = step(mid, p.x) * step(p.x, mid + px) * inside
|
|
let la = max(Finish.band(d, 0.0, px, px), rule)
|
|
let lcol = mix(lcd_ink(), lcd_mid(), self.disabled)
|
|
out = lcd_over(out, vec4(lcol * la, la))
|
|
// an error doubles the line
|
|
let el = Finish.ring_out(d, px, 2.0 * px, px) * self.error
|
|
out = lcd_over(out, vec4(lcd_ink() * el, el))
|
|
// the words, 7 pixels tall, centred in their cells
|
|
let yw = Finish.snap(o.y + c.y - 3.5 * px, px) - o.y
|
|
let xoff = Finish.snap(o.x + (sb.x + mid) * 0.5 - 8.5 * px, px) - o.x
|
|
let xon = Finish.snap(o.x + (mid + sb.z) * 0.5 - 5.5 * px, px) - o.x
|
|
let w_off = lcd_word(p, o, vec2(xoff, yw), px, 0.0)
|
|
let w_on = lcd_word(p, o, vec2(xon, yw), px, 1.0)
|
|
let lit_col = mix(lcd_lit(), lcd_glass(), self.disabled)
|
|
let ghost_col = mix(mix(lcd_mid(), lcd_dark(), 0.25), lcd_ghost(), 0.6 * self.disabled)
|
|
let won = w_on * t + w_off * (1.0 - t)
|
|
let wgh = w_on * (1.0 - t) + w_off * t
|
|
out = lcd_over(out, vec4(ghost_col * wgh, wgh))
|
|
out = lcd_over(out, vec4(lit_col * won, won))
|
|
let kf = lcd_dash_ring(d, p, c, h, o, 2.0, px) * self.focus * live
|
|
out = lcd_over(out, vec4(lcd_ink() * kf, kf))
|
|
return out
|
|
}
|
|
mod.widgets.ToggleFlat.clip_x = false
|
|
mod.widgets.ToggleFlat.clip_y = false
|
|
|
|
// RADIOS. A 12 point diamond in stepped pixels; inside, a diamond segment
|
|
// at ghost, filled with ink when chosen.
|
|
mod.widgets.RadioButtonFlat.padding = mod.turtle.Inset{left: 0.0 right: 8.0 top: 5.0 bottom: 5.0}
|
|
mod.widgets.RadioButtonFlat.label_walk.margin = mod.turtle.Inset{left: 19.0 right: 0.0 top: 0.0 bottom: 0.0}
|
|
mod.widgets.RadioButtonFlat.draw_bg.size = uniform(12.0)
|
|
mod.widgets.RadioButtonFlat.draw_bg.vertex = fn() {
|
|
let g = lcd_grow(self.rect_pos, self.rect_size, 5.0)
|
|
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
|
|
}
|
|
mod.widgets.RadioButtonFlat.draw_bg.pixel = fn() {
|
|
let m = 5.0
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
let o = self.rect_pos
|
|
let live = 1.0 - self.disabled
|
|
let held = self.down * live
|
|
let sb = lcd_markbox(self.rect_size, o, self.size + px, self.size + px, 0.0, px)
|
|
let c = (sb.xy + sb.zw) * 0.5
|
|
let r = (sb.z - sb.x) * 0.5
|
|
// a diamond tested on pixel centres: hard stairs
|
|
let q = abs(lcd_pix(p, o, px) - c)
|
|
let dq = q.x + q.y - r
|
|
let inside = step(dq, 0.0)
|
|
var out = vec4(0.0, 0.0, 0.0, 0.0)
|
|
let qs = abs(p - lcd_throw() - c)
|
|
let ds = (qs.x + qs.y - r) * 0.70711
|
|
let sh = lcd_inkshade(abs(ds) - px * 0.5, 0.14) * (1.0 - inside) * live
|
|
out = vec4(lcd_ink() * sh, sh)
|
|
let chk = lcd_checker(p, o, px)
|
|
let ba = inside * chk * self.hover * live * (1.0 - held)
|
|
out = lcd_over(out, vec4(lcd_mid() * ba, ba))
|
|
let ia = inside * held
|
|
out = lcd_over(out, vec4(lcd_ink() * ia, ia))
|
|
// the stepped line, one pixel wide along the stairs
|
|
let line = inside * step(-1.5 * px, dq)
|
|
let lcol = mix(lcd_ink(), lcd_mid(), self.disabled)
|
|
out = lcd_over(out, vec4(lcol * line, line))
|
|
// the inner diamond segment
|
|
let mk = step(q.x + q.y - r * 0.5, 0.0)
|
|
let gcol = mix(lcd_ghost(), vec3(0.604, 0.631, 0.529), self.disabled)
|
|
let ga = mk * (1.0 - self.active) * (1.0 - held)
|
|
out = lcd_over(out, vec4(gcol * ga, ga))
|
|
let mcol = mix(mix(lcd_ink(), lcd_mid(), self.disabled), lcd_lit(), held)
|
|
let ma = mk * self.active
|
|
out = lcd_over(out, vec4(mcol * ma, ma))
|
|
let dd = (abs(p - c).x + abs(p - c).y - r) * 0.70711
|
|
let kf = Finish.band(-(dd - 2.0 - px), 0.0, px, px) * self.focus * live * lcd_checker(p, o, px)
|
|
out = lcd_over(out, vec4(lcd_ink() * kf, kf))
|
|
return out
|
|
}
|
|
mod.widgets.RadioButtonFlat.clip_x = false
|
|
mod.widgets.RadioButtonFlat.clip_y = false
|
|
|
|
// RADIO TABS. Square caption cells: a one-pixel rule above and below and a
|
|
// divider either side; the chosen one is the inverse legend, dark with lit
|
|
// glass letters. The pointer lays the dither, a press inverts.
|
|
let lcd_caption = fn(p: vec2, sb: vec4, o: vec2, px: float, fill: float, dither: float, focus: float, disabled: float) -> vec4 {
|
|
let live = 1.0 - disabled
|
|
let c = (sb.xy + sb.zw) * 0.5
|
|
let h = (sb.zw - sb.xy) * 0.5
|
|
let d = lcd_rbox(p, c, h, 0.01)
|
|
let inx = step(sb.x, p.x) * step(p.x, sb.z)
|
|
let iny = step(sb.y, p.y) * step(p.y, sb.w)
|
|
let inside = inx * iny
|
|
var out = vec4(0.0, 0.0, 0.0, 0.0)
|
|
let sh = lcd_inkshade(lcd_rbox(p - lcd_throw(), c, h, 0.01), 0.14) * fill * (1.0 - inside) * live
|
|
out = vec4(lcd_ink() * sh, sh)
|
|
let chk = lcd_checker(p, o, px)
|
|
let da = inside * chk * dither * (1.0 - fill)
|
|
out = lcd_over(out, vec4(lcd_mid() * da, da))
|
|
let fa = inside * fill
|
|
let fcol = mix(lcd_ink(), lcd_mid(), disabled)
|
|
out = lcd_over(out, vec4(fcol * fa, fa))
|
|
let rules = max(step(p.y, sb.y + px), step(sb.w - px, p.y)) * inx
|
|
let divs = max(step(p.x, sb.x + px), step(sb.z - px, p.x)) * iny
|
|
let la = max(rules, divs) * inside
|
|
let lcol = mix(lcd_ink(), lcd_mid(), disabled)
|
|
out = lcd_over(out, vec4(lcol * la, la))
|
|
let kf = lcd_dash_ring(d, p, c, h, o, 2.0, px) * focus * live
|
|
out = lcd_over(out, vec4(lcd_ink() * kf, kf))
|
|
return out
|
|
}
|
|
mod.widgets.RadioButtonTabFlat.padding = mod.turtle.Inset{left: 0.0 right: 0.0 top: 5.0 bottom: 5.0}
|
|
mod.widgets.RadioButtonTabFlat.label_walk.margin = mod.turtle.Inset{left: 9.0 right: 9.0 top: 0.0 bottom: 0.0}
|
|
mod.widgets.RadioButtonTabFlat.draw_text.color = #292b20
|
|
mod.widgets.RadioButtonTabFlat.draw_text.color_hover = #292b20
|
|
mod.widgets.RadioButtonTabFlat.draw_text.color_down = #b6bda2
|
|
mod.widgets.RadioButtonTabFlat.draw_text.color_focus = #292b20
|
|
mod.widgets.RadioButtonTabFlat.draw_text.color_active = #b6bda2
|
|
mod.widgets.RadioButtonTabFlat.draw_text.color_disabled = #6c725c
|
|
mod.widgets.RadioButtonTabFlat.draw_text.text_style = mod.theme.font_label{}
|
|
mod.widgets.RadioButtonTabFlat.draw_bg.vertex = fn() {
|
|
let g = lcd_grow(self.rect_pos, self.rect_size, 5.0)
|
|
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
|
|
}
|
|
mod.widgets.RadioButtonTabFlat.draw_bg.pixel = fn() {
|
|
let m = 5.0
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
let sb = lcd_snapbox(self.rect_pos, vec2(0.0, 0.0), self.rect_size, px)
|
|
// chosen: the inverse legend; the press inverts too
|
|
let live = 1.0 - self.disabled
|
|
let inv = max(self.active, self.down * live)
|
|
return lcd_caption(p, sb, self.rect_pos, px, inv, self.hover * live, self.focus, self.disabled)
|
|
}
|
|
mod.widgets.RadioButtonTabFlat.clip_x = false
|
|
mod.widgets.RadioButtonTabFlat.clip_y = false
|
|
mod.widgets.RadioButtonTab.clip_x = false
|
|
mod.widgets.RadioButtonTab.clip_y = false
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// SLIDERS. The bar scale of the wristwatch: a row of short dashes, the
|
|
// value's run filled with ink and the rest at ghost; over it the scale's
|
|
// numbers 0 25 50 75 100 in small segment digits, joined by a one-pixel
|
|
// rule; under it the thumb, a small ink pointer. The pointer is outlined
|
|
// under the hand, and grows while dragged.
|
|
let lcd_scale = fn(p: vec2, o: vec2, size: vec2, top: float, px: float, slide: float, lo: float, hi: float, numbers: float, hover: float, focus: float, drag: float, disabled: float) -> vec4 {
|
|
let live = 1.0 - disabled
|
|
let x0 = 3.0
|
|
let x1 = size.x - 3.0
|
|
let span = max(x1 - x0, 1.0)
|
|
// rows, from the top of the track band
|
|
let dh = 5.5
|
|
let y_num = top
|
|
let y_dash = Finish.snap(o.y + top + mix(0.5, 7.5, numbers), px) - o.y
|
|
let dash_h = 4.0
|
|
let y_ptr = y_dash + dash_h + 1.0
|
|
var out = vec4(0.0, 0.0, 0.0, 0.0)
|
|
// the dashes: whole segments, a gap of two points, about 8.5 apart
|
|
let n = max(floor((span + 2.0) / 8.5), 4.0)
|
|
let pitch = (span + 2.0) / n
|
|
let k = floor((p.x - x0) / pitch)
|
|
let within = p.x - x0 - k * pitch
|
|
let in_dash = step(0.0, within) * step(within, pitch - 2.0) * step(0.0, k) * step(k, n - 1.0)
|
|
let in_row = step(y_dash, p.y) * step(p.y, y_dash + dash_h)
|
|
let sc = (k + 0.5) / n
|
|
let lit = step(lo, sc + 0.5 / n - 0.0001) * step(sc - 0.5 / n + 0.0001, hi) * step(0.0001, hi - lo + 0.0001)
|
|
let full = step(lo, sc) * step(sc, hi)
|
|
let on = max(full, lit * step(0.0, hi - lo - 0.0001) * step(sc, hi + 0.0001) * step(lo - 0.0001, sc))
|
|
let da = in_dash * in_row
|
|
let lcol = mix(lcd_ink(), lcd_mid(), disabled)
|
|
let dcol = mix(lcd_ghost(), lcol, on)
|
|
// the lit dashes throw the ink's shadow
|
|
let sp = p - lcd_throw()
|
|
let sk = clamp(floor((sp.x - x0 + 1.0) / pitch), 0.0, n - 1.0)
|
|
let sbc = vec2(x0 + sk * pitch + (pitch - 2.0) * 0.5, y_dash + dash_h * 0.5)
|
|
let sbd = lcd_rbox(sp, sbc, vec2((pitch - 2.0) * 0.5, dash_h * 0.5), 0.01)
|
|
let ssc = (sk + 0.5) / n
|
|
let sh = lcd_inkshade(sbd, 0.14) * step(lo, ssc) * step(ssc, hi) * (1.0 - da) * live
|
|
out = vec4(lcd_ink() * sh, sh)
|
|
out = lcd_over(out, vec4(dcol * da, da))
|
|
// the numbers and the rule joining them
|
|
if numbers > 0.5 {
|
|
let nm = lcd_scale_numbers(p, x0, x1, y_num, dh, px)
|
|
let ncol = mix(lcd_ink(), lcd_mid(), disabled)
|
|
let na = nm.x
|
|
out = lcd_over(out, vec4(ncol * na, na))
|
|
let yr = Finish.snap(o.y + y_num + dh * 0.5, px) - o.y
|
|
let rule = step(yr, p.y) * step(p.y, yr + px) * step(1.5, nm.y) * step(x0, p.x) * step(p.x, x1)
|
|
let ra = rule * 0.8
|
|
out = lcd_over(out, vec4(ncol * ra, ra))
|
|
}
|
|
// the pointer: a small triangle, tip up against the dashes
|
|
let hx = Finish.snap(o.x + x0 + slide * span, px) - o.x + px * 0.5
|
|
let grow = 1.0 + 0.35 * drag * live
|
|
let ph = 3.5 * grow
|
|
let pw = 3.5 * grow
|
|
let q = p - vec2(hx, y_ptr)
|
|
let td = max(-q.y, abs(q.x) * (ph / pw) + q.y - ph) * 0.7
|
|
let pa = Finish.cover(td, px)
|
|
let psd = max(-(q.y - 1.0), abs(q.x + 0.75) * (ph / pw) + (q.y - 1.0) - ph) * 0.7
|
|
let psh = lcd_inkshade(psd, 0.14) * (1.0 - pa) * live
|
|
out = lcd_over(out, vec4(lcd_ink() * psh, psh))
|
|
out = lcd_over(out, vec4(lcol * pa, pa))
|
|
// under the pointer: a one-pixel box round the thumb; focus: dashed
|
|
let bc = vec2(hx, y_ptr + ph * 0.5)
|
|
let bh = vec2(pw + 1.5, ph * 0.5 + 1.5)
|
|
let bd = lcd_rbox(p, bc, bh, 0.01)
|
|
let hb = Finish.band(-bd, 0.0, px, px) * hover * live * (1.0 - focus)
|
|
out = lcd_over(out, vec4(lcd_ink() * hb, hb))
|
|
let kf = lcd_dash_ring(bd, p, bc, bh, o, 0.0, px) * focus * live
|
|
out = lcd_over(out, vec4(lcd_ink() * kf, kf))
|
|
return out
|
|
}
|
|
|
|
mod.widgets.SliderFlat.draw_bg.vertex = fn() {
|
|
let g = lcd_grow(self.rect_pos, self.rect_size, 4.0)
|
|
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
|
|
}
|
|
mod.widgets.SliderFlat.draw_bg.pixel = fn() {
|
|
let m = 4.0
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
let lo = min(self.origin_pos, self.slide_pos) * self.arc_origin
|
|
let hi = mix(self.slide_pos, max(self.origin_pos, self.slide_pos), self.arc_origin)
|
|
return lcd_scale(p, self.rect_pos, self.rect_size, 19.5, px, self.slide_pos, lo, hi, 1.0, self.hover, self.focus, self.drag, self.disabled)
|
|
}
|
|
mod.widgets.SliderFlat.clip_x = false
|
|
mod.widgets.SliderFlat.clip_y = false
|
|
|
|
// The minimal slider: the dashes and the pointer, no numbers.
|
|
mod.widgets.SliderMinimal.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let lo = min(self.origin_pos, self.slide_pos) * self.arc_origin
|
|
let hi = mix(self.slide_pos, max(self.origin_pos, self.slide_pos), self.arc_origin)
|
|
return lcd_scale(p, self.rect_pos, self.rect_size, self.offset_y, px, self.slide_pos, lo, hi, 0.0, self.hover, self.focus, self.drag, self.disabled)
|
|
}
|
|
|
|
// The value read out beside a slider: tabular digits in the ink.
|
|
let lcd_value_style = mod.theme.font_regular{font_size: 7.5 font_family: mod.theme.font_regular.font_family{ibm_plex_text := FontMember{res: crate_resource("makepad_widgets:resources/jetbrains_mono_variable.ttf") weight: 600.0 asc: 0.0 desc: 0.0}}}
|
|
mod.widgets.SliderMinimal.text_input.draw_text.text_style = lcd_value_style
|
|
mod.widgets.SliderFlat.text_input.draw_text.text_style = lcd_value_style
|
|
mod.widgets.SliderRoundFlat.text_input.draw_text.text_style = lcd_value_style
|
|
|
|
// The round slider becomes a segment bar: a glass window framed in one
|
|
// pixel of ink, its dashes filled up to the value, the number at its right.
|
|
mod.widgets.SliderRoundFlat.height = 20.0
|
|
mod.widgets.SliderRoundFlat.text_input.min_height = 0.0
|
|
mod.widgets.SliderRoundFlat.text_input.padding = mod.turtle.Inset{left: 0.0 right: 6.0 top: 0.0 bottom: 0.0}
|
|
mod.widgets.SliderRoundFlat.draw_bg.vertex = fn() {
|
|
let g = lcd_grow(self.rect_pos, self.rect_size, 4.0)
|
|
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
|
|
}
|
|
mod.widgets.SliderRoundFlat.draw_bg.pixel = fn() {
|
|
let m = 4.0
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
let o = self.rect_pos
|
|
let live = 1.0 - self.disabled
|
|
let sb = lcd_snapbox(o, vec2(self.label_size, 0.0), self.rect_size, px)
|
|
let c = (sb.xy + sb.zw) * 0.5
|
|
let h = (sb.zw - sb.xy) * 0.5
|
|
let d = lcd_rbox(p, c, h, 1.5)
|
|
let inside = Finish.cover(d, px)
|
|
var out = vec4(0.0, 0.0, 0.0, 0.0)
|
|
// a window let into the glass: a shade under its top edge
|
|
let topd = clamp((p.y - sb.y) / 3.0, 0.0, 1.0)
|
|
let wcol = mix(lcd_glass() * 0.82, lcd_glass() * 0.97, topd)
|
|
out = vec4(wcol * inside, inside)
|
|
// dashes inside, leaving room for the number at the right
|
|
let lo = min(self.origin_pos, self.slide_pos) * self.arc_origin
|
|
let hi = mix(self.slide_pos, max(self.origin_pos, self.slide_pos), self.arc_origin)
|
|
let x0 = sb.x + 3.0
|
|
let x1 = sb.z - 36.0
|
|
let span = max(x1 - x0, 1.0)
|
|
let n = max(floor((span + 1.5) / 6.0), 4.0)
|
|
let pitch = (span + 1.5) / n
|
|
let k = floor((p.x - x0) / pitch)
|
|
let within = p.x - x0 - k * pitch
|
|
let in_dash = step(0.0, within) * step(within, pitch - 1.5) * step(0.0, k) * step(k, n - 1.0)
|
|
let in_row = step(c.y - 3.0, p.y) * step(p.y, c.y + 3.0)
|
|
let sc = (k + 0.5) / n
|
|
let on = step(lo, sc) * step(sc, hi)
|
|
let lcol = mix(lcd_ink(), lcd_mid(), self.disabled)
|
|
let dcol = mix(vec3(0.537, 0.561, 0.467), lcol, on)
|
|
let da = in_dash * in_row
|
|
out = lcd_over(out, vec4(dcol * da, da))
|
|
let chk = lcd_checker(p, o, px)
|
|
let hv = inside * chk * 0.0
|
|
let la = Finish.band(d, 0.0, px, px)
|
|
let fl = Finish.band(d, px, 2.0 * px, px) * max(self.hover, self.drag) * live
|
|
out = lcd_over(out, vec4(lcol * max(la, fl), max(la, fl)))
|
|
let kf = lcd_dash_ring(d, p, c, h, o, 2.0, px) * self.focus * live
|
|
out = lcd_over(out, vec4(lcd_ink() * kf, kf))
|
|
return out
|
|
}
|
|
mod.widgets.SliderRoundFlat.clip_x = false
|
|
mod.widgets.SliderRoundFlat.clip_y = false
|
|
|
|
// FADERS. A slot four points wide let into the glass: one device pixel of
|
|
// ink round a core of one-to-one ink hatching, the family's mid-tone. A
|
|
// printed ladder on ONE side of it, two points clear: ten steps, a long
|
|
// tick at 0, 50 and 100. The cap is an inverse tile 18 across and 10 along
|
|
// with a lit line across its middle, the line the value is read off; it
|
|
// throws the ink's shadow and moves over the slot.
|
|
mod.widgets.SliderFader.cap_size = 10.0
|
|
mod.widgets.SliderFader.track_inset = 6.0
|
|
mod.widgets.SliderFaderY.width = 26.0
|
|
mod.widgets.SliderFader.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let live = 1.0 - self.disabled
|
|
let along = mix(self.rect_size.x, self.rect_size.y, self.is_vertical)
|
|
let across = mix(self.rect_size.y, self.rect_size.x, self.is_vertical)
|
|
let a = mix(p.x, self.rect_size.y - p.y, self.is_vertical)
|
|
let b = mix(p.y, p.x, self.is_vertical)
|
|
let oa = mix(self.rect_pos.x, self.rect_pos.y, self.is_vertical)
|
|
let ob = mix(self.rect_pos.y, self.rect_pos.x, self.is_vertical)
|
|
let travel = max(along - self.inset_px * 2.0 - self.cap_px, 1.0)
|
|
let foot = self.inset_px + self.cap_px * 0.5
|
|
let mid = Finish.snap(ob + across * 0.5, px) - ob
|
|
let icol = mix(lcd_ink(), lcd_mid(), self.disabled)
|
|
var out = vec4(0.0, 0.0, 0.0, 0.0)
|
|
// the ladder, on the near side only (left of a column, above a row)
|
|
let pc = lcd_pix(p, self.rect_pos, px)
|
|
let ac = mix(pc.x, self.rect_size.y - pc.y, self.is_vertical)
|
|
let bc = mix(pc.y, pc.x, self.is_vertical)
|
|
let da = ac - foot
|
|
let step10 = travel / 10.0
|
|
let kk = floor(da / step10 + 0.5)
|
|
let near = abs(da - kk * step10)
|
|
let tick = step(near, px * 0.5) * step(-0.5, kk) * step(kk, 10.5)
|
|
let off = mid - bc
|
|
let major = step(fract(kk / 5.0 + 0.01), 0.1)
|
|
let tl = mix(3.0, 5.0, major)
|
|
let tk = tick * step(4.0, off) * step(off, 4.0 + tl)
|
|
out = lcd_over(out, vec4(icol * tk, tk))
|
|
// the slot: six device pixels across, square ends of half a point
|
|
let sa0 = Finish.snap(oa + self.inset_px, px) - oa
|
|
let sa1 = Finish.snap(oa + along - self.inset_px, px) - oa
|
|
let sb0 = mid - 3.0 * px
|
|
let sc = vec2((sa0 + sa1) * 0.5, sb0 + 3.0 * px)
|
|
let sh2 = vec2((sa1 - sa0) * 0.5, 3.0 * px)
|
|
let q = vec2(a, b)
|
|
let sd = lcd_rbox(q, sc, sh2, 0.5)
|
|
let sfill = Finish.cover(sd, px)
|
|
let hatch = lcd_checker(p, self.rect_pos, px)
|
|
var scol = mix(lcd_ground() * 0.92, icol, hatch * 0.85)
|
|
scol = mix(scol, icol, Finish.band(sd, 0.0, px, px))
|
|
out = lcd_over(out, vec4(scol * sfill, sfill))
|
|
// the cap
|
|
let at = foot + self.slide_pos * travel
|
|
let ca_h = Finish.snap(oa + at, px) - oa
|
|
let ca_v = along - (Finish.snap(oa + along - at, px) - oa)
|
|
let ca = mix(ca_h, ca_v, self.is_vertical)
|
|
let cc = vec2(ca, mid)
|
|
let chh = vec2(self.cap_px * 0.5, 9.0)
|
|
let cd = lcd_rbox(q, cc, chh, 1.0)
|
|
// the ink's shadow, down and a little left in screen space
|
|
let sofs = mix(vec2(-0.75, 1.0), vec2(-1.0, -0.75), self.is_vertical)
|
|
let csd = lcd_rbox(q - sofs, cc, chh, 1.0)
|
|
let ca2 = Finish.cover(cd, px)
|
|
let sh = lcd_inkshade(csd, 0.16) * (1.0 - ca2) * live
|
|
out = lcd_over(out, vec4(lcd_ink() * sh, sh))
|
|
let face = mix(mix(lcd_ink(), lcd_dark(), 0.4 * self.hover * live), lcd_mid(), self.disabled)
|
|
out = lcd_over(out, vec4(face * ca2, ca2))
|
|
// the lit line across the cap, one pixel, and a keyline while dragged
|
|
let cut = step(ca, a) * step(a, ca + px) * step(abs(b - cc.y), chh.y - 2.0 * px) * ca2
|
|
out = lcd_over(out, vec4(lcd_lit() * cut, cut))
|
|
let kl = Finish.band(cd, -3.0 * px, -2.0 * px, px) * self.drag * live
|
|
out = lcd_over(out, vec4(lcd_lit() * kl, kl))
|
|
let kf = lcd_dash_ring(cd, q, cc, chh, vec2(oa, ob), 2.0, px) * self.focus * live
|
|
out = lcd_over(out, vec4(lcd_ink() * kf, kf))
|
|
return out
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// KNOBS. The wristwatch's bezel: a dark ring printed with minute marks in
|
|
// the glass colour, lit along its upper edge, standing on the glass with a
|
|
// short shade under it. Inside, a glass dial with its own shadow under the
|
|
// rim, a ring of 24 segments lit up to the value and ghosted beyond, and a
|
|
// plain centre with an ink pointer.
|
|
let lcd_knob = fn(p: vec2, c: vec2, rad: float, o: vec2, px: float, slide: float, origin: float, arc_origin: float, gap: float, hover: float, focus: float, drag: float, disabled: float) -> vec4 {
|
|
let live = 1.0 - disabled
|
|
let v = p - c
|
|
let r = length(v)
|
|
let rin = rad * 0.78
|
|
var out = vec4(0.0, 0.0, 0.0, 0.0)
|
|
// the bezel's shade on the glass
|
|
let sr = length(v - vec2(-0.4, 1.0))
|
|
let sh = (1.0 - smoothstep(rad - 0.8, rad + 1.8, sr)) * 0.30 * (1.0 - Finish.cover(r - rad, px)) * mix(1.0, 0.4, disabled)
|
|
out = vec4(0.0, 0.0, 0.0, sh)
|
|
// a light lip where the glass meets the bezel's foot
|
|
let lip = Finish.band(-(r - rad - px), 0.0, px, px) * smoothstep(0.2, 0.8, v.y / max(r, 0.001)) * 0.7
|
|
out = lcd_over(out, vec4(lcd_g3(0.84) * lip, lip))
|
|
// the bezel ring
|
|
let ring = Finish.cover(max(r - rad, rin - r), px)
|
|
let up = clamp(-v.y / max(rad, 1.0), -1.0, 1.0)
|
|
var bcol = lcd_frame() * (1.0 + 0.25 * up) + lcd_g3(0.02 * hover * live)
|
|
// the minute marks: sixty, the fifth ones longer
|
|
var th = atan2(v.x, -v.y)
|
|
if th < 0.0 {
|
|
th = th + 2.0 * PI
|
|
}
|
|
let mi = floor(th / (PI / 30.0) + 0.5)
|
|
let mang = mi * PI / 30.0
|
|
let mdir = vec2(sin(mang), -cos(mang))
|
|
let mperp = abs(dot(v, vec2(mdir.y, -mdir.x)))
|
|
let five = step(fract(mi / 5.0 + 0.01), 0.1)
|
|
let mlen = mix(0.09, 0.16, five) * rad
|
|
let mark = Finish.band(mperp - px * mix(0.5, 0.75, five), -px, 0.0, px) * step(rad - px * 2.0 - mlen, r) * step(r, rad - px * 2.0)
|
|
let mcol = mix(vec3(0.600, 0.627, 0.529), lcd_lit(), hover * live)
|
|
bcol = mix(bcol, mcol * mix(1.0, 0.5, disabled), mark * 0.9)
|
|
// the lit line on the bezel's upper outer edge, the dark lip inside
|
|
let edge = Finish.band(-(r - rad), 0.0, px, px) * clamp(up * 1.6, 0.0, 1.0)
|
|
bcol = mix(bcol, vec3(0.380, 0.392, 0.416), edge)
|
|
let inner = Finish.band(r - rin, 0.0, px, px)
|
|
bcol = mix(bcol, lcd_g3(0.01), inner)
|
|
out = lcd_over(out, vec4(bcol * ring, ring))
|
|
// the dial glass, shaded under the rim at the top
|
|
let dial = Finish.cover(r - rin, px)
|
|
let shade = (1.0 - smoothstep(0.0, rin * 0.28, dot(v, vec2(0.0, 1.0)) + rin)) * 0.45
|
|
var gcol = lcd_wglass() * (1.0 - shade)
|
|
gcol = gcol + lcd_g3(Finish.grain((p + o) / px, 0.018))
|
|
// the ring of segments
|
|
let start = gap * 0.5 * PI / 180.0
|
|
let sweep = 2.0 * PI - gap * PI / 180.0
|
|
var ta = atan2(-v.x, v.y)
|
|
if ta < 0.0 {
|
|
ta = ta + 2.0 * PI
|
|
}
|
|
let t = (ta - start) / sweep
|
|
let s0 = rin * 0.70
|
|
let s1 = rin * 0.90
|
|
let smid = (s0 + s1) * 0.5
|
|
// segments about three and a half points long, 12 to 24 of them
|
|
let n = clamp(floor(sweep * smid / 3.6), 12.0, 24.0)
|
|
let seg = floor(t * n)
|
|
let seglen = sweep * smid / n
|
|
let f = fract(t * n) * seglen
|
|
let in_seg = step(f, seglen - 1.0) * step(0.0, t) * step(t, 1.0)
|
|
let band = Finish.cover(max(s0 - r, r - s1), px) * in_seg
|
|
let lo = min(origin, slide) * arc_origin
|
|
let hi = mix(slide, max(origin, slide), arc_origin)
|
|
let sc = (seg + 0.5) / n
|
|
let lit = step(lo, sc) * step(sc, hi + 0.5 / n - 0.0001)
|
|
let icol = mix(lcd_ink(), lcd_mid(), disabled)
|
|
let segc = mix(lcd_wglass() * 0.86, icol, lit)
|
|
gcol = mix(gcol, segc, band)
|
|
// the pointer: a short ink bar in the plain centre, a tenth to four
|
|
// tenths of the radius out, well inside the ring of segments
|
|
let av = start + sweep * slide
|
|
let dir = vec2(-sin(av), cos(av))
|
|
let al = clamp(dot(v, dir), rad * 0.10, rad * 0.40)
|
|
let pw = rad * 0.06 * mix(1.0, 1.25, drag * live)
|
|
let pd = length(v - dir * al) - pw
|
|
let pa = Finish.cover(pd, px)
|
|
let psd = length(v - lcd_throw() - dir * clamp(dot(v - lcd_throw(), dir), rad * 0.10, rad * 0.40)) - pw
|
|
gcol = mix(gcol, gcol * 0.86, lcd_inkshade(psd, 1.0) * (1.0 - pa))
|
|
gcol = mix(gcol, icol, pa)
|
|
out = lcd_over(out, vec4(gcol * dial, dial))
|
|
// focus: a dashed ring two points out
|
|
let kr = Finish.band(abs(r - rad - 2.0 - px * 0.5) - px * 0.5, -px, 0.0, px)
|
|
let kd = step(fract(floor(th * rad / px / 2.0 + 0.01) * 0.5), 0.3)
|
|
let kf = kr * kd * focus * live
|
|
out = lcd_over(out, vec4(lcd_ink() * kf, kf))
|
|
return out
|
|
}
|
|
|
|
mod.widgets.RotaryKnob.draw_bg.vertex = fn() {
|
|
let g = lcd_grow(self.rect_pos, self.rect_size, 4.0)
|
|
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
|
|
}
|
|
mod.widgets.RotaryKnob.draw_bg.pixel = fn() {
|
|
let m = 4.0
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
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 rad = dia * 0.5 - 1.5
|
|
let c = vec2(Finish.snap(self.rect_pos.x + self.rect_size.x * 0.5, px), Finish.snap(self.rect_pos.y + self.rect_size.y - dia * 0.5, px)) - self.rect_pos
|
|
return lcd_knob(p, c, rad, self.rect_pos, px, self.slide_pos, self.origin_pos, self.arc_origin, self.gap, self.hover, self.focus, self.drag, self.disabled)
|
|
}
|
|
mod.widgets.RotaryKnob.clip_x = false
|
|
mod.widgets.RotaryKnob.clip_y = false
|
|
mod.widgets.RotaryFlat.draw_bg.vertex = fn() {
|
|
let g = lcd_grow(self.rect_pos, self.rect_size, 4.0)
|
|
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
|
|
}
|
|
mod.widgets.RotaryFlat.draw_bg.pixel = fn() {
|
|
let m = 4.0
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
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 - 24.0)
|
|
let rad = dia * 0.5 - 2.0
|
|
let c = vec2(Finish.snap(self.rect_pos.x + self.rect_size.x * 0.5, px), Finish.snap(self.rect_pos.y + 24.0 + dia * 0.5, px)) - self.rect_pos
|
|
return lcd_knob(p, c, rad, self.rect_pos, px, self.slide_pos, self.origin_pos, self.arc_origin, self.gap, self.hover, self.focus, self.drag, self.disabled)
|
|
}
|
|
mod.widgets.RotaryFlat.clip_x = false
|
|
mod.widgets.RotaryFlat.clip_y = false
|
|
mod.widgets.RotaryFlat.text_input.padding = mod.turtle.Inset{left: 4.0 right: 4.0 top: 1.0 bottom: 1.0}
|
|
mod.widgets.RotaryFlat.text_input.min_height = 0.0
|
|
mod.widgets.RotaryFlat.text_input.draw_text.text_style = lcd_value_style
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// FIELDS. A window let into the glass: one device pixel of ink round a
|
|
// glass a shade darker, a shadow under its top edge, and a light lip under
|
|
// its foot where the cut catches the light. The pointer lays a sparse
|
|
// dither; focus doubles the line. A field a host has made invisible (the
|
|
// value inside a slider) stays invisible.
|
|
let lcd_well = fn(p: vec2, sb: vec4, o: vec2, px: float, hover: float, focus: float, disabled: float) -> vec4 {
|
|
let live = 1.0 - disabled
|
|
let c = (sb.xy + sb.zw) * 0.5
|
|
let h = (sb.zw - sb.xy) * 0.5
|
|
let d = lcd_rbox(p, c, h, 1.5)
|
|
let inside = Finish.cover(d, px)
|
|
var out = vec4(0.0, 0.0, 0.0, 0.0)
|
|
// the lip under the foot
|
|
let lip = Finish.ring_out(d, 0.0, px, px) * step(c.y + h.y - 1.0, p.y) * 0.75
|
|
out = vec4(lcd_g3(0.80) * lip, lip)
|
|
// the glass: shaded under the top edge over four points, ease-out
|
|
let t = clamp((p.y - sb.y) / 4.0, 0.0, 1.0)
|
|
let fall = 1.0 - (1.0 - t) * (1.0 - t)
|
|
var col = mix(lcd_ground() * 0.66, lcd_ground() * 0.955, fall)
|
|
col = mix(col, lcd_ground() * 0.985, disabled)
|
|
col = col + lcd_g3(Finish.grain((p + o) / px, 0.016))
|
|
out = lcd_over(out, vec4(col * inside, inside))
|
|
// under the pointer: one pixel in four of the light middle
|
|
let qa = lcd_quarter(p, o, px) * inside * hover * live * 0.8
|
|
out = lcd_over(out, vec4(lcd_mid() * qa, qa))
|
|
// the line; focus doubles it inward
|
|
let lcol = mix(lcd_ink(), lcd_mid(), disabled)
|
|
let la = max(Finish.band(d, 0.0, px, px), Finish.band(d, px, 2.0 * px, px) * focus * live)
|
|
out = lcd_over(out, vec4(lcol * la, la))
|
|
return out
|
|
}
|
|
|
|
mod.widgets.TextInputFlat.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let sb = lcd_snapbox(self.rect_pos, vec2(0.0, 0.0), self.rect_size - vec2(0.0, px), px)
|
|
let vis = step(0.001, self.border_size)
|
|
return lcd_well(p, sb, self.rect_pos, px, self.hover, self.focus, self.disabled) * vis
|
|
}
|
|
mod.widgets.TextInputFlat.draw_text.color_empty = #5d624d
|
|
mod.widgets.TextInputFlat.draw_text.color_empty_hover = #535843
|
|
mod.widgets.TextInputFlat.draw_text.color_empty_focus = #5d624d
|
|
// The caret is an ink block a point and a half wide.
|
|
mod.widgets.TextInputFlat.draw_cursor.pixel = fn() {
|
|
let a = (1.0 - self.blink) * self.focus * (1.0 - self.disabled)
|
|
return vec4(vec3(0.161, 0.169, 0.125) * a, a)
|
|
}
|
|
mod.widgets.TextInputFlat.draw_selection.border_radius = 0.0
|
|
|
|
// The number field: the same window, its stepper cut off at the right by
|
|
// a one-pixel rule, two cells with pixel arrows; a cell lays the dither
|
|
// under the pointer and inverts while pressed.
|
|
mod.widgets.NumberField.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let sb = lcd_snapbox(self.rect_pos, vec2(0.0, 0.0), self.rect_size - vec2(0.0, px), px)
|
|
return lcd_well(p, sb, self.rect_pos, px, self.hover, self.focus, self.disabled)
|
|
}
|
|
mod.widgets.NumberField.pad_left = 8.0
|
|
mod.widgets.NumberSpin.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let o = self.rect_pos
|
|
let w = self.rect_size.x
|
|
let hgt = self.rect_size.y
|
|
let mid = Finish.snap(o.y + hgt * 0.5, px) - o.y
|
|
let live = 1.0 - self.disabled
|
|
let upper = step(p.y, mid)
|
|
let hot = mix(max(0.0, -self.hot), max(0.0, self.hot), upper) * live
|
|
let dn = mix(max(0.0, -self.down), max(0.0, self.down), upper) * live
|
|
let inx = step(px, p.x) * step(p.x, w - px)
|
|
let iny = step(1.5 * px, p.y) * step(p.y, hgt - 1.5 * px)
|
|
let cell = inx * iny
|
|
var out = vec4(0.0, 0.0, 0.0, 0.0)
|
|
let chk = lcd_checker(p, o, px)
|
|
let ha = cell * chk * hot * (1.0 - dn)
|
|
out = lcd_over(out, vec4(lcd_mid() * ha, ha))
|
|
let ia = cell * dn
|
|
out = lcd_over(out, vec4(lcd_ink() * ia, ia))
|
|
// the rules: down the left edge and across the middle
|
|
let rl = step(p.x, px) * step(px, p.y) * step(p.y, hgt - px)
|
|
let rm = step(mid - px * 0.5, p.y) * step(p.y, mid + px * 0.5) * step(px, p.x)
|
|
let icol = mix(lcd_ink(), lcd_mid(), self.disabled)
|
|
let ra = max(rl, rm)
|
|
out = lcd_over(out, vec4(icol * ra, ra))
|
|
// the arrows: stepped triangles, 5 pixels across
|
|
let cx = Finish.snap(o.x + w * 0.5, px) - o.x
|
|
let q = floor((p + o) / px + vec2(0.001, 0.001)) - floor((vec2(cx, 0.0) + o) / px + vec2(0.5, 0.5))
|
|
let yu = floor((o.y + mid * 0.5) / px) - floor(o.y / px + 0.5)
|
|
let yd = floor((o.y + mid + (hgt - mid) * 0.5) / px) - floor(o.y / px + 0.5)
|
|
let qy = floor((p.y + o.y) / px + 0.001) - floor(o.y / px + 0.5)
|
|
let ru = qy - (yu - 1.0)
|
|
let rd = (yd + 1.0) - qy
|
|
let au = step(0.0, ru) * step(ru, 2.5) * step(abs(q.x + 0.5), ru + 0.6)
|
|
let ad = step(0.0, rd) * step(rd, 2.5) * step(abs(q.x + 0.5), rd + 0.6)
|
|
let ucol = mix(icol, lcd_lit(), max(0.0, self.down) * live)
|
|
let dcol = mix(icol, lcd_lit(), max(0.0, -self.down) * live)
|
|
out = lcd_over(out, vec4(ucol * au, au))
|
|
out = lcd_over(out, vec4(dcol * ad, ad))
|
|
return out
|
|
}
|
|
// The printed arrow glyphs are hidden; the face draws the arrows.
|
|
mod.widgets.NumberSpin.draw_text.color = #292b2000
|
|
mod.widgets.NumberSpin.draw_text.color_disabled = #292b2000
|
|
|
|
// DROP-DOWNS. The window, with a cell 18 points wide at its right split
|
|
// off by a one-pixel rule and a stepped ink triangle in it; open or held,
|
|
// the cell inverts.
|
|
let lcd_choice = fn(p: vec2, sb: vec4, o: vec2, px: float, hover: float, open: float, focus: float, disabled: float) -> vec4 {
|
|
let live = 1.0 - disabled
|
|
var out = lcd_well(p, sb, o, px, 0.0, focus, disabled)
|
|
let c = (sb.xy + sb.zw) * 0.5
|
|
let h = (sb.zw - sb.xy) * 0.5
|
|
let rx = Finish.snap(o.x + sb.z - 18.0, px) - o.x
|
|
let incell = step(rx + px, p.x) * step(p.x, sb.z - px) * step(sb.y + px, p.y) * step(p.y, sb.w - px)
|
|
let chk = lcd_checker(p, o, px)
|
|
let ha = incell * chk * hover * live * (1.0 - open)
|
|
out = lcd_over(out, vec4(lcd_mid() * ha, ha))
|
|
let ia = incell * open * live
|
|
out = lcd_over(out, vec4(lcd_ink() * ia, ia))
|
|
let icol = mix(lcd_ink(), lcd_mid(), disabled)
|
|
let rule = step(rx, p.x) * step(p.x, rx + px) * step(sb.y, p.y) * step(p.y, sb.w)
|
|
out = lcd_over(out, vec4(icol * rule, rule))
|
|
// the triangle: 7 pixels across, 4 down, stepped
|
|
let tx = Finish.snap(o.x + rx + 9.0, px) - o.x
|
|
let q = floor((p + o) / px + vec2(0.001, 0.001)) - floor((vec2(tx, c.y - 1.5) + o) / px + vec2(0.5, 0.5))
|
|
let tr = 3.0 - q.y
|
|
let ta = step(0.0, q.y) * step(q.y, 3.5) * step(abs(q.x + 0.5), tr + 0.6)
|
|
let tcol = mix(icol, lcd_lit(), open * live)
|
|
out = lcd_over(out, vec4(tcol * ta, ta))
|
|
// focus also shows as the dashed ring outside
|
|
let d = lcd_rbox(p, c, h, 1.5)
|
|
return out
|
|
}
|
|
mod.widgets.DropDownFlat.padding = mod.turtle.Inset{left: 8.0 right: 26.0 top: 2.5 bottom: 2.5}
|
|
mod.widgets.DropDownFlat.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let sb = lcd_snapbox(self.rect_pos, vec2(0.0, 0.0), self.rect_size - vec2(0.0, px), px)
|
|
return lcd_choice(p, sb, self.rect_pos, px, self.hover, max(self.active, self.down * self.hover), self.focus, self.disabled)
|
|
}
|
|
mod.widgets.DropDownFlat.draw_text.color = #292b20
|
|
mod.widgets.DropDownFlat.draw_text.color_hover = #292b20
|
|
mod.widgets.DropDownFlat.draw_text.color_focus = #292b20
|
|
mod.widgets.DropDownFlat.draw_text.color_down = #292b20
|
|
mod.widgets.DropDownFlat.draw_text.color_disabled = #6c725c
|
|
|
|
// The select wears a button built before this sheet: it is given the
|
|
// drop-down's window and cell here, by its own path.
|
|
mod.widgets.Select.face.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let sb = lcd_snapbox(self.rect_pos, vec2(0.0, 0.0), self.rect_size - vec2(0.0, px), px)
|
|
return lcd_choice(p, sb, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled)
|
|
}
|
|
mod.widgets.MultiSelect.face.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let sb = lcd_snapbox(self.rect_pos, vec2(0.0, 0.0), self.rect_size - vec2(0.0, px), px)
|
|
return lcd_choice(p, sb, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled)
|
|
}
|
|
mod.widgets.Select.face.padding = mod.turtle.Inset{left: 8.0 right: 26.0 top: 2.5 bottom: 2.5}
|
|
mod.widgets.MultiSelect.face.padding = mod.turtle.Inset{left: 8.0 right: 26.0 top: 2.5 bottom: 2.5}
|
|
mod.widgets.Select.face.min_height = 24.0
|
|
mod.widgets.MultiSelect.face.min_height = 24.0
|
|
mod.widgets.Select.face.draw_text.color = #292b20
|
|
mod.widgets.Select.face.draw_text.color_hover = #292b20
|
|
mod.widgets.Select.face.draw_text.color_down = #292b20
|
|
mod.widgets.Select.face.draw_text.color_focus = #292b20
|
|
mod.widgets.MultiSelect.face.draw_text.color = #292b20
|
|
mod.widgets.MultiSelect.face.draw_text.color_hover = #292b20
|
|
mod.widgets.MultiSelect.face.draw_text.color_down = #292b20
|
|
mod.widgets.MultiSelect.face.draw_text.color_focus = #292b20
|
|
|
|
// The combo box: the same window and cell; its open list inverts the
|
|
// chosen row.
|
|
mod.widgets.ComboBoxFlat.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let sb = lcd_snapbox(self.rect_pos, vec2(0.0, 0.0), self.rect_size - vec2(0.0, px), px)
|
|
return lcd_choice(p, sb, self.rect_pos, px, self.hover, self.down * self.hover, self.focus, self.disabled)
|
|
}
|
|
mod.widgets.ComboBoxFlat.padding = mod.turtle.Inset{left: 8.0 right: 26.0 top: 2.5 bottom: 2.5}
|
|
mod.widgets.ComboBoxFlat.draw_popup_bg.color = #a8af94
|
|
mod.widgets.ComboBoxFlat.draw_popup_bg.border_color = #292b20
|
|
mod.widgets.ComboBoxFlat.draw_popup_bg.border_radius = 0.0
|
|
mod.widgets.ComboBoxFlat.draw_item.color_hover = #7a806533
|
|
mod.widgets.ComboBoxFlat.draw_item.color_active = #292b20
|
|
mod.widgets.ComboBoxFlat.draw_item_text.color_active = #b6bda2
|
|
mod.widgets.ComboBoxFlat.item_height = 20.0
|
|
// the input inside the box takes no row of its own
|
|
mod.widgets.ComboBoxFlat.input.min_height = 0.0
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// TABS. Caption cells between two one-pixel rules; the tab in use is the
|
|
// inverse caption tab, dark with lit glass letters; the pointer lays the
|
|
// light dither.
|
|
mod.widgets.Tabs.height = 22.0
|
|
mod.widgets.Tabs.draw_bg.color = #00000000
|
|
mod.widgets.Tabs.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let yt = Finish.snap(self.rect_pos.y, px) - self.rect_pos.y
|
|
let yb = Finish.snap(self.rect_pos.y + self.rect_size.y, px) - self.rect_pos.y
|
|
let la = max(step(yb - px, p.y) * step(p.y, yb), step(yt, p.y) * step(p.y, yt + px))
|
|
return vec4(lcd_ink() * la, la)
|
|
}
|
|
let lcd_tab_face = fn(p: vec2, size: vec2, o: vec2, px: float, fill: float, dither: float) -> vec4 {
|
|
let sb = lcd_snapbox(o, vec2(0.0, 0.0), size + vec2(px, 0.0), px)
|
|
let inx = step(sb.x, p.x) * step(p.x, sb.z)
|
|
let iny = step(sb.y, p.y) * step(p.y, sb.w)
|
|
let inside = inx * iny
|
|
var out = vec4(0.0, 0.0, 0.0, 0.0)
|
|
let chk = lcd_checker(p, o, px)
|
|
let da = inside * chk * dither
|
|
out = lcd_over(out, vec4(lcd_mid() * da, da))
|
|
let fa = inside * fill
|
|
out = lcd_over(out, vec4(lcd_ink() * fa, fa))
|
|
// the cell's dividers; the strip draws the rules above and below
|
|
let side = max(step(p.x, sb.x + px), step(sb.z - px, p.x)) * iny
|
|
let la = side * inside
|
|
out = lcd_over(out, vec4(lcd_ink() * la, la))
|
|
return out
|
|
}
|
|
mod.widgets.Tabs.draw_tab.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
return lcd_tab_face(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, 0.0, 0.0)
|
|
}
|
|
mod.widgets.Tabs.draw_tab_hover.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
return lcd_tab_face(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, 0.0, 1.0)
|
|
}
|
|
mod.widgets.Tabs.draw_tab_active.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
return lcd_tab_face(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, 1.0, 0.0)
|
|
}
|
|
mod.widgets.Tabs.draw_sep.color = #00000000
|
|
mod.widgets.Tabs.draw_mark.color = #7a8065
|
|
// The close mark is a stepped x, the add mark a one-pixel plus.
|
|
mod.widgets.Tabs.draw_mark.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let o = self.rect_pos
|
|
let is_plus = step(0.5, self.plus)
|
|
let cx0 = mix(self.rect_size.x - 5.5, self.rect_size.x * 0.5, is_plus)
|
|
let c = vec2(Finish.snap(o.x + cx0, px), Finish.snap(o.y + self.rect_size.y * 0.5, px)) - o
|
|
let q = floor((p - c) / px + vec2(0.001, 0.001))
|
|
let r = mix(3.0, 5.0, is_plus)
|
|
let inbox = step(abs(q.x + 0.5), r + 0.5) * step(abs(q.y + 0.5), r + 0.5)
|
|
let cross = max(step(abs(q.x - q.y), 0.5), step(abs(q.x + q.y + 1.0), 0.5)) * inbox
|
|
let plus = max(step(abs(q.x), 0.5), step(abs(q.y), 0.5)) * inbox
|
|
let a = mix(cross, plus, is_plus) * self.color.a
|
|
return vec4(self.color.rgb * a, a)
|
|
}
|
|
mod.widgets.Tabs.draw_text.color = #292b20
|
|
mod.widgets.Tabs.draw_text_active.color = #b6bda2
|
|
mod.widgets.Tabs.draw_text.ink_centered = true
|
|
mod.widgets.Tabs.draw_text_active.ink_centered = true
|
|
let lcd_tab_type = mod.theme.font_label{font_size: 7.5 line_spacing: 1.0}
|
|
mod.widgets.Tabs.draw_text.text_style = lcd_tab_type
|
|
mod.widgets.Tabs.draw_text_active.text_style = lcd_tab_type
|
|
|
|
// The segmented group: one frame round the row, the chosen cell inverse.
|
|
mod.widgets.SegmentedControlFlat.segment_height = 22.0
|
|
mod.widgets.SegmentedControlFlat.segment_padding = 9.0
|
|
mod.widgets.SegmentedControlFlat.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let sb = lcd_snapbox(self.rect_pos, vec2(0.0, 0.0), self.rect_size, px)
|
|
let d = lcd_rbox(p, (sb.xy + sb.zw) * 0.5, (sb.zw - sb.xy) * 0.5, 0.01)
|
|
let la = Finish.band(d, 0.0, px, px)
|
|
return vec4(lcd_ink() * la, la)
|
|
}
|
|
mod.widgets.SegmentedControlFlat.draw_pill.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let sb = lcd_snapbox(self.rect_pos, vec2(0.0, 0.0), self.rect_size, px)
|
|
let d = lcd_rbox(p, (sb.xy + sb.zw) * 0.5, (sb.zw - sb.xy) * 0.5, 0.01)
|
|
let a = Finish.cover(d, px)
|
|
return vec4(lcd_ink() * a, a)
|
|
}
|
|
mod.widgets.SegmentedControlFlat.draw_text.color = #292b20
|
|
mod.widgets.SegmentedControlFlat.draw_text_selected.color = #b6bda2
|
|
mod.widgets.SegmentedControlFlat.draw_text.text_style = mod.theme.font_label{}
|
|
mod.widgets.SegmentedControlFlat.draw_text_selected.text_style = mod.theme.font_label{}
|
|
mod.widgets.SegmentedControlFlat.palette.on_neutral_container = #b6bda2
|
|
|
|
// The dock's tabs: the tab in use is a caption cell with a two-pixel ink
|
|
// bar along its top; the others lay the dither under the pointer.
|
|
mod.widgets.Tab.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let o = self.rect_pos
|
|
let sb = lcd_snapbox(o, vec2(0.0, 0.0), self.rect_size, px)
|
|
let chk = lcd_checker(p, o, px)
|
|
var out = vec4(0.0, 0.0, 0.0, 0.0)
|
|
let da = chk * self.hover * (1.0 - self.active)
|
|
out = lcd_over(out, vec4(lcd_mid() * da, da))
|
|
let bar = step(p.y, sb.y + 2.0 * px) * self.active
|
|
let side = max(step(p.x, sb.x + px), step(sb.z - px, p.x)) * self.active
|
|
let la = max(bar, side)
|
|
out = lcd_over(out, vec4(lcd_ink() * la, la))
|
|
return out
|
|
}
|
|
mod.widgets.TabBar.draw_fill.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let yb = Finish.snap(self.rect_pos.y + self.rect_size.y, px) - self.rect_pos.y
|
|
let la = step(yb - px, p.y) * step(p.y, yb)
|
|
return vec4(lcd_ink() * la, la)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// PROGRESS. The gauge bar of the wristwatch: a pill outlined in one device
|
|
// pixel of ink and, a device pixel of glass in from the line, the value's
|
|
// run as a solid ink pill of its own, from the start and never shorter
|
|
// than it is tall. Only the piece that carries the track draws the line,
|
|
// so stacked sections stand as pills inside one. The amount that is not
|
|
// known cannot move without a clock, so it stands still as a checker
|
|
// dither of ink over the whole inside. The quad is grown 4 points for the
|
|
// ink shadow.
|
|
mod.widgets.ProgressBarFlat.height = 8.0
|
|
mod.widgets.ProgressBarFlat.draw_bg.thickness = uniform(6.0)
|
|
mod.widgets.ProgressBarFlat.draw_bg.border_radius = uniform(0.0)
|
|
mod.widgets.ProgressBarFlat.draw_bg.vertex = fn() {
|
|
let g = lcd_grow(self.rect_pos, self.rect_size, 4.0)
|
|
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
|
|
}
|
|
mod.widgets.ProgressBarFlat.draw_bg.pixel = fn() {
|
|
let m = 4.0
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
let o = self.rect_pos
|
|
// the pill, start to end, the thickness tall and centred in the walk;
|
|
// its line one device pixel of ink
|
|
let hgt = min(self.thickness, self.rect_size.y)
|
|
let y0 = (self.rect_size.y - hgt) * 0.5
|
|
let sb = lcd_snapbox(o, vec2(self.rect_size.x * self.start, y0), vec2(self.rect_size.x * self.end, y0 + hgt), px)
|
|
let c = (sb.xy + sb.zw) * 0.5
|
|
let h = (sb.zw - sb.xy) * 0.5
|
|
let r = min(h.x, h.y)
|
|
let d = lcd_rbox(p, c, h, r)
|
|
let inside = Finish.cover(d, px)
|
|
let la = Finish.band(d, 0.0, px, px) * self.track
|
|
// the inside, two device pixels in: past the line, a pixel of glass
|
|
let hf = max(h.y - 2.0 * px, min(px, h.y))
|
|
let xi0 = sb.x + 2.0 * px
|
|
let xi1 = max(sb.z - 2.0 * px, xi0)
|
|
let th = lcd_throw()
|
|
var fa = 0.0
|
|
var shf = 0.0
|
|
if self.value >= 0.0 {
|
|
// the value's run, its ends the line's inset with it; never
|
|
// shorter than it is tall, so the least value is a dot
|
|
let v = clamp((self.value - self.start) / max(self.end - self.start, 0.0001), 0.0, 1.0)
|
|
let xv = Finish.snap(o.x + mix(xi0, xi1, v), px) - o.x
|
|
let xe = min(max(xv, xi0 + 2.0 * hf), xi1)
|
|
let fh = vec2((xe - xi0) * 0.5, hf)
|
|
let fc = vec2(xi0 + fh.x, c.y)
|
|
let rf = min(fh.x, hf)
|
|
let on = step(0.0001, v) * step(px, xe - xi0)
|
|
fa = Finish.cover(lcd_rbox(p, fc, fh, rf), px) * on
|
|
shf = lcd_inkshade(lcd_rbox(p - th, fc, fh, rf), 0.14) * on * (1.0 - fa)
|
|
} else {
|
|
// not known: the checker dither over the whole inside
|
|
let kh = vec2((xi1 - xi0) * 0.5, hf)
|
|
let kd = lcd_rbox(p, vec2(xi0 + kh.x, c.y), kh, min(kh.x, hf))
|
|
fa = Finish.cover(kd, px) * lcd_checker(p, o, px)
|
|
}
|
|
// the ink's shadow on the reflector: the line's falls outside the
|
|
// pill, the bar's on the glass round it too
|
|
let ds = lcd_rbox(p - th, c, h, r)
|
|
let shl = lcd_inkshade(abs(ds + px * 0.5) - px * 0.5, 0.14) * self.track * (1.0 - inside)
|
|
let sh = max(shl, shf)
|
|
var out = vec4(lcd_ink() * sh, sh)
|
|
out = lcd_over(out, vec4(lcd_ink() * la, la))
|
|
out = lcd_over(out, vec4(lcd_ink() * fa, fa))
|
|
return out * self.opacity
|
|
}
|
|
mod.widgets.ProgressBarFlat.clip_x = false
|
|
mod.widgets.ProgressBarFlat.clip_y = false
|
|
|
|
// LEVEL METERS. Rows of segments, lit ink to the level and ghosts beyond,
|
|
// the full height of the meter less a device pixel either side; the pitch
|
|
// is the meter's own `segment` (zero: one solid bar, ink to the level and
|
|
// ghost beyond) and `segment_gap` the share of it left clear. The held peak
|
|
// is one segment framed in ink; the over flag a small square at the end,
|
|
// filled when the level has gone over, at ghost when not.
|
|
mod.widgets.LevelMeter.height = 8.0
|
|
mod.widgets.LevelMeterColumn.width = 8.0
|
|
mod.widgets.LevelMeter.draw_bg.lamp_size = uniform(6.0)
|
|
mod.widgets.LevelMeter.draw_bg.lamp_gap = uniform(3.0)
|
|
mod.widgets.LevelMeter.draw_bg.segment = uniform(5.0)
|
|
mod.widgets.LevelMeter.draw_bg.segment_gap = uniform(0.3)
|
|
mod.widgets.LevelMeter.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let along = mix(self.rect_size.x, self.rect_size.y, self.vertical)
|
|
let across = mix(self.rect_size.y, self.rect_size.x, self.vertical)
|
|
let s = mix(self.pos.x * self.rect_size.x, (1.0 - self.pos.y) * self.rect_size.y, self.vertical)
|
|
let q = mix(self.pos.y * self.rect_size.y, self.pos.x * self.rect_size.x, self.vertical)
|
|
let lamp_room = self.lamp * (self.lamp_size + self.lamp_gap)
|
|
let bar = max(4.0, along - lamp_room)
|
|
let solid = step(self.segment, 0.49)
|
|
let pitch = max(self.segment, 1.0)
|
|
let clear = mix(clamp(pitch * self.segment_gap, px, pitch - px), 0.0, solid)
|
|
let n = mix(max(floor((bar + clear) / pitch), 1.0), 1.0, solid)
|
|
let k = mix(floor(s / pitch), 0.0, solid)
|
|
let within = s - k * pitch
|
|
let in_cell = mix(step(within, pitch - clear) * step(k, n - 1.0), step(s, bar), solid) * step(0.0, s)
|
|
// the full height, one device pixel in from either side
|
|
let cw = max(across - 2.0 * px, px)
|
|
let qa = px
|
|
let in_q = step(qa, q) * step(q, qa + cw)
|
|
let share = (k + 0.5) / max(n, 1.0)
|
|
let lit_seg = step(share, self.level + 0.5 / max(n, 1.0) - 0.0001) * step(0.0001, self.level)
|
|
let lit = mix(lit_seg, step(s, self.level * bar) * step(0.0001, self.level), solid)
|
|
let col = mix(lcd_ghost(), lcd_ink(), lit)
|
|
let a = in_cell * in_q
|
|
var out = vec4(col * a, a)
|
|
// the held peak: its segment framed, or on a solid bar a one-point rule
|
|
if self.hold > 0.002 {
|
|
let hk = floor(self.hold * n - 0.0001)
|
|
let e = step(abs(k - hk), 0.5) * in_q * step(k, n - 1.0) * (1.0 - solid)
|
|
let fr = e * (1.0 - step(px, within) * step(within, pitch - clear - px) * step(qa + px, q) * step(q, qa + cw - px)) * step(within, pitch - clear)
|
|
let hs = step(abs(s - self.hold * bar), 0.5) * in_q * solid
|
|
let ha = max(fr, hs)
|
|
out = lcd_over(out, vec4(lcd_ink() * ha, ha))
|
|
}
|
|
if self.lamp > 0.5 {
|
|
let lc = vec2(bar + self.lamp_gap + self.lamp_size * 0.5, across * 0.5)
|
|
let dq = abs(vec2(s, q) - lc) - vec2(self.lamp_size * 0.5, min(self.lamp_size * 0.5, across * 0.5 - px))
|
|
let ld = max(dq.x, dq.y)
|
|
let on = step(0.5, self.over)
|
|
let la = Finish.cover(ld, px)
|
|
let inner = Finish.cover(ld + px * 1.5, px)
|
|
let fcol = mix(lcd_ghost(), lcd_ink(), on)
|
|
out = lcd_over(out, vec4(lcd_ink() * la, la))
|
|
let ia = inner * (1.0 - on)
|
|
out = lcd_over(out, vec4(fcol * ia, ia))
|
|
}
|
|
return out * self.opacity
|
|
}
|
|
|
|
// SCROLL BARS. Eight points: the trough a one-pixel ink rule on its inner
|
|
// side; the thumb an ink block 1.5 points in, a checker dither at rest,
|
|
// solid under the pointer and while dragged.
|
|
mod.widgets.ScrollBar.bar_size = 8.0
|
|
mod.widgets.ScrollBar.bar_side_margin = 0.0
|
|
mod.widgets.ScrollBar.draw_bg.size = 8.0
|
|
mod.widgets.ScrollBar.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let o = self.rect_pos
|
|
let len = mix(self.rect_size.x, self.rect_size.y, self.is_vertical)
|
|
let t = mix(p.x, p.y, self.is_vertical)
|
|
let b = mix(p.y, p.x, self.is_vertical)
|
|
let across = mix(self.rect_size.y, self.rect_size.x, self.is_vertical)
|
|
let rule = step(b, px)
|
|
var out = vec4(lcd_ink() * rule, rule)
|
|
let t0 = len * self.norm_scroll + 1.5
|
|
let t1 = len * (self.norm_scroll + self.norm_handle) - 1.5
|
|
let on = step(t0, t) * step(t, t1) * step(1.5 + px, b) * step(b, across - 1.5)
|
|
let chk = lcd_checker(p, o, px)
|
|
let solid = max(self.hover, self.drag)
|
|
let ta = on * mix(chk, 1.0, solid)
|
|
out = lcd_over(out, vec4(lcd_ink() * ta, ta))
|
|
return out * self.opacity
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// WINDOWS. A group is a glass window set in the black plate the way the
|
|
// wristwatch sets each of its readouts: eight-sided, its corners cut by
|
|
// `cut` points (none rounds them instead), on a bezel `fw` points wide
|
|
// of which `fout` stand outside the box. The bezel is a grey lip one
|
|
// device pixel wide round its outer edge, lit along the top and a shade
|
|
// darker down the sides, a near-black band, and a black edge where it
|
|
// meets the glass; it stands a little proud, so a short shade falls round
|
|
// it where it sits on glass. The glass inside is a step lighter than the
|
|
// page and sits under the bezel on every side: an inner shadow all round,
|
|
// a little deeper under the top edge, then the sheen, the grain and the
|
|
// pixel matrix. An inset window is the same in a thinner bezel.
|
|
let lcd_window = fn(p: vec2, size: vec2, o: vec2, px: float, cut: float, fw: float, fout: float, sunken: float) -> vec4 {
|
|
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o
|
|
let hi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y, px)) - o
|
|
let hb = (hi - lo) * 0.5
|
|
let c = lo + hb
|
|
let fin = Finish.snap(fw - fout, px)
|
|
let fo = Finish.snap(fout, px)
|
|
let h = hb - vec2(fin, fin)
|
|
let hf = hb + vec2(fo, fo)
|
|
let wf = fin + fo
|
|
let rg = 2.0
|
|
let corner = step(0.01, cut)
|
|
let kg = max(cut, 0.01)
|
|
// the glass and the bezel's outer edge: cut at the corners, or rounded
|
|
let dg = mix(lcd_rbox(p, c, h, rg), Finish.sd_chamfer(p, c, h, kg), corner)
|
|
let df = mix(lcd_rbox(p, c, hf, rg + wf), Finish.sd_chamfer(p, c, hf, kg + wf * 0.4142), corner)
|
|
var out = vec4(0.0, 0.0, 0.0, 0.0)
|
|
// the bezel's shade on the page round it (lost on the black plate)
|
|
let ps = p - vec2(-0.5, 1.5)
|
|
let dsh = mix(lcd_rbox(ps, c, hf, rg + wf), Finish.sd_chamfer(ps, c, hf, kg + wf * 0.4142), corner)
|
|
let fcov = Finish.cover(df, px)
|
|
let sh = (1.0 - smoothstep(-1.5, 3.0, dsh)) * 0.26 * (1.0 - fcov)
|
|
out = vec4(0.0, 0.0, 0.0, sh)
|
|
// the bezel: a face falling from a dark grey at the top to near black
|
|
let fa = fcov * (1.0 - Finish.cover(dg, px))
|
|
let ft = clamp((p.y - (c.y - hf.y)) / max(2.0 * hf.y, 1.0), 0.0, 1.0)
|
|
var fcol = mix(vec3(0.149, 0.153, 0.173), vec3(0.110, 0.114, 0.129), smoothstep(0.0, 0.35, ft))
|
|
fcol = mix(fcol, vec3(0.122, 0.122, 0.145), smoothstep(0.7, 1.0, ft))
|
|
// the lip: one device pixel of grey round the outer edge, lit along
|
|
// the top, a shade darker down the sides and under the foot
|
|
let sy = clamp((c.y - p.y) / max(hf.y, 1.0), -1.0, 1.0)
|
|
let lipc = mix(vec3(0.204, 0.208, 0.227), vec3(0.302, 0.310, 0.333), smoothstep(-0.2, 0.6, sy))
|
|
fcol = mix(fcol, lipc, Finish.band(df, 0.0, px, px))
|
|
// the black edge where the bezel meets the glass
|
|
fcol = mix(fcol, vec3(0.012, 0.012, 0.012), Finish.band(-dg, 0.0, px, px))
|
|
out = lcd_over(out, vec4(fcol * fa, fa))
|
|
// the glass
|
|
let ga = Finish.cover(dg, px)
|
|
let gl = c - h
|
|
let u = ((p.x - gl.x) / max(2.0 * h.x, 1.0) - (p.y - gl.y) / max(2.0 * h.y, 1.0)) * 0.5
|
|
var col = lcd_glass_at(lcd_wglass(), p + o, px, 0.05 * u)
|
|
// the inner shadow all round: the glass sits well under the bezel on
|
|
// every side, ease-out from 28 per cent at the edge over five points
|
|
let ein = clamp(-dg / mix(5.0, 5.5, sunken), 0.0, 1.0)
|
|
col = col * mix(0.72, 1.0, 1.0 - (1.0 - ein) * (1.0 - ein))
|
|
// and deeper under the top edge, where the bezel keeps the light off
|
|
let reach = min(max(2.0 * h.y * 0.12, 5.0), 10.0)
|
|
let t = clamp((p.y - gl.y) / reach, 0.0, 1.0)
|
|
let fall = 1.0 - (1.0 - t) * (1.0 - t)
|
|
col = col * mix(mix(0.86, 0.82, sunken), 1.0, fall)
|
|
out = lcd_over(out, vec4(col * ga, ga))
|
|
return out
|
|
}
|
|
|
|
// THE WATCH FRAME. Every window on the sheet is the wristwatch's own LCD
|
|
// window: rounded by `r`, in a thin black bezel `fw` wide, all of it
|
|
// inside the box, printed with a red ring one device pixel wide a point
|
|
// out from the glass and a blue line along the bezel's outer edge, the
|
|
// glass a step lighter than the page sitting under it with the inner
|
|
// shadow (light on a readout, deep on a pane: `deep` from 0 to 1), the
|
|
// sheen, the grain and the pixel matrix.
|
|
let lcd_screen = fn(p: vec2, size: vec2, o: vec2, px: float, r: float, fw: float, deep: float) -> vec4 {
|
|
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o
|
|
let hi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y, px)) - o
|
|
let hb = (hi - lo) * 0.5
|
|
let c = lo + hb
|
|
let f = Finish.snap(fw, px)
|
|
let h = hb - vec2(f, f)
|
|
let dg = lcd_rbox(p, c, h, r)
|
|
let df = lcd_rbox(p, c, hb, r + f)
|
|
var out = vec4(0.0, 0.0, 0.0, 0.0)
|
|
// the bezel: near black, the blue line along its outer edge, the red
|
|
// ring a point out from the glass, and the black edge at the glass
|
|
let fa = Finish.cover(df, px) * (1.0 - Finish.cover(dg, px))
|
|
var fcol = vec3(0.071, 0.075, 0.086)
|
|
fcol = mix(fcol, vec3(0.188, 0.412, 0.741), Finish.band(df, 0.0, px, px))
|
|
fcol = mix(fcol, vec3(0.776, 0.157, 0.141), Finish.band(-dg, 1.0, 1.0 + px, px))
|
|
fcol = mix(fcol, vec3(0.012, 0.012, 0.012), Finish.band(-dg, 0.0, px, px))
|
|
out = lcd_over(out, vec4(fcol * fa, fa))
|
|
// the glass
|
|
let ga = Finish.cover(dg, px)
|
|
let gl = c - h
|
|
let u = ((p.x - gl.x) / max(2.0 * h.x, 1.0) - (p.y - gl.y) / max(2.0 * h.y, 1.0)) * 0.5
|
|
var col = lcd_glass_at(lcd_wglass(), p + o, px, 0.05 * u)
|
|
// the inner shadow all round, deeper under the top edge
|
|
let ein = clamp(-dg / mix(3.5, 5.0, deep), 0.0, 1.0)
|
|
col = col * mix(mix(0.82, 0.72, deep), 1.0, 1.0 - (1.0 - ein) * (1.0 - ein))
|
|
let t = clamp((p.y - gl.y) / mix(5.0, 9.0, deep), 0.0, 1.0)
|
|
col = col * mix(mix(0.9, 0.84, deep), 1.0, 1.0 - (1.0 - t) * (1.0 - t))
|
|
out = lcd_over(out, vec4(col * ga, ga))
|
|
return out
|
|
}
|
|
|
|
mod.widgets.PanelView.clip_x = false
|
|
mod.widgets.PanelView.clip_y = false
|
|
mod.widgets.PanelView.draw_bg.vertex = fn() {
|
|
let g = lcd_grow(self.rect_pos, self.rect_size, 8.0)
|
|
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
|
|
}
|
|
mod.widgets.PanelView.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let m = 8.0
|
|
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
let r = min(8.0, 0.1 * min(self.rect_size.x, self.rect_size.y))
|
|
return lcd_screen(p, self.rect_size, self.rect_pos, px, r, 3.5, 1.0)
|
|
}
|
|
|
|
// SPLITTERS. The bar between two windows is the plate itself, so the
|
|
// windows stand on it as they stand everywhere else; the grip shows only
|
|
// under the pointer, as a bar of the bezel's grey, lit while it is held.
|
|
mod.widgets.Splitter.draw_bg.color_bg = #0a0b0d
|
|
mod.widgets.Splitter.draw_bg.color = #0a0b0d
|
|
mod.widgets.Splitter.draw_bg.color_hover = #34363b
|
|
mod.widgets.Splitter.draw_bg.color_drag = #4d5055
|
|
mod.widgets.Splitter.draw_bg.color_focus = #34363b
|
|
mod.widgets.Splitter.draw_bg.border_radius = 0.0
|
|
mod.widgets.Splitter.draw_bg.splitter_pad = 0.0
|
|
|
|
// The card is the wristwatch window too; its palette and
|
|
// shadow numbers from Rust are not read.
|
|
mod.widgets.Card.draw_bg.vertex = fn() {
|
|
let g = lcd_grow(self.rect_pos, self.rect_size, 8.0)
|
|
return self.clip_and_transform_vertex(g.xy, g.zw)
|
|
}
|
|
mod.widgets.Card.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let m = 8.0
|
|
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
var out = lcd_screen(p, self.rect_size, self.rect_pos, px, 5.0, 3.5, 0.5)
|
|
// the pointer lays a sparse dither over the glass; a press darkens it
|
|
let d = lcd_rbox(p, self.rect_size * 0.5, self.rect_size * 0.5 - vec2(3.5, 3.5), 5.0)
|
|
let la = Finish.cover(d, px) * lcd_quarter(p, self.rect_pos, px) * max(self.hover, self.down) * 0.8
|
|
let lcol = mix(lcd_mid(), lcd_dark(), self.down)
|
|
return lcd_over(out, vec4(lcol * la, la))
|
|
}
|
|
|
|
// The screen view is that window: its corner a tenth of its shorter side,
|
|
// five points at most, the bezel three and a half points, and no sheen laid
|
|
// over what it holds.
|
|
mod.widgets.ScreenView.sheen = 0.0
|
|
mod.widgets.ScreenView.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let r = min(5.0, 0.1 * min(self.rect_size.x, self.rect_size.y))
|
|
return lcd_screen(p, self.rect_size, self.rect_pos, px, r, 3.5, 0.0)
|
|
}
|
|
|
|
// The plain rounded view keeps the colours its caller gives it; the edge
|
|
// is one snapped device pixel.
|
|
mod.widgets.RoundedView.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
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 sb = lcd_snapbox(self.rect_pos, lo, hi, px)
|
|
let d = lcd_rbox(p, (sb.xy + sb.zw) * 0.5, (sb.zw - sb.xy) * 0.5, max(min(self.border_radius, 6.0), 0.5))
|
|
var fill = self.color
|
|
if self.color_2.x > -0.5 {
|
|
fill = mix(self.color, self.color_2, mix(self.pos.y, self.pos.x, self.gradient_fill_horizontal))
|
|
}
|
|
let inside = Finish.cover(d, px)
|
|
var out = vec4(fill.rgb * fill.a * inside, fill.a * inside)
|
|
if self.border_size > 0.0 {
|
|
let la = Finish.band(d, 0.0, px, px) * self.border_color.a
|
|
out = lcd_over(out, vec4(self.border_color.rgb * la, la))
|
|
}
|
|
return out
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// LISTS. Rows of 22 points, each ruled under in one device pixel of the
|
|
// ghost, as the mockup rules its lists; the rule stops ten points short of
|
|
// the right edge, where a scroll bar stands. The row's words have no chosen
|
|
// ink, so a chosen row cannot invert: it is a flat block of the glass a
|
|
// rung darker, two points in from the row's top, foot and left and from
|
|
// the rule's end, its corners cut in two-pixel steps, and it throws the
|
|
// ink's shadow. The pointer lays the checker dither, over the chosen block
|
|
// too; a disabled row lays none.
|
|
mod.widgets.ListItemOne.height = 22.0
|
|
mod.widgets.ListItem.padding = mod.turtle.Inset{left: 16.0 right: 8.0 top: 0.0 bottom: 0.0}
|
|
mod.widgets.ListItem.draw_text.color = #292b20
|
|
mod.widgets.ListItem.draw_text_2.color = #45493a
|
|
mod.widgets.ListItem.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let o = self.rect_pos
|
|
let live = 1.0 - self.disabled
|
|
let sel = step(0.5, self.selected)
|
|
var out = vec4(0.0, 0.0, 0.0, 0.0)
|
|
let sb = lcd_snapbox(o, vec2(0.0, 1.0), self.rect_size - vec2(10.0, 1.0), px)
|
|
let inx = step(sb.x, p.x) * step(p.x, sb.z)
|
|
let iny = step(sb.y, p.y) * step(p.y, sb.w)
|
|
let inrow = inx * iny
|
|
// the rule under the row: its last device pixel, the row box wide
|
|
let yb = Finish.snap(o.y + self.rect_size.y, px) - o.y
|
|
let ra = step(yb - px, p.y) * step(p.y, yb) * inx
|
|
// the chosen row: the row box less two points at the sides and one at
|
|
// the top and foot, its corners cut two and a half pixels along; the
|
|
// cut passes between pixel centres, so the stair is two pixels deep
|
|
// wherever the row stands
|
|
let cb = lcd_snapbox(o, sb.xy + vec2(2.0, 1.0), sb.zw - vec2(2.0, 1.0), px)
|
|
let c = (cb.xy + cb.zw) * 0.5
|
|
let h = (cb.zw - cb.xy) * 0.5
|
|
let k = 2.5 * px
|
|
let dq = Finish.sd_chamfer(lcd_pix(p, o, px), c, h, k)
|
|
let fa = step(dq, 0.0) * sel
|
|
// its shadow on the reflector, laid under the rule and the block
|
|
let sh = lcd_inkshade(Finish.sd_chamfer(p - lcd_throw(), c, h, k), 0.10) * sel * (1.0 - fa)
|
|
out = lcd_over(out, vec4(lcd_ink() * sh, sh))
|
|
out = lcd_over(out, vec4(lcd_ghost() * ra, ra))
|
|
out = lcd_over(out, vec4(lcd_glass() * 0.86 * fa, fa))
|
|
// the pointer's dither, over the chosen block too
|
|
let chk = lcd_checker(p, o, px)
|
|
let ba = inrow * chk * max(self.hover, self.down) * live
|
|
out = lcd_over(out, vec4(lcd_mid() * ba, ba))
|
|
return out
|
|
}
|
|
|
|
// The open list of a drop-down: a glass sheet in one pixel of ink; the
|
|
// pointer's row lays the dither, the chosen row inverts.
|
|
mod.widgets.PopupMenuFlat.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let sb = lcd_snapbox(self.rect_pos, vec2(0.0, 0.0), self.rect_size, px)
|
|
let d = lcd_rbox(p, (sb.xy + sb.zw) * 0.5, (sb.zw - sb.xy) * 0.5, 1.5)
|
|
let inside = Finish.cover(d, px)
|
|
let t = clamp((p.y - sb.y) / 4.0, 0.0, 1.0)
|
|
var col = lcd_glass_at(lcd_glass() * mix(0.8, 1.02, t), p + self.rect_pos, px, 0.0)
|
|
var out = vec4(col * inside, inside)
|
|
let la = Finish.band(d, 0.0, px, px)
|
|
return lcd_over(out, vec4(lcd_ink() * la, la))
|
|
}
|
|
mod.widgets.PopupMenuItem.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let o = self.rect_pos
|
|
let live = 1.0 - self.disabled
|
|
let chk = lcd_checker(p, o, px)
|
|
let inrow = step(1.0, p.x) * step(p.x, self.rect_size.x - 1.0)
|
|
var out = vec4(0.0, 0.0, 0.0, 0.0)
|
|
let ha = chk * self.hover * live * (1.0 - self.active) * inrow
|
|
out = lcd_over(out, vec4(lcd_mid() * ha, ha))
|
|
let ia = self.active * inrow
|
|
out = lcd_over(out, vec4(lcd_ink() * ia, ia))
|
|
return out
|
|
}
|
|
mod.widgets.PopupMenuItem.draw_text.color_active = #b6bda2
|
|
|
|
// THE SPINNER. Eight segments round a plain centre, one in ink and the
|
|
// next two fading through the dark and light middles to the ghosts. A
|
|
// sheet may not read the pass clock and the spinner carries no other, so
|
|
// it stands still.
|
|
mod.widgets.SpinnerFlat.size = 16.0
|
|
mod.widgets.SpinnerFlat.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let o = self.rect_pos
|
|
let c = vec2(Finish.snap(o.x + self.rect_size.x * 0.5, px), Finish.snap(o.y + self.rect_size.y * 0.5, px)) - o
|
|
let rad = min(self.rect_size.x, self.rect_size.y) * 0.5
|
|
let v = p - c
|
|
var th = atan2(v.x, -v.y)
|
|
if th < 0.0 {
|
|
th = th + 2.0 * PI
|
|
}
|
|
let k = floor(th / (PI * 0.25) + 0.5)
|
|
let ang = k * PI * 0.25
|
|
let dir = vec2(sin(ang), -cos(ang))
|
|
let along = clamp(dot(v, dir), rad * 0.4, rad * 0.9)
|
|
let dd = length(v - dir * along) - rad * 0.11
|
|
let a = Finish.cover(dd, px) * self.alpha
|
|
let age = fract(k / 8.0)
|
|
var col = lcd_ghost()
|
|
col = mix(col, lcd_mid(), step(abs(k - 6.0), 0.5))
|
|
col = mix(col, lcd_dark(), step(abs(k - 7.0), 0.5))
|
|
col = mix(col, lcd_ink(), step(abs(k - 8.0), 0.5) + step(k, 0.5))
|
|
return vec4(col * a, a)
|
|
}
|
|
|
|
// Chips: a pixel-framed cell 20 points tall inside the room its size rung
|
|
// takes, one device pixel of ink; the pointer lays the dither; a chosen
|
|
// chip (light letters) is the inverse legend.
|
|
mod.widgets.ChipFlat.radius = 0.0
|
|
mod.widgets.ChipFlat.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let o = self.rect_pos
|
|
let inset = max((self.rect_size.y - 20.0) * 0.5, 0.0)
|
|
let sb = lcd_snapbox(o, vec2(0.0, inset), self.rect_size - vec2(0.0, inset), px)
|
|
let c = (sb.xy + sb.zw) * 0.5
|
|
let h = (sb.zw - sb.xy) * 0.5
|
|
let pq = lcd_pix(p, o, px)
|
|
let dq = Finish.sd_chamfer(pq, c, h, 2.0 * px)
|
|
let inside = step(dq, 0.0)
|
|
let chosen = step(0.6, dot(self.ink.rgb, vec3(0.299, 0.587, 0.114)))
|
|
var out = vec4(0.0, 0.0, 0.0, 0.0)
|
|
let chk = lcd_checker(p, o, px)
|
|
let ha = inside * chk * max(self.hover, self.down) * (1.0 - chosen)
|
|
out = lcd_over(out, vec4(mix(lcd_mid(), lcd_dark(), self.down) * ha, ha))
|
|
let fa = inside * chosen
|
|
out = lcd_over(out, vec4(lcd_ink() * fa, fa))
|
|
let la = inside * step(-px, dq)
|
|
out = lcd_over(out, vec4(lcd_ink() * la, la))
|
|
let kf = inside * step(-3.0 * px, dq) * step(dq, -2.0 * px) * self.keyed
|
|
out = lcd_over(out, vec4(mix(lcd_ink(), lcd_lit(), chosen) * kf, kf))
|
|
return out
|
|
}
|
|
// Badges: a small inverse legend, dark with lit glass digits.
|
|
mod.widgets.BadgePalette.neutral = #292b20
|
|
mod.widgets.BadgePalette.on_neutral = #b6bda2
|
|
mod.widgets.BadgePalette.neutral_container = #92987f
|
|
mod.widgets.BadgePalette.on_neutral_container = #292b20
|
|
mod.widgets.BadgeFlat.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let o = self.rect_pos
|
|
let bh = min(16.0, self.rect_size.y)
|
|
let sb = lcd_snapbox(o, vec2(0.0, (self.rect_size.y - bh) * 0.5), vec2(self.rect_size.x, (self.rect_size.y + bh) * 0.5), px)
|
|
let c = (sb.xy + sb.zw) * 0.5
|
|
let h = (sb.zw - sb.xy) * 0.5
|
|
let d = Finish.sd_chamfer(lcd_pix(p, o, px), c, h, 2.0 * px)
|
|
let a = step(d, 0.0) * self.color.a
|
|
let ds = lcd_rbox(p - lcd_throw(), c, h, 1.0)
|
|
let sh = lcd_inkshade(ds, 0.14) * (1.0 - a)
|
|
var out = vec4(lcd_ink() * sh, sh)
|
|
return lcd_over(out, vec4(self.color.rgb * a, a))
|
|
}
|
|
|
|
// Rules between groups: one device pixel of ink.
|
|
mod.widgets.Hr.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let y = Finish.snap(self.rect_pos.y + self.rect_size.y * 0.5, px) - self.rect_pos.y
|
|
let a = step(y - px, p.y) * (1.0 - step(y, p.y))
|
|
return vec4(lcd_ink() * a, a)
|
|
}
|
|
mod.widgets.Vr.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let x = Finish.snap(self.rect_pos.x + self.rect_size.x * 0.5, px) - self.rect_pos.x
|
|
let a = step(x - px, p.x) * (1.0 - step(x, p.x))
|
|
return vec4(lcd_ink() * a, a)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// LAMPS. Nothing on the mockup's glass glows, so a lamp is a segment: lit,
|
|
// a disc of ink cut on the pixel grid, stairs and all, throwing the ink's
|
|
// shadow; half lit, the checker dither of ink over its ghost; out, the
|
|
// ghost alone. Every intent prints the same ink. The bar lamp is the same
|
|
// segment as a bar, its corners cut in two-pixel steps. The lens keeps the
|
|
// size and the place the lamp gives it; the quad is grown 4 points for the
|
|
// shadow.
|
|
let lcd_lamp = fn(p: vec2, size: vec2, o: vec2, px: float, lit: float, bar: float, reach: float) -> vec4 {
|
|
// the lens inside whatever room the quad keeps for a halo, a whole
|
|
// number of device pixels each way from a snapped corner, so a disc an
|
|
// even number of pixels wide centres on a pixel corner and an odd one
|
|
// on a pixel
|
|
let rch = clamp(reach, 0.0, 1.0)
|
|
let t = size.y / (1.0 + 2.0 * rch)
|
|
let run = max((size.x - 2.0 * t * rch) * 0.5, t * 0.5)
|
|
let h0 = vec2(mix(t * 0.5, run, bar), t * 0.5)
|
|
let c0 = size * 0.5
|
|
let nn = max(floor(2.0 * h0 / px + vec2(0.5, 0.5)), vec2(1.0, 1.0))
|
|
let lo = vec2(Finish.snap(o.x + c0.x - h0.x, px), Finish.snap(o.y + c0.y - h0.y, px)) - o
|
|
let h = nn * px * 0.5
|
|
let c = lo + h
|
|
// the segment, tested on pixel centres
|
|
let pq = lcd_pix(p, o, px)
|
|
let dq = mix(length(pq - c) - h.y, Finish.sd_chamfer(pq, c, h, 2.5 * px), bar)
|
|
let inside = step(dq, 0.0)
|
|
// the eased amount read in three steps: out, half lit, lit
|
|
let lv = floor(clamp(lit, 0.0, 1.0) * 2.0 + 0.5)
|
|
let full = step(1.5, lv)
|
|
let part = step(0.5, lv) * (1.0 - full)
|
|
let ia = inside * max(full, part * lcd_checker(p, o, px))
|
|
// the ink's shadow round the segment: all of it lit, half of it
|
|
// dithered, none from the ghost
|
|
let ps = p - lcd_throw()
|
|
let ds = mix(length(ps - c) - h.y, Finish.sd_chamfer(ps, c, h, 2.5 * px), bar)
|
|
let sh = lcd_inkshade(ds, 0.14) * (full + 0.5 * part) * (1.0 - inside)
|
|
var out = vec4(lcd_ink() * sh, sh)
|
|
// the ghost the segment always leaves, and the ink over it
|
|
out = lcd_over(out, vec4(lcd_ghost() * inside, inside))
|
|
out = lcd_over(out, vec4(lcd_ink() * ia, ia))
|
|
return out
|
|
}
|
|
|
|
mod.widgets.Lamp.draw_bg.vertex = fn() {
|
|
let g = lcd_grow(self.rect_pos, self.rect_size, 4.0)
|
|
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
|
|
}
|
|
mod.widgets.Lamp.draw_bg.pixel = fn() {
|
|
let m = 4.0
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
return lcd_lamp(p, self.rect_size, self.rect_pos, px, self.lit, self.bar, self.reach) * self.opacity
|
|
}
|
|
// The bar lamp is the lamp with its bar instance set: the same face.
|
|
mod.widgets.LampBar.draw_bg.vertex = fn() {
|
|
let g = lcd_grow(self.rect_pos, self.rect_size, 4.0)
|
|
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
|
|
}
|
|
mod.widgets.LampBar.draw_bg.pixel = fn() {
|
|
let m = 4.0
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
return lcd_lamp(p, self.rect_size, self.rect_pos, px, self.lit, self.bar, self.reach) * self.opacity
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// SWITCHES. The rocker and the slide switch are flags, as the toggles are:
|
|
// a box of one device pixel of ink whose thrown side is an inverted cell
|
|
// and whose other side is empty glass, which the pointer lays the checker
|
|
// dither over and a press darkens toward its turn. Disabled, the ink drops
|
|
// to the light middle; an error doubles the line. The rocker's two cells
|
|
// are split by a one-pixel rule, a dot-matrix O on the off cell and an I
|
|
// one pixel wide on the on cell, lit glass on the thrown cell's ink and at
|
|
// ghost on the other. The quads are grown 5 points for the ink shadow and
|
|
// the focus ring.
|
|
let lcd_rocker = fn(p: vec2, sb: vec4, o: vec2, px: float, t: float, hover: float, down: float, focus: float, fault: float, disabled: float) -> vec4 {
|
|
let live = 1.0 - disabled
|
|
let held = down * live
|
|
let c = (sb.xy + sb.zw) * 0.5
|
|
let h = (sb.zw - sb.xy) * 0.5
|
|
let d = lcd_rbox(p, c, h, 0.01)
|
|
let inside = Finish.cover(d, px)
|
|
// the cells either side of the rule; `t` at 1 throws the on cell
|
|
let mid = Finish.snap(o.x + c.x - px, px) - o.x
|
|
let in_on = step(mid, p.x)
|
|
let cur = mix(1.0 - in_on, in_on, t)
|
|
let sh = lcd_inkshade(abs(lcd_rbox(p - lcd_throw(), c, h, 0.01)) - px * 0.5, 0.14) * (1.0 - inside) * live
|
|
var out = vec4(lcd_ink() * sh, sh)
|
|
// the other cell: dithered under the pointer, darker while pressed
|
|
let oa = inside * (1.0 - cur) * lcd_checker(p, o, px) * max(hover * live, held)
|
|
out = lcd_over(out, vec4(mix(lcd_mid(), lcd_dark(), held) * oa, oa))
|
|
// the thrown cell filled, the box, and the rule between the cells
|
|
let icol = mix(lcd_ink(), lcd_mid(), disabled)
|
|
let fa = inside * cur
|
|
out = lcd_over(out, vec4(icol * fa, fa))
|
|
let rule = step(mid, p.x) * step(p.x, mid + px) * inside
|
|
let la = max(Finish.band(d, 0.0, px, px), rule)
|
|
out = lcd_over(out, vec4(icol * la, la))
|
|
let el = Finish.ring_out(d, px, 2.0 * px, px) * fault
|
|
out = lcd_over(out, vec4(lcd_ink() * el, el))
|
|
// the legends, 7 pixels tall in the middle of their cells
|
|
let yl = Finish.snap(o.y + c.y - 3.5 * px, px) - o.y
|
|
let xo = Finish.snap(o.x + (sb.x + px + mid) * 0.5 - 2.5 * px, px) - o.x
|
|
let xi = Finish.snap(o.x + (mid + sb.z) * 0.5 - 0.5 * px, px) - o.x
|
|
let qp = floor((p + o) / px + vec2(0.001, 0.001))
|
|
let qo = qp - floor((vec2(xo, yl) + o) / px + vec2(0.5, 0.5))
|
|
let qi = qp - floor((vec2(xi, yl) + o) / px + vec2(0.5, 0.5))
|
|
let g_o = lcd_letter(0.0, qo)
|
|
let g_i = step(abs(qi.x), 0.5) * step(0.0, qi.y) * step(qi.y, 6.5)
|
|
let won = g_i * t + g_o * (1.0 - t)
|
|
let wgh = g_i * (1.0 - t) + g_o * t
|
|
let ghost_col = mix(mix(lcd_mid(), lcd_dark(), 0.25), lcd_ghost(), 0.6 * disabled)
|
|
out = lcd_over(out, vec4(ghost_col * wgh, wgh))
|
|
out = lcd_over(out, vec4(mix(lcd_lit(), lcd_glass(), disabled) * won, won))
|
|
let kf = lcd_dash_ring(d, p, c, h, o, 2.0, px) * focus * live
|
|
out = lcd_over(out, vec4(lcd_ink() * kf, kf))
|
|
return out
|
|
}
|
|
|
|
mod.widgets.ToggleRocker.draw_bg.vertex = fn() {
|
|
let g = lcd_grow(self.rect_pos, self.rect_size, 5.0)
|
|
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
|
|
}
|
|
mod.widgets.ToggleRocker.draw_bg.pixel = fn() {
|
|
let m = 5.0
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
let sb = lcd_markbox(self.rect_size, self.rect_pos, self.size * self.pill_aspect, self.size, self.mark_at_end, px)
|
|
let t = step(0.5, mix(self.active, self.drag_pos, self.drag))
|
|
return lcd_rocker(p, sb, self.rect_pos, px, t, self.hover, self.down, self.focus, self.error, self.disabled)
|
|
}
|
|
mod.widgets.ToggleRocker.clip_x = false
|
|
mod.widgets.ToggleRocker.clip_y = false
|
|
|
|
// The slide: its thrown cell is the knob, a square of ink a pixel or more
|
|
// clear of the box, its corners notched, with three grip lines of lit
|
|
// glass across its middle. It follows a drag and throws the ink's shadow
|
|
// on the slot; the free end of the slot holds a small square segment, ink
|
|
// while on and a ghost while off.
|
|
let lcd_slide = fn(p: vec2, sb: vec4, o: vec2, px: float, kt: float, inset: float, pitch: float, hover: float, down: float, focus: float, fault: float, disabled: float) -> vec4 {
|
|
let live = 1.0 - disabled
|
|
let held = down * live
|
|
let c = (sb.xy + sb.zw) * 0.5
|
|
let h = (sb.zw - sb.xy) * 0.5
|
|
let d = lcd_rbox(p, c, h, 0.01)
|
|
let inside = Finish.cover(d, px)
|
|
// the knob: whole device pixels, the line and the inset in from the
|
|
// box, travelling the slot's length less its own
|
|
let cl = px + max(floor(inset / px + 0.5), 1.0) * px
|
|
let ks = max(sb.w - sb.y - 2.0 * cl, 2.0 * px)
|
|
let travel = max(sb.z - sb.x - 2.0 * cl - ks, 0.0)
|
|
let t = step(0.5, kt)
|
|
let kx = Finish.snap(o.x + sb.x + cl + clamp(kt, 0.0, 1.0) * travel, px) - o.x
|
|
let ky = sb.y + cl
|
|
let kh = vec2(ks * 0.5, ks * 0.5)
|
|
let kc = vec2(kx, ky) + kh
|
|
let pq = lcd_pix(p, o, px)
|
|
let knob = step(Finish.sd_chamfer(pq, kc, kh, 1.5 * px), 0.0)
|
|
// the mark: the knob's square at the free end, a third of it in on
|
|
// every side
|
|
let fx = mix(sb.z - cl - ks * 0.5, sb.x + cl + ks * 0.5, t)
|
|
let mm = floor(ks / px / 3.0 + 0.5) * px
|
|
let mh = max(kh - vec2(mm, mm), vec2(px * 0.5, px * 0.5))
|
|
let mark = step(lcd_rbox(pq, vec2(fx, kc.y), mh, 0.01), 0.0) * (1.0 - knob)
|
|
// the ink's shadow: the line's round the box, the knob's on the slot
|
|
let th = lcd_throw()
|
|
let sl = lcd_inkshade(abs(lcd_rbox(p - th, c, h, 0.01)) - px * 0.5, 0.14) * (1.0 - inside)
|
|
let sk = lcd_inkshade(Finish.sd_chamfer(p - th, kc, kh, 1.5 * px), 0.14) * (1.0 - knob) * inside
|
|
let sh = max(sl, sk) * live
|
|
var out = vec4(lcd_ink() * sh, sh)
|
|
// the free slot: dithered under the pointer, darker while pressed
|
|
let oa = inside * (1.0 - knob) * lcd_checker(p, o, px) * max(hover * live, held)
|
|
out = lcd_over(out, vec4(mix(lcd_mid(), lcd_dark(), held) * oa, oa))
|
|
// the box, doubled by an error
|
|
let icol = mix(lcd_ink(), lcd_mid(), disabled)
|
|
let la = Finish.band(d, 0.0, px, px)
|
|
out = lcd_over(out, vec4(icol * la, la))
|
|
let el = Finish.ring_out(d, px, 2.0 * px, px) * fault
|
|
out = lcd_over(out, vec4(lcd_ink() * el, el))
|
|
// the mark: ink while on, a ghost while off
|
|
let mcol = mix(mix(lcd_ghost(), vec3(0.604, 0.631, 0.529), disabled), icol, t)
|
|
out = lcd_over(out, vec4(mcol * mark, mark))
|
|
// the knob, and its grip: three lit columns one device pixel wide,
|
|
// `pitch` apart, over the middle half of its height
|
|
out = lcd_over(out, vec4(icol * knob, knob))
|
|
let kw = floor(ks / px + 0.5)
|
|
let gp = max(floor(pitch / px + 0.5), 2.0)
|
|
let qk = floor((p + o) / px + vec2(0.001, 0.001)) - floor((vec2(kx, ky) + o) / px + vec2(0.5, 0.5))
|
|
let rel = qk.x - floor(kw * 0.5)
|
|
let gq = floor(rel / gp + 0.5)
|
|
let rim = floor(kw * 0.25)
|
|
let grip = step(abs(rel - gq * gp), 0.5) * step(abs(gq), 1.0) * step(rim, qk.y) * step(qk.y, kw - 1.0 - rim) * knob
|
|
out = lcd_over(out, vec4(mix(lcd_lit(), lcd_glass(), disabled) * grip, grip))
|
|
let kf = lcd_dash_ring(d, p, c, h, o, 2.0, px) * focus * live
|
|
out = lcd_over(out, vec4(lcd_ink() * kf, kf))
|
|
return out
|
|
}
|
|
|
|
mod.widgets.ToggleSlide.draw_bg.vertex = fn() {
|
|
let g = lcd_grow(self.rect_pos, self.rect_size, 5.0)
|
|
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
|
|
}
|
|
mod.widgets.ToggleSlide.draw_bg.pixel = fn() {
|
|
let m = 5.0
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
let sb = lcd_markbox(self.rect_size, self.rect_pos, self.size * self.pill_aspect, self.size, self.mark_at_end, px)
|
|
let kt = mix(self.active, self.drag_pos, self.drag)
|
|
return lcd_slide(p, sb, self.rect_pos, px, kt, self.knob_inset, self.grip_pitch, self.hover, self.down, self.focus, self.error, self.disabled)
|
|
}
|
|
mod.widgets.ToggleSlide.clip_x = false
|
|
mod.widgets.ToggleSlide.clip_y = false
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// THE NEEDLE METER. A scale printed in ink on the face, which is the
|
|
// screen's glass, framed in one device pixel with its corners cut in
|
|
// two-pixel steps: an arc of one pixel, a tick at every division, the
|
|
// majors two pixels wide and longer, and the majors' values in small
|
|
// segment digits inside the arc wherever they have the room. The loud end
|
|
// is a band of ink dither just outside the arc, no colour. The needle is a
|
|
// bar of ink two pixels wide from the pivot to past the scale, under a
|
|
// disc of ink cut on the pixel grid; the needle, the pivot, the scale and
|
|
// the frame throw the ink's shadow. The quad is grown 4 points for the
|
|
// frame's.
|
|
|
|
// The distance to the scale's arc, at q from the pivot: a band one device
|
|
// pixel wide, ended square at both ends of the sweep. The angle is taken a
|
|
// hair off the pivot, which may lie on a pixel centre.
|
|
let lcd_arc_d = fn(q: vec2, rr: float, sweep: float, px: float) -> float {
|
|
let r = length(q)
|
|
let t = atan2(q.x, 0.0001 - q.y) / sweep + 0.5
|
|
return max(abs(r - rr) - px * 0.5, max(-t, t - 1.0) * sweep * r)
|
|
}
|
|
|
|
// The distance to the nearest tick of a scale of n divisions, a major one
|
|
// every `nmin`, at q from the pivot: a minor one device pixel wide, a major
|
|
// two, each running in from the arc by its length.
|
|
let lcd_tick_d = fn(q: vec2, rr: float, sweep: float, n: float, nmin: float, tmaj: float, tmin: float, px: float) -> float {
|
|
let t = atan2(q.x, 0.0001 - q.y) / sweep + 0.5
|
|
let k = floor(clamp(t, 0.0, 1.0) * n + 0.5)
|
|
let ak = (k / n - 0.5) * sweep
|
|
let dir = vec2(sin(ak), -cos(ak))
|
|
let major = 1.0 - step(0.5, k - nmin * floor(k / nmin + 0.0001))
|
|
let r = length(q)
|
|
let perp = abs(q.x * dir.y - q.y * dir.x) - px * mix(0.5, 1.0, major)
|
|
let radial = max(rr - mix(tmin, tmaj, major) - r, r - rr)
|
|
return max(max(perp, radial), -dot(q, dir))
|
|
}
|
|
|
|
// The distance to the needle: a bar `w` either side of its axis, from the
|
|
// pivot out to `reach` along nd.
|
|
let lcd_needle_d = fn(q: vec2, nd: vec2, reach: float, w: float) -> float {
|
|
let along = dot(q, nd)
|
|
let across = abs(q.x * nd.y - q.y * nd.x)
|
|
return max(across - w, max(along - reach, -along))
|
|
}
|
|
|
|
// The value of major j of nmaj, 0 to 100, in small segment digits set
|
|
// upright inside the arc, in from `inner` along the major by half the
|
|
// number's own extent there: coverage at p.
|
|
let lcd_meter_number = fn(p: vec2, o: vec2, pv: vec2, sweep: float, j: float, nmaj: float, inner: float, px: float) -> float {
|
|
let dh = 5.5
|
|
let adv = dh * 0.68
|
|
let cw = dh * 0.5
|
|
let v = floor(j / nmaj * 100.0 + 0.5)
|
|
let nd = 1.0 + step(9.5, v) + step(99.5, v)
|
|
let w = (nd - 1.0) * adv + cw
|
|
let aj = (j / nmaj - 0.5) * sweep
|
|
let dir = vec2(sin(aj), -cos(aj))
|
|
let ext = w * 0.5 * abs(dir.x) + dh * 0.5 * abs(dir.y)
|
|
let cc = pv + dir * (inner - ext)
|
|
let left = Finish.snap(o.x + cc.x - w * 0.5, px) - o.x
|
|
let top = Finish.snap(o.y + cc.y - dh * 0.5, px) - o.y
|
|
let k = floor((p.x - left) / adv)
|
|
let u = (p.x - left - k * adv) / cw
|
|
// the digit k places from the left: hundreds, tens or units
|
|
let ex = nd - 1.0 - k
|
|
let pw = mix(1.0, mix(10.0, 100.0, step(1.5, ex)), step(0.5, ex))
|
|
var dg = floor(v / pw + 0.0001)
|
|
dg = dg - 10.0 * floor(dg / 10.0 + 0.0001)
|
|
let inside = step(0.0, k) * step(k, nd - 0.5) * step(0.0, u) * step(u, 1.0)
|
|
return Finish.seg7(vec2(u, (p.y - top) / dh), dg, px / dh) * inside
|
|
}
|
|
|
|
// The meter's face: the glass and its frame, the scale, the values, the
|
|
// zone, the needle and the pivot.
|
|
let lcd_meter = fn(p: vec2, size: vec2, o: vec2, px: float, face: vec4, sheen: float, pv: vec2, rr: float, sweep: float, majors: float, minors: float, zone: float, tmaj: float, tmin: float, pr: float, na: float) -> vec4 {
|
|
// the face on whole device pixels, its corners cut in two-pixel steps
|
|
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o
|
|
let hi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y, px)) - o
|
|
let c = (lo + hi) * 0.5
|
|
let h = (hi - lo) * 0.5
|
|
let pq = lcd_pix(p, o, px)
|
|
let dq = Finish.sd_chamfer(pq, c, h, 2.5 * px)
|
|
let fa = step(dq, 0.0)
|
|
let th = lcd_throw()
|
|
// the frame's shadow on the glass round the face
|
|
let sf = lcd_inkshade(abs(Finish.sd_chamfer(p - th, c, h, 2.5 * px) + px * 0.5) - px * 0.5, 0.14)
|
|
let so = sf * (1.0 - fa)
|
|
var out = vec4(lcd_ink() * so, so)
|
|
// the screen's glass with the windows' sheen, grain and pixel matrix
|
|
let u = ((p.x - lo.x) / max(2.0 * h.x, 1.0) - (p.y - lo.y) / max(2.0 * h.y, 1.0)) * 0.5
|
|
let gcol = lcd_glass_at(face.rgb, p + o, px, 2.0 * sheen * u)
|
|
out = lcd_over(out, vec4(gcol * fa, fa))
|
|
// the frame, the arc and the ticks
|
|
let nmaj = max(floor(majors + 0.5), 1.0)
|
|
let nmin = max(floor(minors + 0.5), 1.0)
|
|
let n = nmaj * nmin
|
|
let q = p - pv
|
|
let frame = fa * step(-px, dq)
|
|
let arc = Finish.cover(lcd_arc_d(q, rr, sweep, px), px)
|
|
let tick = Finish.cover(lcd_tick_d(q, rr, sweep, n, nmin, tmaj, tmin, px), px)
|
|
// the values, read off the major nearest p, where the majors stand far
|
|
// enough apart for the widest of them
|
|
let t = atan2(q.x, 0.0001 - q.y) / sweep + 0.5
|
|
let j = floor(clamp(t, 0.0, 1.0) * nmaj + 0.5)
|
|
let roomy = step(13.0, (rr - tmaj - 7.5) * sweep / nmaj)
|
|
let num = lcd_meter_number(p, o, pv, sweep, j, nmaj, rr - tmaj - 2.0, px) * roomy
|
|
// the loud end: a checker of ink just outside the arc
|
|
var zn = 0.0
|
|
if zone < 0.999 {
|
|
let rz = length(q) - rr
|
|
zn = step(zone, t) * step(t, 1.0001) * step(px * 0.5, rz) * step(rz, 3.0) * lcd_checker(p, o, px)
|
|
}
|
|
// the needle, and the disc of the pivot over its root
|
|
let nd = vec2(sin(na), -cos(na))
|
|
let needle = Finish.cover(lcd_needle_d(q, nd, rr + 3.0, px), px)
|
|
let pin = step(length(pq - pv), pr)
|
|
let ink = max(max(max(frame, arc), max(tick, num)), max(max(zn, needle), pin)) * fa
|
|
// the ink's shadow on the glass, of all but the values and the zone
|
|
let qs = q - th
|
|
let ss = lcd_inkshade(min(lcd_arc_d(qs, rr, sweep, px), lcd_tick_d(qs, rr, sweep, n, nmin, tmaj, tmin, px)), 0.14)
|
|
let sn = lcd_inkshade(min(lcd_needle_d(qs, nd, rr + 3.0, px), length(qs) - pr), 0.14)
|
|
let sh = max(sf, max(ss, sn)) * fa * (1.0 - ink)
|
|
out = lcd_over(out, vec4(lcd_ink() * sh, sh))
|
|
out = lcd_over(out, vec4(lcd_ink() * ink, ink))
|
|
return out * face.a
|
|
}
|
|
|
|
mod.widgets.NeedleMeter.draw_bg.vertex = fn() {
|
|
let g = lcd_grow(self.rect_pos, self.rect_size, 4.0)
|
|
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
|
|
}
|
|
mod.widgets.NeedleMeter.draw_bg.pixel = fn() {
|
|
let m = 4.0
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
let o = self.rect_pos
|
|
let pv = lcd_pix(self.pivot(), o, px)
|
|
let na = self.needle_angle(self.value)
|
|
return lcd_meter(p, self.rect_size, o, px, self.color, self.sheen, pv, self.scale_radius(), self.sweep * 0.017453293, self.majors, self.minors, self.zone, self.tick_major, self.tick_minor, self.pivot_size, na) * self.opacity
|
|
}
|
|
mod.widgets.NeedleMeter.clip_x = false
|
|
mod.widgets.NeedleMeter.clip_y = false
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// RINGS AND ARCS. The gauge bar bent round: a band outlined in one device
|
|
// pixel of ink along both its edges and across its ends, which are cut
|
|
// square. Inside the line, past a device pixel of glass where the band is
|
|
// seven device pixels or more across, the part still to go stands at the
|
|
// ghost and the part travelled is solid ink, from the start and never
|
|
// shorter than it is wide; a whole ring has no ends, so its run leaves the
|
|
// top with no gap, and a ring cut into sections is a row of such pieces.
|
|
// The amount that is not known stands still as the checker dither round
|
|
// the whole inside. The line throws the ink's shadow outside the band and
|
|
// the run on the glass and the ghost beside it. Round caps and the intent
|
|
// colours are not read, so the activity rings nest three of these bands
|
|
// all in ink, told apart by the glass between them and not by a hue. The
|
|
// label stays in the middle, at the foot of an arc, in the ink. The quad
|
|
// is grown 4 points for the shadow.
|
|
|
|
// Where q, from the centre, stands on a piece of ring that leaves the
|
|
// shader angle a0 (nought straight down, rising clockwise, -pi at the
|
|
// top) and runs `span` radians on, clockwise for dir 1 and back for -1:
|
|
// .x its distance past the start and .y past the end, in points along
|
|
// the arc, and .z the angle travelled from the start.
|
|
let lcd_sweep_at = fn(q: vec2, a0: float, span: float, dir: float) -> vec3 {
|
|
let rl = length(q)
|
|
var dl = atan2(-q.x, q.y) - a0 - dir * span * 0.5
|
|
dl = dl - 2.0 * PI * floor((dl + PI) / (2.0 * PI))
|
|
let u = dir * dl + span * 0.5
|
|
return vec3(-u * rl, (u - span) * rl, u)
|
|
}
|
|
|
|
// One piece of band about c, its outer edge r1 out and `th` across, from
|
|
// the shader angle a0 through `span` (dir as above), with its run of ink
|
|
// `run` radians from the start, below nought when the amount is not known.
|
|
let lcd_ring_face = fn(p: vec2, c: vec2, o: vec2, px: float, r1: float, th: float, a0: float, span: float, dir: float, run: float, track: float) -> vec4 {
|
|
let q = p - c
|
|
let qs = q - lcd_throw()
|
|
let rl = length(q)
|
|
let rls = length(qs)
|
|
let hw = th * 0.5
|
|
let rc = r1 - hw
|
|
// the inside: in past the line and, where the band has the room, a
|
|
// device pixel of glass
|
|
let gi = px + px * step(7.0 * px, th + 0.5 * px)
|
|
let hn = max(hw - gi, min(0.5 * px, hw))
|
|
// a whole ring has no ends to cut
|
|
let cut = step(span, 2.0 * PI - 0.001)
|
|
let e = lcd_sweep_at(q, a0, span, dir)
|
|
let es = lcd_sweep_at(qs, a0, span, dir)
|
|
let de = mix(-1000.0, max(e.x, e.y), cut)
|
|
let d = max(abs(rl - rc) - hw, de)
|
|
let ds = max(abs(rls - rc) - hw, mix(-1000.0, max(es.x, es.y), cut))
|
|
let inside = Finish.cover(d, px)
|
|
let ia = Finish.cover(max(abs(rl - rc) - hn, de + gi), px)
|
|
let la = Finish.band(d, 0.0, px, px) * track
|
|
var ga = 0.0
|
|
var fa = 0.0
|
|
var shf = 0.0
|
|
if run >= 0.0 {
|
|
// the run, square at both ends; at an end of the band it keeps the
|
|
// inside's inset, and it is never shorter than it is wide
|
|
let gs = gi * cut
|
|
let on = step(0.0001, run)
|
|
let sf = min(max(run, (2.0 * hn + gs) / max(rc, px)), span)
|
|
let whole = step(2.0 * PI - 0.001, sf)
|
|
let fe = lcd_sweep_at(q, a0, sf, dir)
|
|
let fs = lcd_sweep_at(qs, a0, sf, dir)
|
|
let df = max(max(abs(rl - rc) - hn, mix(max(fe.x + gs, fe.y), -1000.0, whole)), mix(-1000.0, e.y + gi, cut))
|
|
let dfs = max(max(abs(rls - rc) - hn, mix(max(fs.x + gs, fs.y), -1000.0, whole)), mix(-1000.0, es.y + gi, cut))
|
|
ga = ia * track
|
|
fa = Finish.cover(df, px) * on
|
|
shf = lcd_inkshade(dfs, 0.14) * on * (1.0 - fa)
|
|
} else {
|
|
// not known: the checker dither round the whole inside
|
|
fa = ia * lcd_checker(p, o, px)
|
|
}
|
|
// the ink's shadow on the reflector: the line's outside the band, the
|
|
// run's on the glass and the ghost beside it
|
|
let shl = lcd_inkshade(abs(ds + px * 0.5) - px * 0.5, 0.14) * track * (1.0 - inside)
|
|
let sh = max(shl, shf)
|
|
var out = vec4(lcd_ghost() * ga, ga)
|
|
out = lcd_over(out, vec4(lcd_ink() * sh, sh))
|
|
out = lcd_over(out, vec4(lcd_ink() * la, la))
|
|
out = lcd_over(out, vec4(lcd_ink() * fa, fa))
|
|
return out
|
|
}
|
|
|
|
mod.widgets.ProgressRingFlat.draw_bg.vertex = fn() {
|
|
let g = lcd_grow(self.rect_pos, self.rect_size, 4.0)
|
|
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
|
|
}
|
|
mod.widgets.ProgressRingFlat.draw_bg.pixel = fn() {
|
|
let m = 4.0
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
let o = self.rect_pos
|
|
// the centre on a pixel corner and the edges whole device pixels out
|
|
// from it; a nested ring stands `inset` in
|
|
let c = vec2(Finish.snap(o.x + self.rect_size.x * 0.5, px), Finish.snap(o.y + self.rect_size.y * self.center_y, px)) - o
|
|
let r1 = max(floor((min(self.rect_size.x * 0.5, self.rect_size.y * self.center_y) - self.inset) / px + 0.001) * px, 3.0 * px)
|
|
let th = clamp(Finish.snap(self.thickness, px), 3.0 * px, r1)
|
|
// the stock's angles; a negative direction runs from the far end
|
|
let flip = step(self.direction, 0.0)
|
|
let a0 = self.base_angle + mix(self.start, 1.0 - self.start, flip) * self.sweep
|
|
var run = -1.0
|
|
if self.value >= 0.0 {
|
|
run = (clamp(self.value, self.start, self.end) - self.start) * self.sweep
|
|
}
|
|
return lcd_ring_face(p, c, o, px, r1, th, a0, max(self.end - self.start, 0.0) * self.sweep, 1.0 - 2.0 * flip, run, self.track) * self.opacity
|
|
}
|
|
mod.widgets.ProgressRingFlat.clip_x = false
|
|
mod.widgets.ProgressRingFlat.clip_y = false
|
|
mod.widgets.ProgressRingFlat.draw_text.color = #292b20
|
|
|
|
// GAUGES. The dial is the ring's band over three quarters of a turn, open
|
|
// at the foot, with the scale printed inside it: ten steps two points in
|
|
// from the band and a long tick at 0, 50 and 100, one device pixel of ink
|
|
// cut on the pixel grid, so the centre stands in the middle of a pixel.
|
|
// The zones are dithers of ink on the ghost inside the band, never a
|
|
// colour: the safe zone plain, the warning zone one pixel in four, the
|
|
// critical zone the checker. The needle is two device pixels of ink from
|
|
// six points behind the hub to the scale, on a hub three points round cut
|
|
// on the pixel grid; with a needle the band keeps its zones whole and the
|
|
// needle carries the reading, without one the part travelled fills with
|
|
// ink. Laid flat, the band is the gauge bar's pill, its scale nine ink
|
|
// dividers across the inside at the tenths and its marker two device
|
|
// pixels of ink across the pill, two points proud of it. The line, the
|
|
// run, the ticks, the needle and the marker throw the ink's shadow.
|
|
// `band` at nought leaves the zones out; their blend and the palette are
|
|
// not read. The quad is grown 4 points for the shadow.
|
|
|
|
// The dial about c, its outer edge r1 out: the band from the lower left
|
|
// through `sweep` to the lower right, the zones, the scale and the needle
|
|
// at v.
|
|
let lcd_gauge_dial = fn(p: vec2, c: vec2, o: vec2, px: float, r1: float, th: float, sweep: float, v: float, warn: float, crit: float, zones: float, needle: float) -> vec4 {
|
|
let q = p - c
|
|
let qs = q - lcd_throw()
|
|
let rl = length(q)
|
|
let rls = length(qs)
|
|
// the opening is centred on the foot
|
|
let a0 = PI - sweep * 0.5
|
|
let hw = th * 0.5
|
|
let rc = r1 - hw
|
|
let gi = px + px * step(7.0 * px, th + 0.5 * px)
|
|
let hn = max(hw - gi, min(0.5 * px, hw))
|
|
let e = lcd_sweep_at(q, a0, sweep, 1.0)
|
|
let es = lcd_sweep_at(qs, a0, sweep, 1.0)
|
|
let d = max(abs(rl - rc) - hw, max(e.x, e.y))
|
|
let ds = max(abs(rls - rc) - hw, max(es.x, es.y))
|
|
let inside = Finish.cover(d, px)
|
|
let ia = Finish.cover(max(abs(rl - rc) - hn, max(e.x, e.y) + gi), px)
|
|
let la = Finish.band(d, 0.0, px, px)
|
|
// the zones by the share of the sweep: plain, then one pixel in four,
|
|
// then the checker
|
|
let f = e.z / max(sweep, 0.001)
|
|
let z = step(warn, f) + step(crit, f)
|
|
let za = ia * mix(lcd_quarter(p, o, px), lcd_checker(p, o, px), step(1.5, z)) * step(0.5, z) * zones
|
|
// without a needle, the run of ink as on a ring
|
|
let sf = min(max(v * sweep, (2.0 * hn + gi) / max(rc, px)), sweep)
|
|
let fe = lcd_sweep_at(q, a0, sf, 1.0)
|
|
let fs = lcd_sweep_at(qs, a0, sf, 1.0)
|
|
let on = step(0.001, v) * (1.0 - needle)
|
|
let fa = Finish.cover(max(max(abs(rl - rc) - hn, max(fe.x + gi, fe.y)), e.y + gi), px) * on
|
|
let shf = lcd_inkshade(max(max(abs(rls - rc) - hn, max(fs.x + gi, fs.y)), es.y + gi), 0.14) * on * (1.0 - fa)
|
|
// the scale: a tick at each tenth, the long ones at 0, 50 and 100,
|
|
// two points in from the band
|
|
let rt = rc - hw - 2.0
|
|
let k = clamp(floor(f * 10.0 + 0.5), 0.0, 10.0)
|
|
let tl = mix(3.0, 5.0, step(fract(k / 5.0 + 0.01), 0.1))
|
|
let tk = step(abs(f - k * 0.1) * sweep * rl, 0.5 * px) * step(rt - tl, rl) * step(rl, rt)
|
|
let fsh = es.z / max(sweep, 0.001)
|
|
let ks = clamp(floor(fsh * 10.0 + 0.5), 0.0, 10.0)
|
|
let tls = mix(3.0, 5.0, step(fract(ks / 5.0 + 0.01), 0.1))
|
|
let sht = lcd_inkshade(max(abs(fsh - ks * 0.1) * sweep * rls - 0.5 * px, max(rt - tls - rls, rls - rt)), 0.14) * (1.0 - tk)
|
|
// the needle from behind the hub to the scale, and the hub
|
|
let av = a0 + v * sweep
|
|
let nd = vec2(-sin(av), cos(av))
|
|
let tip = nd * rt
|
|
let tail = vec2(0.0, 0.0) - nd * 6.0
|
|
let hub = step(length(lcd_pix(p, o, px) - c), 3.0)
|
|
let na = max(Finish.cover(lcd_seg(q, tail, tip) - px, px), hub) * needle
|
|
let nsh = lcd_inkshade(min(lcd_seg(qs, tail, tip) - px, rls - 3.0), 0.14) * needle * (1.0 - na)
|
|
// the line's shadow outside the band, and the rest
|
|
let shl = lcd_inkshade(abs(ds + px * 0.5) - px * 0.5, 0.14) * (1.0 - inside)
|
|
let sh = max(max(shl, shf), max(nsh, sht))
|
|
var out = vec4(lcd_ghost() * ia, ia)
|
|
out = lcd_over(out, vec4(lcd_ink() * sh, sh))
|
|
out = lcd_over(out, vec4(lcd_ink() * za, za))
|
|
out = lcd_over(out, vec4(lcd_ink() * tk, tk))
|
|
out = lcd_over(out, vec4(lcd_ink() * la, la))
|
|
out = lcd_over(out, vec4(lcd_ink() * fa, fa))
|
|
out = lcd_over(out, vec4(lcd_ink() * na, na))
|
|
return out
|
|
}
|
|
|
|
// Laid flat: the gauge bar's pill `th` tall across the walk, the zones
|
|
// along its inside, the dividers and the marker at v.
|
|
let lcd_gauge_bar = fn(p: vec2, o: vec2, size: vec2, px: float, th: float, v: float, warn: float, crit: float, zones: float, needle: float) -> vec4 {
|
|
let tw = lcd_throw()
|
|
let hgt = min(th, size.y)
|
|
let y0 = (size.y - hgt) * 0.5
|
|
let sb = lcd_snapbox(o, vec2(0.0, y0), vec2(size.x, y0 + hgt), px)
|
|
let c = (sb.xy + sb.zw) * 0.5
|
|
let h = (sb.zw - sb.xy) * 0.5
|
|
let r = min(h.x, h.y)
|
|
let d = lcd_rbox(p, c, h, r)
|
|
let inside = Finish.cover(d, px)
|
|
let la = Finish.band(d, 0.0, px, px)
|
|
// the inside, two device pixels in, as on the bar
|
|
let hf = max(h.y - 2.0 * px, min(px, h.y))
|
|
let xi0 = sb.x + 2.0 * px
|
|
let xi1 = max(sb.z - 2.0 * px, xi0)
|
|
let kh = vec2((xi1 - xi0) * 0.5, hf)
|
|
let ia = Finish.cover(lcd_rbox(p, vec2(xi0 + kh.x, c.y), kh, min(kh.x, hf)), px)
|
|
let f = (p.x - xi0) / max(xi1 - xi0, 0.001)
|
|
let z = step(warn, f) + step(crit, f)
|
|
let za = ia * mix(lcd_quarter(p, o, px), lcd_checker(p, o, px), step(1.5, z)) * step(0.5, z) * zones
|
|
// the scale: a divider one device pixel wide at each tenth between
|
|
let k = clamp(floor(f * 10.0 + 0.5), 1.0, 9.0)
|
|
let xk = Finish.snap(o.x + mix(xi0, xi1, k * 0.1), px) - o.x
|
|
let tk = ia * step(xk, p.x) * step(p.x, xk + px)
|
|
// without a marker, the run of ink as on the bar
|
|
let xv = Finish.snap(o.x + mix(xi0, xi1, v), px) - o.x
|
|
let xe = min(max(xv, xi0 + 2.0 * hf), xi1)
|
|
let fh = vec2((xe - xi0) * 0.5, hf)
|
|
let fc = vec2(xi0 + fh.x, c.y)
|
|
let rf = min(fh.x, hf)
|
|
let on = step(0.001, v) * (1.0 - needle)
|
|
let fa = Finish.cover(lcd_rbox(p, fc, fh, rf), px) * on
|
|
let shf = lcd_inkshade(lcd_rbox(p - tw, fc, fh, rf), 0.14) * on * (1.0 - fa)
|
|
// the marker: two device pixels of ink across the pill, two points
|
|
// proud of it either side
|
|
let mc = vec2(xv, c.y)
|
|
let mh = vec2(px, h.y + Finish.snap(2.0, px))
|
|
let ma = Finish.cover(lcd_rbox(p, mc, mh, 0.0), px) * needle
|
|
let msh = lcd_inkshade(lcd_rbox(p - tw, mc, mh, 0.0), 0.14) * needle * (1.0 - ma)
|
|
let shl = lcd_inkshade(abs(lcd_rbox(p - tw, c, h, r) + px * 0.5) - px * 0.5, 0.14) * (1.0 - inside)
|
|
let sh = max(max(shl, shf), msh)
|
|
var out = vec4(lcd_ghost() * ia, ia)
|
|
out = lcd_over(out, vec4(lcd_ink() * sh, sh))
|
|
out = lcd_over(out, vec4(lcd_ink() * za, za))
|
|
out = lcd_over(out, vec4(lcd_ink() * tk, tk))
|
|
out = lcd_over(out, vec4(lcd_ink() * la, la))
|
|
out = lcd_over(out, vec4(lcd_ink() * fa, fa))
|
|
out = lcd_over(out, vec4(lcd_ink() * ma, ma))
|
|
return out
|
|
}
|
|
|
|
mod.widgets.Gauge.draw_bg.vertex = fn() {
|
|
let g = lcd_grow(self.rect_pos, self.rect_size, 4.0)
|
|
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
|
|
}
|
|
mod.widgets.Gauge.draw_bg.pixel = fn() {
|
|
let m = 4.0
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
let o = self.rect_pos
|
|
let v = clamp(self.value, 0.0, 1.0)
|
|
let zoned = step(0.01, self.band)
|
|
let hand = step(0.5, self.needle)
|
|
var out = vec4(0.0, 0.0, 0.0, 0.0)
|
|
if self.linear > 0.5 {
|
|
out = lcd_gauge_bar(p, o, self.rect_size, px, self.thickness, v, self.zone_warn, self.zone_critical, zoned, hand)
|
|
} else {
|
|
// the centre in the middle of a pixel and the edges half a pixel
|
|
// off it: the ticks run through whole pixels, the band's edges
|
|
// between them
|
|
let c = (floor((o + self.rect_size * 0.5) / px + vec2(0.001, 0.001)) + vec2(0.5, 0.5)) * px - o
|
|
let r1 = (floor(min(self.rect_size.x, self.rect_size.y) * 0.5 / px - 0.5) + 0.5) * px
|
|
let th = clamp(Finish.snap(self.thickness, px), 3.0 * px, max(r1 * 0.5, 3.0 * px))
|
|
out = lcd_gauge_dial(p, c, o, px, r1, th, self.sweep, v, self.zone_warn, self.zone_critical, zoned, hand)
|
|
}
|
|
return out * self.opacity
|
|
}
|
|
mod.widgets.Gauge.clip_x = false
|
|
mod.widgets.Gauge.clip_y = false
|
|
mod.widgets.Gauge.draw_text.color = #292b20
|
|
mod.widgets.Gauge.draw_range.color = #45493a
|
|
|
|
// THE NAVIGATION LINE. One device pixel of ink through the middle of its
|
|
// strip, run out to the value, and the rest of the strip's width at the
|
|
// ghost. Done, it does not fade under a veil: as its opacity falls it
|
|
// drops out a pixel at a time, in a fixed order of four along the line.
|
|
mod.widgets.NavigationProgress.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let o = self.rect_pos
|
|
let y = Finish.snap(o.y + (self.rect_size.y - px) * 0.5, px) - o.y
|
|
let row = step(y, p.y) * step(p.y, y + px)
|
|
let xv = Finish.snap(o.x + self.rect_size.x * clamp(self.value, 0.0, 1.0), px) - o.x
|
|
let col = mix(lcd_ghost(), lcd_ink(), step(p.x, xv))
|
|
// a pixel's place in the order, counted in window pixels along x
|
|
let i = floor(fract(floor((p.x + o.x) / px + 0.001) * 0.25) * 4.0 + 0.5)
|
|
let a = row * step((4.0 * fract(i * 0.5) + floor(i * 0.5) + 0.5) * 0.25, self.opacity)
|
|
return vec4(col * a, a)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// THE GROUND. The window is the black plate of the wristwatch's face, and
|
|
// the glass is in the windows set into it: every pane and panel is one,
|
|
// so the plate shows as the bezels between them. Flat near-black with a
|
|
// grain one device pixel fine, no sheen.
|
|
mod.widgets.Window.show_bg = true
|
|
mod.widgets.Window.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size + self.rect_pos
|
|
let g = fract(sin(dot(floor(p / px), vec2(12.9898, 78.233))) * 43758.5453)
|
|
let col = vec3(0.039, 0.043, 0.051) * (0.94 + 0.12 * g)
|
|
return vec4(col, 1.0)
|
|
}
|
|
true
|