makepad/widgets/sheets/themes/blueprint/widgets.splash

2620 lines
132 KiB
Text

use mod.prelude.widgets_internal.*
// An engineering blueprint: every control is a technical line drawing in
// white ink on the deep cyanotype paper of the window's ground. Outlines
// are two device pixels with corners of two points, dimension, centre and
// hatch lines one device pixel, all crisp at the device pixel and evenly
// weighted, with no glow and no relief. A face is the paper a shade
// deeper; nothing is filled except where a state needs it: the chosen
// thing takes the pale wash or a light hatch, a held thing the hatched
// shadow of a recess along its top and left edges, a lamp its light.
// Focus is a dashed sky marquee standing off the box, as a selection is
// marked on a drawing; the red pencil marks only danger.
mod.widgets.Button.padding = mod.turtle.Inset{left: 11.0 right: 11.0 top: 4.0 bottom: 4.0}
mod.widgets.Button.draw_bg.border_radius = 2.0
mod.widgets.Button.draw_bg.border_size = 0.67
// A ROW OF FIELDS IS ONE HEIGHT: the box metrics are stated once here and
// handed to each field below, and the buttons take the same 26 point row.
let field_height = 26.0
let field_pad_v = 4.0
let field_radius = 2.0
let field_border = 0.67
// 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.TextInputFlat)
field_face(mod.widgets.TextInputFlat.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 = #154577
// Marks leave the layout box: plain views stop clipping so a focus
// marquee, a hatched side or a leader line can fall into the gap beside a
// control. Scroll views still clip.
mod.widgets.View.clip_x = false
mod.widgets.View.clip_y = false
// ---------------------------------------------------------------------------
// SHARED PARTS. Distances in points, negative inside, y down; `px` is one
// device pixel in points. The inks: paper #154577, face #11416f (the paper
// a shade deeper), lifted face #1a4e84, held face #0d3a68, well #0f3d6c,
// chosen wash #2a679f, line ink #e3f1ff, sky ink #9cd8fa, dimension ink
// #91c5f7, disabled line #6f93bd, red pencil #f46b66, amber pencil
// #ffd166, green #8ef0b8.
let bp_paper = fn() -> vec3 { return vec3(0.082, 0.271, 0.467) }
let bp_face = fn() -> vec3 { return vec3(0.067, 0.255, 0.435) }
let bp_face_hi = fn() -> vec3 { return vec3(0.102, 0.306, 0.518) }
let bp_face_lo = fn() -> vec3 { return vec3(0.051, 0.227, 0.408) }
let bp_well = fn() -> vec3 { return vec3(0.059, 0.239, 0.424) }
let bp_wash = fn() -> vec3 { return vec3(0.165, 0.404, 0.624) }
let bp_ink = fn() -> vec3 { return vec3(0.890, 0.945, 1.0) }
let bp_white = fn() -> vec3 { return vec3(1.0, 1.0, 1.0) }
let bp_sky = fn() -> vec3 { return vec3(0.612, 0.847, 0.980) }
let bp_sky_hi = fn() -> vec3 { return vec3(0.769, 0.914, 1.0) }
let bp_dimc = fn() -> vec3 { return vec3(0.569, 0.773, 0.969) }
let bp_off = fn() -> vec3 { return vec3(0.435, 0.576, 0.741) }
let bp_red = fn() -> vec3 { return vec3(0.957, 0.420, 0.400) }
let bp_green = fn() -> vec3 { return vec3(0.557, 0.941, 0.722) }
let bp_amber = fn() -> vec3 { return vec3(1.0, 0.820, 0.400) }
// Ink `ink` laid at coverage `a` over the premultiplied `under`.
let bp_put = fn(under: vec4, ink: vec3, a: float) -> vec4 {
return mix(under, vec4(ink, 1.0), clamp(a, 0.0, 1.0))
}
// The near and far corners of a control's box on whole device pixels, in
// its own points.
let bp_lo = fn(o: vec2, px: float) -> vec2 {
return vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o
}
let bp_hi = fn(o: vec2, s: vec2, px: float) -> vec2 {
return vec2(Finish.snap(o.x + s.x, px), Finish.snap(o.y + s.y, px)) - o
}
// Distance to a segment.
let bp_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.000001), 0.0, 1.0)
return length(pa - ba * t)
}
// Hairline hatching at 45 degrees, rising to the right, `pitch` points
// apart: the section lining of a drawing. Counted in the caller's own
// points, so it never moves with the page.
let bp_hatch = fn(p: vec2, pitch: float, px: float) -> float {
let u = (p.x + p.y) * 0.70710678
let t = abs(u - pitch * floor(u / pitch + 0.5))
return Finish.cover(t - 0.5 * px, px)
}
// Dashes along a run `t` points long: on for `duty` of every `period`.
let bp_dash = fn(t: float, period: float, duty: float) -> float {
return step(fract(t / period), duty)
}
// The focus marquee: a dashed sky hairline 2.5 points outside the box
// (centre `c`, half size `h`, corner `r`), laid over `under`.
let bp_focus = fn(under: vec4, p: vec2, c: vec2, h: vec2, r: float, px: float, focus: float) -> vec4 {
let off = Finish.snap(2.5, px)
let d = Material.sd_box(p, c, h + vec2(off, off), r + off)
let dash = bp_dash(p.x + p.y, 4.0, 0.55)
return bp_put(under, bp_sky(), Finish.band(d, 0.0, px, px) * dash * focus)
}
// A filled arrowhead, its tip at `tip`, pointing along the unit `dir`,
// `len` long and `hw` either side at its base. Coverage.
let bp_arrow = fn(p: vec2, tip: vec2, dir: vec2, len: float, hw: float, px: float) -> float {
let q = p - tip
let along = 0.0 - dot(q, dir)
let across = abs(q.x * dir.y - q.y * dir.x)
let k = hw / len
let side = (across - along * k) / sqrt(1.0 + k * k)
let d = max(max(0.0 - along, along - len), side)
return Finish.cover(d, px)
}
// The recess a held thing is drawn as: section hatching inside its top
// and left edges, `w` points deep, inside the line `lw` of the box `d`.
let bp_recess = fn(p: vec2, lo: vec2, d: float, lw: float, w: float, px: float) -> float {
let rim = min(step(p.y, lo.y + lw + w) + step(p.x, lo.x + lw + w), 1.0)
return rim * Finish.cover(d + lw, px) * bp_hatch(p, 2.5, px)
}
// THE LETTERING. A single-stroke drafting hand, as a draughtsman's
// lettering guide draws it: each character a few straight strokes in a
// cell 4 units wide and 6 tall (y down), returned as the distance in units
// from `q` to the nearest stroke. The codes: 0-9 the digits, 10-35 A-Z,
// 36 minus, 37 point, 38 slash, 39 colon, 40 space.
let bp_glyph = fn(c: float, q: vec2) -> float {
var d = 100.0
if min(abs(c - 0.0), abs(c - 24.0)) < 0.5 {
d = min(d, bp_seg(q, vec2(1.2, 0.0), vec2(2.8, 0.0)))
d = min(d, bp_seg(q, vec2(2.8, 0.0), vec2(4.0, 1.3)))
d = min(d, bp_seg(q, vec2(4.0, 1.3), vec2(4.0, 4.7)))
d = min(d, bp_seg(q, vec2(4.0, 4.7), vec2(2.8, 6.0)))
d = min(d, bp_seg(q, vec2(2.8, 6.0), vec2(1.2, 6.0)))
d = min(d, bp_seg(q, vec2(1.2, 6.0), vec2(0.0, 4.7)))
d = min(d, bp_seg(q, vec2(0.0, 4.7), vec2(0.0, 1.3)))
d = min(d, bp_seg(q, vec2(0.0, 1.3), vec2(1.2, 0.0)))
}
if abs(c - 1.0) < 0.5 {
d = min(d, bp_seg(q, vec2(0.8, 1.2), vec2(2.2, 0.0)))
d = min(d, bp_seg(q, vec2(2.2, 0.0), vec2(2.2, 6.0)))
}
if abs(c - 2.0) < 0.5 {
d = min(d, bp_seg(q, vec2(0.0, 1.3), vec2(1.0, 0.1)))
d = min(d, bp_seg(q, vec2(1.0, 0.1), vec2(3.0, 0.1)))
d = min(d, bp_seg(q, vec2(3.0, 0.1), vec2(4.0, 1.2)))
d = min(d, bp_seg(q, vec2(4.0, 1.2), vec2(4.0, 2.3)))
d = min(d, bp_seg(q, vec2(4.0, 2.3), vec2(0.0, 6.0)))
d = min(d, bp_seg(q, vec2(0.0, 6.0), vec2(4.0, 6.0)))
}
if abs(c - 3.0) < 0.5 {
d = min(d, bp_seg(q, vec2(0.0, 0.8), vec2(1.0, 0.0)))
d = min(d, bp_seg(q, vec2(1.0, 0.0), vec2(3.0, 0.0)))
d = min(d, bp_seg(q, vec2(3.0, 0.0), vec2(4.0, 1.0)))
d = min(d, bp_seg(q, vec2(4.0, 1.0), vec2(4.0, 2.0)))
d = min(d, bp_seg(q, vec2(4.0, 2.0), vec2(3.0, 2.9)))
d = min(d, bp_seg(q, vec2(3.0, 2.9), vec2(1.4, 2.9)))
d = min(d, bp_seg(q, vec2(3.0, 2.9), vec2(4.0, 3.9)))
d = min(d, bp_seg(q, vec2(4.0, 3.9), vec2(4.0, 5.0)))
d = min(d, bp_seg(q, vec2(4.0, 5.0), vec2(3.0, 6.0)))
d = min(d, bp_seg(q, vec2(3.0, 6.0), vec2(1.0, 6.0)))
d = min(d, bp_seg(q, vec2(1.0, 6.0), vec2(0.0, 5.2)))
}
if abs(c - 4.0) < 0.5 {
d = min(d, bp_seg(q, vec2(3.0, 6.0), vec2(3.0, 0.0)))
d = min(d, bp_seg(q, vec2(3.0, 0.0), vec2(0.0, 4.2)))
d = min(d, bp_seg(q, vec2(0.0, 4.2), vec2(4.0, 4.2)))
}
if abs(c - 5.0) < 0.5 {
d = min(d, bp_seg(q, vec2(4.0, 0.0), vec2(0.4, 0.0)))
d = min(d, bp_seg(q, vec2(0.4, 0.0), vec2(0.1, 2.8)))
d = min(d, bp_seg(q, vec2(0.1, 2.8), vec2(1.2, 2.4)))
d = min(d, bp_seg(q, vec2(1.2, 2.4), vec2(3.0, 2.4)))
d = min(d, bp_seg(q, vec2(3.0, 2.4), vec2(4.0, 3.5)))
d = min(d, bp_seg(q, vec2(4.0, 3.5), vec2(4.0, 5.0)))
d = min(d, bp_seg(q, vec2(4.0, 5.0), vec2(3.0, 6.0)))
d = min(d, bp_seg(q, vec2(3.0, 6.0), vec2(1.0, 6.0)))
d = min(d, bp_seg(q, vec2(1.0, 6.0), vec2(0.0, 5.2)))
}
if abs(c - 6.0) < 0.5 {
d = min(d, bp_seg(q, vec2(3.7, 0.6), vec2(2.9, 0.0)))
d = min(d, bp_seg(q, vec2(2.9, 0.0), vec2(1.2, 0.0)))
d = min(d, bp_seg(q, vec2(1.2, 0.0), vec2(0.0, 1.4)))
d = min(d, bp_seg(q, vec2(0.0, 1.4), vec2(0.0, 4.7)))
d = min(d, bp_seg(q, vec2(0.0, 4.7), vec2(1.2, 6.0)))
d = min(d, bp_seg(q, vec2(1.2, 6.0), vec2(2.8, 6.0)))
d = min(d, bp_seg(q, vec2(2.8, 6.0), vec2(4.0, 4.8)))
d = min(d, bp_seg(q, vec2(4.0, 4.8), vec2(4.0, 3.8)))
d = min(d, bp_seg(q, vec2(4.0, 3.8), vec2(2.8, 2.7)))
d = min(d, bp_seg(q, vec2(2.8, 2.7), vec2(1.2, 2.7)))
d = min(d, bp_seg(q, vec2(1.2, 2.7), vec2(0.0, 3.8)))
}
if abs(c - 7.0) < 0.5 {
d = min(d, bp_seg(q, vec2(0.0, 0.0), vec2(4.0, 0.0)))
d = min(d, bp_seg(q, vec2(4.0, 0.0), vec2(1.5, 6.0)))
}
if abs(c - 8.0) < 0.5 {
d = min(d, bp_seg(q, vec2(1.2, 0.0), vec2(2.8, 0.0)))
d = min(d, bp_seg(q, vec2(2.8, 0.0), vec2(3.7, 0.8)))
d = min(d, bp_seg(q, vec2(3.7, 0.8), vec2(3.7, 2.1)))
d = min(d, bp_seg(q, vec2(3.7, 2.1), vec2(2.8, 2.9)))
d = min(d, bp_seg(q, vec2(2.8, 2.9), vec2(1.2, 2.9)))
d = min(d, bp_seg(q, vec2(1.2, 2.9), vec2(0.3, 2.1)))
d = min(d, bp_seg(q, vec2(0.3, 2.1), vec2(0.3, 0.8)))
d = min(d, bp_seg(q, vec2(0.3, 0.8), vec2(1.2, 0.0)))
d = min(d, bp_seg(q, vec2(1.2, 2.9), vec2(2.8, 2.9)))
d = min(d, bp_seg(q, vec2(2.8, 2.9), vec2(4.0, 4.0)))
d = min(d, bp_seg(q, vec2(4.0, 4.0), vec2(4.0, 5.0)))
d = min(d, bp_seg(q, vec2(4.0, 5.0), vec2(3.0, 6.0)))
d = min(d, bp_seg(q, vec2(3.0, 6.0), vec2(1.0, 6.0)))
d = min(d, bp_seg(q, vec2(1.0, 6.0), vec2(0.0, 5.0)))
d = min(d, bp_seg(q, vec2(0.0, 5.0), vec2(0.0, 4.0)))
d = min(d, bp_seg(q, vec2(0.0, 4.0), vec2(1.2, 2.9)))
}
if abs(c - 9.0) < 0.5 {
d = min(d, bp_seg(q, vec2(0.3, 5.4), vec2(1.1, 6.0)))
d = min(d, bp_seg(q, vec2(1.1, 6.0), vec2(2.8, 6.0)))
d = min(d, bp_seg(q, vec2(2.8, 6.0), vec2(4.0, 4.6)))
d = min(d, bp_seg(q, vec2(4.0, 4.6), vec2(4.0, 1.3)))
d = min(d, bp_seg(q, vec2(4.0, 1.3), vec2(2.8, 0.0)))
d = min(d, bp_seg(q, vec2(2.8, 0.0), vec2(1.2, 0.0)))
d = min(d, bp_seg(q, vec2(1.2, 0.0), vec2(0.0, 1.2)))
d = min(d, bp_seg(q, vec2(0.0, 1.2), vec2(0.0, 2.2)))
d = min(d, bp_seg(q, vec2(0.0, 2.2), vec2(1.2, 3.3)))
d = min(d, bp_seg(q, vec2(1.2, 3.3), vec2(2.8, 3.3)))
d = min(d, bp_seg(q, vec2(2.8, 3.3), vec2(4.0, 2.2)))
}
if abs(c - 10.0) < 0.5 {
d = min(d, bp_seg(q, vec2(0.0, 6.0), vec2(2.0, 0.0)))
d = min(d, bp_seg(q, vec2(2.0, 0.0), vec2(4.0, 6.0)))
d = min(d, bp_seg(q, vec2(0.7, 4.0), vec2(3.3, 4.0)))
}
if abs(c - 11.0) < 0.5 {
d = min(d, bp_seg(q, vec2(0.0, 3.0), vec2(3.0, 3.0)))
d = min(d, bp_seg(q, vec2(3.0, 3.0), vec2(4.0, 4.0)))
d = min(d, bp_seg(q, vec2(4.0, 4.0), vec2(4.0, 5.0)))
d = min(d, bp_seg(q, vec2(4.0, 5.0), vec2(3.0, 6.0)))
d = min(d, bp_seg(q, vec2(3.0, 6.0), vec2(0.0, 6.0)))
d = min(d, bp_seg(q, vec2(0.0, 6.0), vec2(0.0, 0.0)))
d = min(d, bp_seg(q, vec2(0.0, 0.0), vec2(2.8, 0.0)))
d = min(d, bp_seg(q, vec2(2.8, 0.0), vec2(3.7, 0.8)))
d = min(d, bp_seg(q, vec2(3.7, 0.8), vec2(3.7, 2.2)))
d = min(d, bp_seg(q, vec2(3.7, 2.2), vec2(2.9, 3.0)))
}
if abs(c - 12.0) < 0.5 {
d = min(d, bp_seg(q, vec2(4.0, 1.0), vec2(3.0, 0.0)))
d = min(d, bp_seg(q, vec2(3.0, 0.0), vec2(1.0, 0.0)))
d = min(d, bp_seg(q, vec2(1.0, 0.0), vec2(0.0, 1.0)))
d = min(d, bp_seg(q, vec2(0.0, 1.0), vec2(0.0, 5.0)))
d = min(d, bp_seg(q, vec2(0.0, 5.0), vec2(1.0, 6.0)))
d = min(d, bp_seg(q, vec2(1.0, 6.0), vec2(3.0, 6.0)))
d = min(d, bp_seg(q, vec2(3.0, 6.0), vec2(4.0, 5.0)))
}
if abs(c - 13.0) < 0.5 {
d = min(d, bp_seg(q, vec2(0.0, 0.0), vec2(0.0, 6.0)))
d = min(d, bp_seg(q, vec2(0.0, 6.0), vec2(2.4, 6.0)))
d = min(d, bp_seg(q, vec2(2.4, 6.0), vec2(4.0, 4.4)))
d = min(d, bp_seg(q, vec2(4.0, 4.4), vec2(4.0, 1.6)))
d = min(d, bp_seg(q, vec2(4.0, 1.6), vec2(2.4, 0.0)))
d = min(d, bp_seg(q, vec2(2.4, 0.0), vec2(0.0, 0.0)))
}
if abs(c - 14.0) < 0.5 {
d = min(d, bp_seg(q, vec2(4.0, 0.0), vec2(0.0, 0.0)))
d = min(d, bp_seg(q, vec2(0.0, 0.0), vec2(0.0, 6.0)))
d = min(d, bp_seg(q, vec2(0.0, 6.0), vec2(4.0, 6.0)))
d = min(d, bp_seg(q, vec2(0.0, 3.0), vec2(2.8, 3.0)))
}
if abs(c - 15.0) < 0.5 {
d = min(d, bp_seg(q, vec2(4.0, 0.0), vec2(0.0, 0.0)))
d = min(d, bp_seg(q, vec2(0.0, 0.0), vec2(0.0, 6.0)))
d = min(d, bp_seg(q, vec2(0.0, 3.0), vec2(2.8, 3.0)))
}
if abs(c - 16.0) < 0.5 {
d = min(d, bp_seg(q, vec2(4.0, 1.0), vec2(3.0, 0.0)))
d = min(d, bp_seg(q, vec2(3.0, 0.0), vec2(1.0, 0.0)))
d = min(d, bp_seg(q, vec2(1.0, 0.0), vec2(0.0, 1.0)))
d = min(d, bp_seg(q, vec2(0.0, 1.0), vec2(0.0, 5.0)))
d = min(d, bp_seg(q, vec2(0.0, 5.0), vec2(1.0, 6.0)))
d = min(d, bp_seg(q, vec2(1.0, 6.0), vec2(3.0, 6.0)))
d = min(d, bp_seg(q, vec2(3.0, 6.0), vec2(4.0, 5.0)))
d = min(d, bp_seg(q, vec2(4.0, 5.0), vec2(4.0, 3.4)))
d = min(d, bp_seg(q, vec2(4.0, 3.4), vec2(2.2, 3.4)))
}
if abs(c - 17.0) < 0.5 {
d = min(d, bp_seg(q, vec2(0.0, 0.0), vec2(0.0, 6.0)))
d = min(d, bp_seg(q, vec2(4.0, 0.0), vec2(4.0, 6.0)))
d = min(d, bp_seg(q, vec2(0.0, 3.0), vec2(4.0, 3.0)))
}
if abs(c - 18.0) < 0.5 {
d = min(d, bp_seg(q, vec2(2.0, 0.0), vec2(2.0, 6.0)))
d = min(d, bp_seg(q, vec2(1.0, 0.0), vec2(3.0, 0.0)))
d = min(d, bp_seg(q, vec2(1.0, 6.0), vec2(3.0, 6.0)))
}
if abs(c - 20.0) < 0.5 {
d = min(d, bp_seg(q, vec2(0.0, 0.0), vec2(0.0, 6.0)))
d = min(d, bp_seg(q, vec2(4.0, 0.0), vec2(0.0, 3.8)))
d = min(d, bp_seg(q, vec2(1.4, 2.7), vec2(4.0, 6.0)))
}
if abs(c - 21.0) < 0.5 {
d = min(d, bp_seg(q, vec2(0.0, 0.0), vec2(0.0, 6.0)))
d = min(d, bp_seg(q, vec2(0.0, 6.0), vec2(4.0, 6.0)))
}
if abs(c - 22.0) < 0.5 {
d = min(d, bp_seg(q, vec2(0.0, 6.0), vec2(0.0, 0.0)))
d = min(d, bp_seg(q, vec2(0.0, 0.0), vec2(2.0, 3.6)))
d = min(d, bp_seg(q, vec2(2.0, 3.6), vec2(4.0, 0.0)))
d = min(d, bp_seg(q, vec2(4.0, 0.0), vec2(4.0, 6.0)))
}
if abs(c - 23.0) < 0.5 {
d = min(d, bp_seg(q, vec2(0.0, 6.0), vec2(0.0, 0.0)))
d = min(d, bp_seg(q, vec2(0.0, 0.0), vec2(4.0, 6.0)))
d = min(d, bp_seg(q, vec2(4.0, 6.0), vec2(4.0, 0.0)))
}
if abs(c - 25.0) < 0.5 {
d = min(d, bp_seg(q, vec2(0.0, 6.0), vec2(0.0, 0.0)))
d = min(d, bp_seg(q, vec2(0.0, 0.0), vec2(3.0, 0.0)))
d = min(d, bp_seg(q, vec2(3.0, 0.0), vec2(4.0, 1.0)))
d = min(d, bp_seg(q, vec2(4.0, 1.0), vec2(4.0, 2.2)))
d = min(d, bp_seg(q, vec2(4.0, 2.2), vec2(3.0, 3.2)))
d = min(d, bp_seg(q, vec2(3.0, 3.2), vec2(0.0, 3.2)))
}
if abs(c - 27.0) < 0.5 {
d = min(d, bp_seg(q, vec2(0.0, 6.0), vec2(0.0, 0.0)))
d = min(d, bp_seg(q, vec2(0.0, 0.0), vec2(3.0, 0.0)))
d = min(d, bp_seg(q, vec2(3.0, 0.0), vec2(4.0, 1.0)))
d = min(d, bp_seg(q, vec2(4.0, 1.0), vec2(4.0, 2.2)))
d = min(d, bp_seg(q, vec2(4.0, 2.2), vec2(3.0, 3.2)))
d = min(d, bp_seg(q, vec2(3.0, 3.2), vec2(0.0, 3.2)))
d = min(d, bp_seg(q, vec2(2.0, 3.2), vec2(4.0, 6.0)))
}
if abs(c - 28.0) < 0.5 {
d = min(d, bp_seg(q, vec2(4.0, 1.0), vec2(3.0, 0.0)))
d = min(d, bp_seg(q, vec2(3.0, 0.0), vec2(1.0, 0.0)))
d = min(d, bp_seg(q, vec2(1.0, 0.0), vec2(0.0, 1.0)))
d = min(d, bp_seg(q, vec2(0.0, 1.0), vec2(0.0, 2.0)))
d = min(d, bp_seg(q, vec2(0.0, 2.0), vec2(1.0, 3.0)))
d = min(d, bp_seg(q, vec2(1.0, 3.0), vec2(3.0, 3.0)))
d = min(d, bp_seg(q, vec2(3.0, 3.0), vec2(4.0, 4.0)))
d = min(d, bp_seg(q, vec2(4.0, 4.0), vec2(4.0, 5.0)))
d = min(d, bp_seg(q, vec2(4.0, 5.0), vec2(3.0, 6.0)))
d = min(d, bp_seg(q, vec2(3.0, 6.0), vec2(1.0, 6.0)))
d = min(d, bp_seg(q, vec2(1.0, 6.0), vec2(0.0, 5.0)))
}
if abs(c - 29.0) < 0.5 {
d = min(d, bp_seg(q, vec2(0.0, 0.0), vec2(4.0, 0.0)))
d = min(d, bp_seg(q, vec2(2.0, 0.0), vec2(2.0, 6.0)))
}
if abs(c - 30.0) < 0.5 {
d = min(d, bp_seg(q, vec2(0.0, 0.0), vec2(0.0, 5.0)))
d = min(d, bp_seg(q, vec2(0.0, 5.0), vec2(1.0, 6.0)))
d = min(d, bp_seg(q, vec2(1.0, 6.0), vec2(3.0, 6.0)))
d = min(d, bp_seg(q, vec2(3.0, 6.0), vec2(4.0, 5.0)))
d = min(d, bp_seg(q, vec2(4.0, 5.0), vec2(4.0, 0.0)))
}
if abs(c - 31.0) < 0.5 {
d = min(d, bp_seg(q, vec2(0.0, 0.0), vec2(2.0, 6.0)))
d = min(d, bp_seg(q, vec2(2.0, 6.0), vec2(4.0, 0.0)))
}
if abs(c - 32.0) < 0.5 {
d = min(d, bp_seg(q, vec2(0.0, 0.0), vec2(1.0, 6.0)))
d = min(d, bp_seg(q, vec2(1.0, 6.0), vec2(2.0, 2.0)))
d = min(d, bp_seg(q, vec2(2.0, 2.0), vec2(3.0, 6.0)))
d = min(d, bp_seg(q, vec2(3.0, 6.0), vec2(4.0, 0.0)))
}
if abs(c - 33.0) < 0.5 {
d = min(d, bp_seg(q, vec2(0.0, 0.0), vec2(4.0, 6.0)))
d = min(d, bp_seg(q, vec2(4.0, 0.0), vec2(0.0, 6.0)))
}
if abs(c - 34.0) < 0.5 {
d = min(d, bp_seg(q, vec2(0.0, 0.0), vec2(2.0, 3.0)))
d = min(d, bp_seg(q, vec2(2.0, 3.0), vec2(4.0, 0.0)))
d = min(d, bp_seg(q, vec2(2.0, 3.0), vec2(2.0, 6.0)))
}
if abs(c - 35.0) < 0.5 {
d = min(d, bp_seg(q, vec2(0.0, 0.0), vec2(4.0, 0.0)))
d = min(d, bp_seg(q, vec2(4.0, 0.0), vec2(0.0, 6.0)))
d = min(d, bp_seg(q, vec2(0.0, 6.0), vec2(4.0, 6.0)))
}
if abs(c - 36.0) < 0.5 {
d = min(d, bp_seg(q, vec2(0.8, 3.2), vec2(3.2, 3.2)))
}
if abs(c - 38.0) < 0.5 {
d = min(d, bp_seg(q, vec2(3.6, 0.0), vec2(0.4, 6.0)))
}
if abs(c - 37.0) < 0.5 {
d = min(d, length(q - vec2(2.0, 5.9)) - 0.35)
}
if abs(c - 39.0) < 0.5 {
d = min(d, length(q - vec2(2.0, 1.9)) - 0.35)
d = min(d, length(q - vec2(2.0, 5.9)) - 0.35)
}
return d
}
// The strings the sheet letters, by number, character by character.
let bp_str = fn(s: float, i: float) -> float {
var c = 40.0
if abs(s - 0.0) < 0.5 {
if abs(i - 0.0) < 0.5 { c = 16.0 }
if abs(i - 1.0) < 0.5 { c = 14.0 }
if abs(i - 2.0) < 0.5 { c = 23.0 }
if abs(i - 3.0) < 0.5 { c = 14.0 }
if abs(i - 4.0) < 0.5 { c = 27.0 }
if abs(i - 5.0) < 0.5 { c = 10.0 }
if abs(i - 6.0) < 0.5 { c = 21.0 }
if abs(i - 8.0) < 0.5 { c = 10.0 }
if abs(i - 9.0) < 0.5 { c = 27.0 }
if abs(i - 10.0) < 0.5 { c = 27.0 }
if abs(i - 11.0) < 0.5 { c = 10.0 }
if abs(i - 12.0) < 0.5 { c = 23.0 }
if abs(i - 13.0) < 0.5 { c = 16.0 }
if abs(i - 14.0) < 0.5 { c = 14.0 }
if abs(i - 15.0) < 0.5 { c = 22.0 }
if abs(i - 16.0) < 0.5 { c = 14.0 }
if abs(i - 17.0) < 0.5 { c = 23.0 }
if abs(i - 18.0) < 0.5 { c = 29.0 }
}
if abs(s - 1.0) < 0.5 {
if abs(i - 0.0) < 0.5 { c = 29.0 }
if abs(i - 1.0) < 0.5 { c = 18.0 }
if abs(i - 2.0) < 0.5 { c = 29.0 }
if abs(i - 3.0) < 0.5 { c = 21.0 }
if abs(i - 4.0) < 0.5 { c = 14.0 }
}
if abs(s - 2.0) < 0.5 {
if abs(i - 0.0) < 0.5 { c = 13.0 }
if abs(i - 1.0) < 0.5 { c = 32.0 }
if abs(i - 2.0) < 0.5 { c = 16.0 }
if abs(i - 4.0) < 0.5 { c = 23.0 }
if abs(i - 5.0) < 0.5 { c = 24.0 }
}
if abs(s - 3.0) < 0.5 {
if abs(i - 0.0) < 0.5 { c = 10.0 }
if abs(i - 1.0) < 0.5 { c = 36.0 }
if abs(i - 2.0) < 0.5 { c = 0.0 }
if abs(i - 3.0) < 0.5 { c = 0.0 }
if abs(i - 4.0) < 0.5 { c = 1.0 }
}
if abs(s - 4.0) < 0.5 {
if abs(i - 0.0) < 0.5 { c = 28.0 }
if abs(i - 1.0) < 0.5 { c = 12.0 }
if abs(i - 2.0) < 0.5 { c = 10.0 }
if abs(i - 3.0) < 0.5 { c = 21.0 }
if abs(i - 4.0) < 0.5 { c = 14.0 }
}
if abs(s - 5.0) < 0.5 {
if abs(i - 0.0) < 0.5 { c = 1.0 }
if abs(i - 1.0) < 0.5 { c = 39.0 }
if abs(i - 2.0) < 0.5 { c = 1.0 }
}
if abs(s - 6.0) < 0.5 {
if abs(i - 0.0) < 0.5 { c = 28.0 }
if abs(i - 1.0) < 0.5 { c = 17.0 }
if abs(i - 2.0) < 0.5 { c = 14.0 }
if abs(i - 3.0) < 0.5 { c = 14.0 }
if abs(i - 4.0) < 0.5 { c = 29.0 }
}
if abs(s - 7.0) < 0.5 {
if abs(i - 0.0) < 0.5 { c = 1.0 }
if abs(i - 1.0) < 0.5 { c = 38.0 }
if abs(i - 2.0) < 0.5 { c = 1.0 }
}
if abs(s - 8.0) < 0.5 {
if abs(i - 0.0) < 0.5 { c = 27.0 }
if abs(i - 1.0) < 0.5 { c = 14.0 }
if abs(i - 2.0) < 0.5 { c = 31.0 }
}
if abs(s - 9.0) < 0.5 {
if abs(i - 0.0) < 0.5 { c = 10.0 }
}
return c
}
// The number of characters in each string.
let bp_str_len = fn(s: float) -> float {
var n = 0.0
if abs(s - 0.0) < 0.5 { n = 19.0 }
if abs(s - 1.0) < 0.5 { n = 5.0 }
if abs(s - 2.0) < 0.5 { n = 6.0 }
if abs(s - 3.0) < 0.5 { n = 5.0 }
if abs(s - 4.0) < 0.5 { n = 5.0 }
if abs(s - 5.0) < 0.5 { n = 3.0 }
if abs(s - 6.0) < 0.5 { n = 5.0 }
if abs(s - 7.0) < 0.5 { n = 3.0 }
if abs(s - 8.0) < 0.5 { n = 3.0 }
if abs(s - 9.0) < 0.5 { n = 1.0 }
return n
}
// Lettering in that hand: string `s` from its top left `o`, capitals `cap`
// points tall, strokes `w` points wide. Coverage.
let bp_text = fn(p: vec2, o: vec2, cap: float, s: float, w: float, px: float) -> float {
let k = cap / 6.0
let u = (p - o) / k
let i = floor((u.x + 0.7) / 5.4)
let n = bp_str_len(s)
let live = step(0.0, i) * step(i, n - 1.0) * step(-1.5, u.y) * step(u.y, 7.5)
var cov = 0.0
if live > 0.5 {
let q = vec2(u.x - i * 5.4, u.y)
cov = Finish.cover(bp_glyph(bp_str(s, i), q) * k - 0.5 * w, px)
}
return cov
}
// One character `ch` in the same hand, its cell's top left at `o`.
let bp_char = fn(p: vec2, o: vec2, cap: float, ch: float, w: float, px: float) -> float {
let k = cap / 6.0
let q = (p - o) / k
let live = step(-1.5, q.x) * step(q.x, 5.5) * step(-1.5, q.y) * step(q.y, 7.5)
var cov = 0.0
if live > 0.5 {
cov = Finish.cover(bp_glyph(ch, q) * k - 0.5 * w, px)
}
return cov
}
// A whole number `v` (up to four figures) in the same hand, centred on
// `ctr`: the figure a dimension line carries.
let bp_num = fn(p: vec2, ctr: vec2, cap: float, v: float, w: float, px: float) -> float {
let k = cap / 6.0
let vv = floor(v + 0.5)
let nd = 1.0 + step(9.5, vv) + step(99.5, vv) + step(999.5, vv)
let o = ctr - vec2((nd * 5.4 - 1.4) * k * 0.5, cap * 0.5)
let u = (p - o) / k
let i = floor((u.x + 0.7) / 5.4)
let live = step(0.0, i) * step(i, nd - 1.0) * step(-1.5, u.y) * step(u.y, 7.5)
var cov = 0.0
if live > 0.5 {
let dg = floor(vv / pow(10.0, nd - 1.0 - i)) - 10.0 * floor(vv / pow(10.0, nd - i))
let q = vec2(u.x - i * 5.4, u.y)
cov = Finish.cover(bp_glyph(dg, q) * k - 0.5 * w, px)
}
return cov
}
// The wait mark a replaced face draws itself: twelve short ticks on a ring
// where the label was, the one at the head in full ink on the button's own
// animation clock, the rest fading behind it.
let bp_wait = fn(under: vec4, p: vec2, lo: vec2, hi: vec2, px: float, loading: float, t: float) -> vec4 {
let c = (lo + hi) * 0.5
let r = clamp((hi.y - lo.y) * 0.3, 3.0, 8.0)
let q = p - c
let a = fract(atan2(q.y, q.x) / 6.2831853 + 0.25)
let slot = floor(a * 12.0 + 0.5)
let ang = slot / 12.0 * 6.2831853 - 1.5707963
let dir = vec2(cos(ang), sin(ang))
let s = bp_seg(q, dir * (r - 2.5), dir * r)
let head = fract(slot / 12.0 - t)
let lit = mix(0.2, 1.0, pow(1.0 - head, 3.0))
let cov = Finish.cover(s - 0.5 * px, px) * loading
return bp_put(under, mix(bp_off(), bp_ink(), lit), cov)
}
// ---------------------------------------------------------------------------
// BUTTONS. One face for the family; `style` picks the dressing:
// 0 standard (the face under a two-pixel line), 1 primary (the chosen one:
// the pale wash under a three-pixel line of sky), 2 secondary (the line in
// sky), 3 danger (the line dashed in red pencil), 4 outline (the line
// alone, the grid showing through), 5 flat and 6 flatter (nothing until the
// pointer comes, then a hairline), 7 tertiary (the face under a hairline),
// 8 dashed (the outline in dashes).
// Under the pointer the line turns full white and the face lifts a shade;
// held, the face goes a shade deeper and the recess hatch runs inside its
// top and left edges; disabled, the line is dashed in the dim ink. The
// quad is grown by 6 points for the focus marquee.
let bp_button = fn(p: vec2, o: vec2, s: vec2, px: float, hover: float, down: float, focus: float, disabled: float, style: float) -> vec4 {
let lo = bp_lo(o, px)
let hi = bp_hi(o, s, px)
let c = (lo + hi) * 0.5
let h = (hi - lo) * 0.5
let r = 2.0
let d = Material.sd_box(p, c, h, r)
let live = 1.0 - disabled
let hv = hover * live
let dn = down * live
let hero = step(0.5, style) * step(style, 1.5)
let armed = step(1.5, style) * step(style, 2.5)
let danger = step(2.5, style) * step(style, 3.5)
let outline = step(3.5, style) * step(style, 4.5)
let flat = step(4.5, style) * step(style, 6.5)
let thin = step(6.5, style) * step(style, 7.5)
let dashed = step(7.5, style)
let inside = Finish.cover(d, px)
// The face.
var face = mix(bp_face(), bp_face_hi(), hv)
face = mix(face, bp_face_lo(), dn)
let hface = mix(mix(bp_wash(), vec3(0.200, 0.455, 0.682), hv), vec3(0.137, 0.365, 0.576), dn)
face = mix(face, hface, hero)
var fa = 0.9
fa = mix(fa, max(0.5 * hv, 0.8 * dn), max(max(outline, flat), dashed))
fa = mix(fa, 0.4, disabled)
var out = vec4(face * fa * inside, fa * inside)
// The line's weight.
var lw = 2.0 * px
lw = mix(lw, 3.0 * px, hero)
lw = mix(lw, px, max(thin, flat))
// Held: the recess.
out = bp_put(out, bp_ink(), bp_recess(p, lo, d, lw, 3.0, px) * dn * 0.55)
// The line.
var lc = mix(bp_ink(), bp_white(), hv)
lc = mix(lc, mix(bp_sky(), bp_sky_hi(), hv), max(hero, armed))
lc = mix(lc, mix(bp_red(), vec3(1.0, 0.58, 0.55), hv), danger)
lc = mix(lc, bp_off(), disabled)
var shown = mix(1.0, max(hv, dn), flat)
let dash = bp_dash(p.x + p.y, 5.0, 0.6)
shown = shown * mix(1.0, dash, max(max(danger, dashed), disabled))
out = bp_put(out, lc, Finish.band(d, 0.0, lw, px) * shown)
return bp_focus(out, p, c, h, r, px, focus * live)
}
mod.widgets.ButtonFlat.clip_x = false
mod.widgets.ButtonFlat.clip_y = false
mod.widgets.ButtonFlat.padding = mod.turtle.Inset{left: 11.0 right: 11.0 top: 4.0 bottom: 4.0}
mod.widgets.ButtonFlat.min_height = 26.0
mod.widgets.ButtonFlat.icon_walk = Walk{width: 13.0, height: Fit}
mod.widgets.ButtonFlat.draw_bg.bp_room = uniform(6.0)
mod.widgets.ButtonFlat.draw_bg.vertex = fn() {
let m = self.bp_room
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
mod.widgets.ButtonFlat.draw_bg.pixel = fn() {
let m = self.bp_room
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 f = bp_button(p, self.rect_pos, self.rect_size, px, self.hover, self.down, self.focus, self.disabled, 5.0)
return self.host_over(bp_wait(f, p, vec2(0.0, 0.0), self.rect_size, px, self.loading, self.anim_time), m)
}
mod.widgets.Button.draw_bg.pixel = fn() {
let m = self.bp_room
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 f = bp_button(p, self.rect_pos, self.rect_size, px, self.hover, self.down, self.focus, self.disabled, 0.0)
return self.host_over(bp_wait(f, p, vec2(0.0, 0.0), self.rect_size, px, self.loading, self.anim_time), m)
}
// The size rungs and the compound are standard buttons. They share the
// flat button's draw object, so they are handed one of their own.
mod.widgets.ButtonXs.draw_bg = mod.widgets.Button.draw_bg{}
mod.widgets.ButtonSm.draw_bg = mod.widgets.Button.draw_bg{}
mod.widgets.ButtonLg.draw_bg = mod.widgets.Button.draw_bg{}
mod.widgets.ButtonXl.draw_bg = mod.widgets.Button.draw_bg{}
mod.widgets.ButtonCompound.draw_bg = mod.widgets.Button.draw_bg{}
mod.widgets.ButtonXs.min_height = 18.0
mod.widgets.ButtonSm.min_height = 22.0
mod.widgets.ButtonLg.min_height = 32.0
mod.widgets.ButtonXl.min_height = 38.0
mod.widgets.ButtonSm.padding = mod.turtle.Inset{left: 9.0 right: 9.0 top: 0.0 bottom: 0.0}
mod.widgets.ButtonXs.padding = mod.turtle.Inset{left: 7.0 right: 7.0 top: 0.0 bottom: 0.0}
mod.widgets.ButtonLg.padding = mod.turtle.Inset{left: 14.0 right: 14.0 top: 0.0 bottom: 0.0}
mod.widgets.ButtonIcon.padding = mod.turtle.Inset{left: 8.0 right: 8.0 top: 4.0 bottom: 4.0}
mod.widgets.ButtonPrimary.draw_bg.pixel = fn() {
let m = self.bp_room
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 f = bp_button(p, self.rect_pos, self.rect_size, px, self.hover, self.down, self.focus, self.disabled, 1.0)
return self.host_over(bp_wait(f, p, vec2(0.0, 0.0), self.rect_size, px, self.loading, self.anim_time), m)
}
mod.widgets.ButtonSecondary.draw_bg.pixel = fn() {
let m = self.bp_room
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 f = bp_button(p, self.rect_pos, self.rect_size, px, self.hover, self.down, self.focus, self.disabled, 2.0)
return self.host_over(bp_wait(f, p, vec2(0.0, 0.0), self.rect_size, px, self.loading, self.anim_time), m)
}
mod.widgets.ButtonTertiary.draw_bg.pixel = fn() {
let m = self.bp_room
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 f = bp_button(p, self.rect_pos, self.rect_size, px, self.hover, self.down, self.focus, self.disabled, 7.0)
return self.host_over(bp_wait(f, p, vec2(0.0, 0.0), self.rect_size, px, self.loading, self.anim_time), m)
}
mod.widgets.ButtonDanger.draw_bg.pixel = fn() {
let m = self.bp_room
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 f = bp_button(p, self.rect_pos, self.rect_size, px, self.hover, self.down, self.focus, self.disabled, 3.0)
return self.host_over(bp_wait(f, p, vec2(0.0, 0.0), self.rect_size, px, self.loading, self.anim_time), m)
}
mod.widgets.ButtonOutline.draw_bg.pixel = fn() {
let m = self.bp_room
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 f = bp_button(p, self.rect_pos, self.rect_size, px, self.hover, self.down, self.focus, self.disabled, 4.0)
return self.host_over(bp_wait(f, p, vec2(0.0, 0.0), self.rect_size, px, self.loading, self.anim_time), m)
}
mod.widgets.ButtonDashed.draw_bg.pixel = fn() {
let m = self.bp_room
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 f = bp_button(p, self.rect_pos, self.rect_size, px, self.hover, self.down, self.focus, self.disabled, 8.0)
return self.host_over(bp_wait(f, p, vec2(0.0, 0.0), self.rect_size, px, self.loading, self.anim_time), m)
}
mod.widgets.ButtonFlatter.draw_bg.pixel = fn() {
let m = self.bp_room
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 f = bp_button(p, self.rect_pos, self.rect_size, px, self.hover, self.down, self.focus, self.disabled, 6.0)
return self.host_over(bp_wait(f, p, vec2(0.0, 0.0), self.rect_size, px, self.loading, self.anim_time), m)
}
// Button inks: white on every face, the pale sky on the secondary, a pale
// rose on danger (the red pencil is the line, never the word), the dim ink
// when off.
mod.widgets.ButtonFlat.draw_text.color = #eef6ff
mod.widgets.ButtonFlat.draw_text.color_hover = #ffffff
mod.widgets.ButtonFlat.draw_text.color_down = #ffffff
mod.widgets.ButtonFlat.draw_text.color_focus = #eef6ff
mod.widgets.ButtonFlat.draw_text.color_disabled = #86a6cb
mod.widgets.ButtonPrimary.draw_text.color = #ffffff
mod.widgets.ButtonPrimary.draw_text.color_hover = #ffffff
mod.widgets.ButtonPrimary.draw_text.color_down = #ffffff
mod.widgets.ButtonPrimary.draw_text.color_focus = #ffffff
mod.widgets.ButtonPrimary.draw_text.color_disabled = #86a6cb
mod.widgets.ButtonPrimary.draw_icon.color = #ffffff
mod.widgets.ButtonSecondary.draw_text.color = #c4e9ff
mod.widgets.ButtonSecondary.draw_text.color_hover = #e6f6ff
mod.widgets.ButtonSecondary.draw_text.color_down = #e6f6ff
mod.widgets.ButtonSecondary.draw_text.color_focus = #c4e9ff
mod.widgets.ButtonSecondary.draw_text.color_disabled = #86a6cb
mod.widgets.ButtonSecondary.draw_icon.color = #c4e9ff
mod.widgets.ButtonTertiary.draw_text.color = #eef6ff
mod.widgets.ButtonTertiary.draw_text.color_hover = #ffffff
mod.widgets.ButtonTertiary.draw_text.color_down = #ffffff
mod.widgets.ButtonTertiary.draw_text.color_focus = #eef6ff
mod.widgets.ButtonTertiary.draw_text.color_disabled = #86a6cb
mod.widgets.ButtonTertiary.draw_icon.color = #eef6ff
mod.widgets.ButtonDanger.draw_text.color = #ffdcd8
mod.widgets.ButtonDanger.draw_text.color_hover = #ffffff
mod.widgets.ButtonDanger.draw_text.color_down = #ffffff
mod.widgets.ButtonDanger.draw_text.color_focus = #ffdcd8
mod.widgets.ButtonDanger.draw_text.color_disabled = #86a6cb
mod.widgets.ButtonDanger.draw_icon.color = #ffdcd8
mod.widgets.ButtonOutline.draw_text.color = #eef6ff
mod.widgets.ButtonOutline.draw_text.color_hover = #ffffff
mod.widgets.ButtonOutline.draw_text.color_down = #ffffff
mod.widgets.ButtonOutline.draw_text.color_focus = #eef6ff
mod.widgets.ButtonOutline.draw_text.color_disabled = #86a6cb
mod.widgets.ButtonPrimary.draw_bg.loading_color = #ffffff
// The trailing icon is a draw object of its own, which the icon ink above
// does not reach, and the roles share the flat button's until each is
// handed one of its own with the ink of its leading icon.
mod.widgets.ButtonFlat.draw_icon_end.color = #eef6ff
mod.widgets.ButtonPrimary.draw_icon_end = mod.widgets.ButtonFlat.draw_icon_end{color: #ffffff}
mod.widgets.ButtonSecondary.draw_icon_end = mod.widgets.ButtonFlat.draw_icon_end{color: #c4e9ff}
mod.widgets.ButtonDanger.draw_icon_end = mod.widgets.ButtonFlat.draw_icon_end{color: #ffdcd8}
mod.widgets.ButtonTertiary.draw_icon_end = mod.widgets.ButtonFlat.draw_icon_end{color: #eef6ff}
// 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 bp_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}}
}
bp_held_states(mod.widgets.ButtonFlat)
bp_held_states(mod.widgets.CheckBoxFlat)
bp_held_states(mod.widgets.ToggleFlat)
bp_held_states(mod.widgets.RadioButtonFlat)
bp_held_states(mod.widgets.RadioButtonTabFlat)
bp_held_states(mod.widgets.TextInputFlat)
bp_held_states(mod.widgets.DropDownFlat)
// ---------------------------------------------------------------------------
// CHECK BOXES. A 15 point square with corners of two points in the
// two-pixel line over the face; checked, a check of two strokes in the sky
// ink; mixed, the pale wash with a white dash across it; error, the line in
// red pencil; held, the well hatched as a recess; disabled, the line dashed.
let bp_check = fn(p: vec2, o: vec2, rs: vec2, px: float, sz: float, at_end: float, hover: float, down: float, focus: float, active: float, mixed: float, err: float, disabled: float) -> vec4 {
let x0 = (rs.x - sz) * at_end
let y0 = rs.y * 0.5 - sz * 0.5
let lo = vec2(Finish.snap(o.x + x0, px), Finish.snap(o.y + y0, px)) - o
let hi = lo + vec2(Finish.snap(sz, px), Finish.snap(sz, px))
let c = (lo + hi) * 0.5
let h = (hi - lo) * 0.5
let d = Material.sd_box(p, c, h, 2.0)
let live = 1.0 - disabled
let hv = hover * live
let dn = down * live
let mx = mixed * (1.0 - active)
let lw = 2.0 * px
let inside = Finish.cover(d, px)
var face = mix(bp_face(), bp_face_hi(), hv)
face = mix(face, bp_face_lo(), dn)
face = mix(face, bp_wash(), mx * live)
var out = vec4(face * 0.9 * inside, 0.9 * inside)
out = bp_put(out, bp_ink(), Finish.cover(d + lw, px) * bp_hatch(p, 2.5, px) * dn * 0.5)
var lc = mix(bp_ink(), bp_white(), hv)
lc = mix(lc, bp_red(), err)
lc = mix(lc, bp_off(), disabled)
let dash = mix(1.0, bp_dash(p.x + p.y, 4.0, 0.6), disabled)
out = bp_put(out, lc, Finish.band(d, 0.0, lw, px) * dash)
// The check.
let k = hi.x - lo.x
let a = lo + vec2(0.22, 0.52) * k
let b = lo + vec2(0.42, 0.73) * k
let e = lo + vec2(0.79, 0.27) * k
let cw = max(2.5 * px, 0.12 * k)
let ck = min(bp_seg(p, a, b), bp_seg(p, b, e)) - cw * 0.5
var ink = mix(bp_sky(), bp_sky_hi(), hv)
ink = mix(ink, bp_red(), err)
ink = mix(ink, bp_off(), disabled)
out = bp_put(out, ink, Finish.cover(ck, px) * active)
// Mixed: the dash.
let md = max(abs(p.x - c.x) - 0.25 * k, abs(p.y - c.y) - cw * 0.5)
out = bp_put(out, mix(bp_white(), bp_off(), disabled), Finish.cover(md, px) * mx)
return bp_focus(out, p, c, h, 2.0, px, focus * live)
}
mod.widgets.CheckBoxFlat.clip_x = false
mod.widgets.CheckBoxFlat.clip_y = false
mod.widgets.CheckBoxFlat.label_walk.margin.left = 18.0
mod.widgets.CheckBoxFlat.draw_bg.size = uniform(15.0)
mod.widgets.CheckBoxFlat.draw_bg.border_radius = uniform(2.0)
mod.widgets.CheckBoxFlat.draw_bg.vertex = fn() {
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(5.0, 5.0), self.rect_size + vec2(10.0, 10.0))
}
mod.widgets.CheckBoxFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * (self.rect_size + vec2(10.0, 10.0)) - vec2(5.0, 5.0)
return bp_check(p, self.rect_pos, self.rect_size, px, self.size, self.mark_at_end, self.hover, self.down, self.focus, self.active, self.mixed, self.error, self.disabled)
}
mod.widgets.CheckBoxFlat.draw_text.color_error = #ffb3ae
// RADIOS. A 15 point circle in the two-pixel line; on, a disc of the sky
// ink with a device pixel of face between it and the line.
mod.widgets.RadioButtonFlat.clip_x = false
mod.widgets.RadioButtonFlat.clip_y = false
mod.widgets.RadioButtonFlat.label_walk.margin.left = 18.0
mod.widgets.RadioButtonFlat.draw_bg.size = uniform(15.0)
mod.widgets.RadioButtonFlat.draw_bg.vertex = fn() {
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(5.0, 5.0), self.rect_size + vec2(10.0, 10.0))
}
mod.widgets.RadioButtonFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * (self.rect_size + vec2(10.0, 10.0)) - vec2(5.0, 5.0)
let o = self.rect_pos
let r = Finish.snap(self.size * 0.5, px)
let c = vec2(Finish.snap(o.x + r, px) - o.x, Finish.snap(o.y + self.rect_size.y * 0.5, px) - o.y)
let d = length(p - c) - r
let live = 1.0 - self.disabled
let hv = self.hover * live
let dn = self.down * live
let lw = 2.0 * px
let inside = Finish.cover(d, px)
var face = mix(bp_face(), bp_face_hi(), hv)
face = mix(face, bp_face_lo(), dn)
var out = vec4(face * 0.9 * inside, 0.9 * inside)
out = bp_put(out, bp_ink(), Finish.cover(d + lw, px) * bp_hatch(p, 2.5, px) * dn * 0.5)
var lc = mix(bp_ink(), bp_white(), hv)
lc = mix(lc, bp_off(), self.disabled)
let dash = mix(1.0, bp_dash(atan2(p.y - c.y, p.x - c.x) * r, 4.0, 0.6), self.disabled)
out = bp_put(out, lc, Finish.band(d, 0.0, lw, px) * dash)
let dd = length(p - c) - Finish.snap(r * 0.5, px)
out = bp_put(out, mix(mix(bp_sky(), bp_sky_hi(), hv), bp_off(), self.disabled), Finish.cover(dd, px) * self.active)
return bp_focus(out, p, c, vec2(r, r), r, px, self.focus * live)
}
// TOGGLES AND SWITCHES. One drawing for the three, `kind` picks it:
// 0 the toggle, an outlined pill with a round thumb; 1 the rocker, a box
// split down the middle whose pressed half is hatched as a recess, an I on
// one half and an O on the other, the lit one in sky; 2 the slide switch,
// a slot with a square cap carrying three grip lines. On, the stretch the
// thumb has travelled is hatched in sky and the thumb is the sky ink.
let bp_toggle = fn(p: vec2, o: vec2, rs: vec2, px: float, size: float, aspect: float, at_end: float, t: float, active: float, hover: float, down: float, focus: float, disabled: float, kind: float) -> vec4 {
let sz = vec2(size * aspect, size)
let x0 = (rs.x - sz.x) * at_end
let lo = vec2(Finish.snap(o.x + x0, px), Finish.snap(o.y + rs.y * 0.5 - sz.y * 0.5, px)) - o
let hi = lo + vec2(Finish.snap(sz.x, px), Finish.snap(sz.y, px))
let c = (lo + hi) * 0.5
let h = (hi - lo) * 0.5
let pill = step(kind, 0.5)
let rocker = step(0.5, kind) * step(kind, 1.5)
let slide = step(1.5, kind)
let r = mix(2.5, h.y, pill)
let d = Material.sd_box(p, c, h, r)
let live = 1.0 - disabled
let hv = hover * live
let dn = down * live
let on = active * live
let lw = 2.0 * px
let inside = Finish.cover(d, px)
var face = mix(bp_face(), bp_face_hi(), hv * 0.6)
var out = vec4(face * 0.9 * inside, 0.9 * inside)
var lc = mix(bp_ink(), bp_white(), hv)
lc = mix(lc, bp_off(), disabled)
let dash = mix(1.0, bp_dash(p.x + p.y, 4.0, 0.6), disabled)
var sky = mix(bp_sky(), bp_sky_hi(), hv)
sky = mix(sky, bp_off(), disabled)
// The thumb's travel.
let tr = h.y - Finish.snap(3.0, px)
let tx0 = lo.x + h.y
let tx1 = hi.x - h.y
let tcx = Finish.snap(o.x + mix(tx0, tx1, t), px) - o.x
// The travelled stretch, hatched in sky (toggle and slide switch).
let trav = Finish.cover(d + lw + px, px) * step(p.x, tcx) * (1.0 - rocker)
out = bp_put(out, sky, trav * bp_hatch(p, 2.5, px) * mix(0.0, 0.85, t) * live)
// The rocker: the pressed half recessed, the divider, the I and the O.
let half = mix(step(p.x, c.x), step(c.x, p.x), active)
out = bp_put(out, bp_ink(), half * Finish.cover(d + lw, px) * bp_hatch(p, 2.5, px) * rocker * 0.45)
let divd = Finish.cover(abs(p.x - c.x) - px, px) * inside * rocker
out = bp_put(out, lc, divd)
let iq = p - vec2(c.x + h.x * 0.5, c.y)
let im = max(abs(iq.x) - px, abs(iq.y) - h.y * 0.42)
out = bp_put(out, mix(lc, sky, on), Finish.cover(im, px) * rocker)
let oq = length(p - vec2(c.x - h.x * 0.5, c.y)) - h.y * 0.38
out = bp_put(out, mix(sky, lc, active), Finish.band(oq, 0.0, lw, px) * rocker)
// The line.
out = bp_put(out, lc, Finish.band(d, 0.0, lw, px) * dash)
// The toggle's thumb.
let td = length(p - vec2(tcx, c.y)) - tr
let tin = Finish.cover(td, px) * pill
out = bp_put(out, mix(face, sky, on), tin)
out = bp_put(out, mix(bp_ink(), bp_face(), on), tin * bp_hatch(p, 2.0, px) * dn * 0.6)
out = bp_put(out, mix(lc, sky, on), Finish.band(td, 0.0, lw, px) * pill)
// The slide switch's cap: a square block over half the slot.
let cw = h.x * 0.5 - Finish.snap(2.0, px)
let ccx = Finish.snap(o.x + mix(lo.x + Finish.snap(2.0, px) + cw, hi.x - Finish.snap(2.0, px) - cw, t), px) - o.x
let cd = Material.sd_box(p, vec2(ccx, c.y), vec2(cw, h.y - Finish.snap(2.0, px)), 1.0)
out = bp_put(out, mix(face, bp_face_lo(), dn), Finish.cover(cd, px) * slide)
let gq = abs(p.x - ccx)
let grip = Finish.cover(abs(gq - 2.5 * step(1.0, gq)) - 0.5 * px, px) * step(gq, 3.5) * Finish.cover(cd + h.y * 0.35, px)
out = bp_put(out, mix(lc, sky, on), grip * slide)
out = bp_put(out, mix(lc, sky, on), Finish.band(cd, 0.0, lw, px) * slide)
return bp_focus(out, p, c, h, r, px, focus * live)
}
mod.widgets.ToggleFlat.clip_x = false
mod.widgets.ToggleFlat.clip_y = false
mod.widgets.ToggleFlat.label_walk.margin.left = 42.0
mod.widgets.ToggleFlat.draw_bg.size = uniform(16.0)
mod.widgets.ToggleFlat.draw_bg.pill_aspect = uniform(2.375)
// A knob icon is a 7 point mark in the middle of the thumb. The widget
// places it for the stock knob, and a sheet's writes to those values do
// not reach where it reads them; so the icon's own point hook scales the
// mark about its centre and moves it onto this thumb.
let bp_knob_icon = fn(pos: vec2, rs: vec2, dx: float) -> vec2 {
let c = rs * 0.5
let k = 7.0 / max(min(rs.x, rs.y), 1.0)
return c + (pos - c) * k + vec2(dx, 0.0)
}
mod.widgets.ToggleFlat.draw_icon_off.transform_svg_point = fn(pos: vec2) -> vec2 {
return bp_knob_icon(pos, self.rect_size, 1.0)
}
mod.widgets.ToggleFlat.draw_icon_on.transform_svg_point = fn(pos: vec2) -> vec2 {
return bp_knob_icon(pos, self.rect_size, -1.0)
}
mod.widgets.ToggleFlat.draw_bg.vertex = fn() {
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(5.0, 5.0), self.rect_size + vec2(10.0, 10.0))
}
mod.widgets.ToggleFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * (self.rect_size + vec2(10.0, 10.0)) - vec2(5.0, 5.0)
let t = mix(self.active, self.drag_pos, self.drag)
return bp_toggle(p, self.rect_pos, self.rect_size, px, self.size, self.pill_aspect, self.mark_at_end, t, self.active, self.hover, self.down, self.focus, self.disabled, 0.0)
}
// THE SWITCHES are the rocker and the slide switch drawn above, the
// toggle's size, quad and label gap. The rocker turns on a click; the
// slide switch follows a drag, as the toggle does.
mod.widgets.ToggleRocker.clip_x = false
mod.widgets.ToggleSlide.clip_x = false
mod.widgets.ToggleRocker.clip_y = false
mod.widgets.ToggleSlide.clip_y = false
mod.widgets.ToggleRocker.label_walk.margin.left = 42.0
mod.widgets.ToggleSlide.label_walk.margin.left = 42.0
mod.widgets.ToggleRocker.draw_bg.size = uniform(16.0)
mod.widgets.ToggleSlide.draw_bg.size = uniform(16.0)
mod.widgets.ToggleRocker.draw_bg.pill_aspect = uniform(2.375)
mod.widgets.ToggleSlide.draw_bg.pill_aspect = uniform(2.375)
mod.widgets.ToggleRocker.draw_bg.vertex = mod.widgets.ToggleFlat.draw_bg.vertex
mod.widgets.ToggleSlide.draw_bg.vertex = mod.widgets.ToggleFlat.draw_bg.vertex
mod.widgets.ToggleRocker.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * (self.rect_size + vec2(10.0, 10.0)) - vec2(5.0, 5.0)
return bp_toggle(p, self.rect_pos, self.rect_size, px, self.size, self.pill_aspect, self.mark_at_end, self.active, self.active, self.hover, self.down, self.focus, self.disabled, 1.0)
}
mod.widgets.ToggleSlide.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * (self.rect_size + vec2(10.0, 10.0)) - vec2(5.0, 5.0)
let t = mix(self.active, self.drag_pos, self.drag)
return bp_toggle(p, self.rect_pos, self.rect_size, px, self.size, self.pill_aspect, self.mark_at_end, t, self.active, self.hover, self.down, self.focus, self.disabled, 2.0)
}
// ---------------------------------------------------------------------------
// SLIDERS. The rail is a slot drawn in two lines, each two device pixels,
// four device pixels apart with round ends; the value fills the slot in
// sky; a tick scale in the dimension ink runs under it, a tick every tenth
// and a longer one at the ends and the middle. The thumb is a small
// outlined square cross-hatched in sky, or (`round_thumb`) a circle with a
// centre mark whose upright runs on past it as a centre line.
let bp_slider = fn(p: vec2, o: vec2, rs: vec2, px: float, cy: float, pad_l: float, pad_r: float, pos: float, from: float, hover: float, focus: float, drag: float, disabled: float, ruler: float, round_thumb: float) -> vec4 {
let live = 1.0 - disabled
let touch = max(max(hover, drag), focus) * live
let yc = Finish.snap(o.y + cy, px) - o.y
let x0 = Finish.snap(o.x + pad_l, px) - o.x
let x1 = Finish.snap(o.x + rs.x - pad_r, px) - o.x
let hh = Finish.snap(2.67, px)
let lw = 2.0 * px
let d = Material.sd_box(p, vec2((x0 + x1) * 0.5, yc), vec2((x1 - x0) * 0.5 + hh, hh), hh)
let inside = Finish.cover(d, px)
var out = vec4(bp_face() * 0.85 * inside, 0.85 * inside)
// The value in the slot.
let xv = mix(x0, x1, pos)
let xf = mix(x0, x1, from)
let fa = min(xf, xv)
let fb = max(xf, xv)
let fd = max(d + lw, max(fa - p.x, p.x - fb))
var ink = mix(bp_sky(), bp_sky_hi(), drag * live)
ink = mix(ink, bp_off(), disabled)
out = bp_put(out, ink, Finish.cover(fd, px) * step(0.5 * px, fb - fa))
// The rails.
var lc = mix(bp_ink(), bp_white(), touch)
lc = mix(lc, bp_off(), disabled)
out = bp_put(out, lc, Finish.band(d, 0.0, lw, px))
// The scale.
let tq = (p.x - x0) / max(x1 - x0, 1.0) * 10.0
let ti = floor(tq + 0.5)
let tx = abs(tq - ti) * (x1 - x0) / 10.0
let major = min(step(abs(ti - 5.0), 0.5) + step(ti, 0.5) + step(9.5, ti), 1.0)
let ty = p.y - (yc + hh + Finish.snap(2.5, px))
let tl = mix(2.5, 4.5, major)
let tick = Finish.cover(tx - 0.5 * px, px) * step(0.0, ty) * step(ty, tl) * step(-0.5, ti) * step(ti, 10.5) * ruler
out = bp_put(out, mix(bp_dimc(), bp_off(), disabled), tick)
// The thumb.
let hx = Finish.snap(o.x + xv, px) - o.x
let tc = vec2(hx, yc)
let sq = Material.sd_box(p, tc, vec2(6.0, 6.0), 2.0)
let ci = length(p - tc) - 6.5
let td = mix(sq, ci, round_thumb)
let tin = Finish.cover(td, px)
out = bp_put(out, mix(bp_face(), bp_face_hi(), touch), tin)
let q = p - tc
let xh = max(bp_hatch(q, 2.0, px), bp_hatch(vec2(0.0 - q.x, q.y), 2.0, px))
out = bp_put(out, ink, xh * Finish.cover(td + lw + 0.5 * px, px) * (1.0 - round_thumb) * (0.7 + 0.3 * touch))
let cross = max(Finish.cover(abs(q.x) - 0.5 * px, px), Finish.cover(abs(q.y) - 0.5 * px, px)) * Finish.cover(td + lw + 1.0, px)
out = bp_put(out, lc, cross * round_thumb)
let upright = Finish.cover(abs(q.x) - 0.5 * px, px) * step(abs(q.y), 9.0) * (1.0 - tin)
out = bp_put(out, mix(bp_dimc(), bp_off(), disabled), upright * round_thumb)
out = bp_put(out, lc, Finish.band(td, 0.0, lw, px))
return bp_focus(out, p, tc, vec2(6.0, 6.0), mix(2.0, 6.5, round_thumb), px, focus * live)
}
mod.widgets.SliderFlat.clip_x = false
mod.widgets.SliderFlat.clip_y = false
mod.widgets.SliderFlat.height = 36.0
mod.widgets.SliderFlat.draw_bg.vertex = fn() {
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(6.0, 6.0), self.rect_size + vec2(12.0, 12.0))
}
mod.widgets.SliderFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * (self.rect_size + vec2(12.0, 12.0)) - vec2(6.0, 6.0)
let lo = min(self.origin_pos, self.slide_pos)
let from = mix(0.0, lo, self.arc_origin)
let to = mix(self.slide_pos, max(self.origin_pos, self.slide_pos), self.arc_origin)
return bp_slider(p, self.rect_pos, self.rect_size, px, 23.0, 6.0, 6.0, to, from, self.hover, self.focus, self.drag, self.disabled, 1.0, 0.0)
}
// The minimal slider is a dimension line: a hairline between two
// extension marks, the value a two-pixel line of sky from the start to an
// arrowhead, and an extension mark standing at the value.
mod.widgets.SliderMinimal.clip_x = false
mod.widgets.SliderMinimal.clip_y = false
mod.widgets.SliderMinimal.draw_bg.vertex = fn() {
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(6.0, 6.0), self.rect_size + vec2(12.0, 12.0))
}
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 + vec2(12.0, 12.0)) - vec2(6.0, 6.0)
let o = self.rect_pos
let live = 1.0 - self.disabled
let touch = max(max(self.hover, self.drag), self.focus) * live
let y = Finish.snap(o.y + self.offset_y + (self.rect_size.y - self.offset_y) * 0.5, px) - o.y + 0.5 * px
let x0 = Finish.snap(o.x + 1.0, px) - o.x
let x1 = Finish.snap(o.x + self.rect_size.x - 1.0, px) - o.x
let inx = step(x0, p.x) * step(p.x, x1)
var lc = mix(bp_ink(), bp_white(), touch)
lc = mix(lc, bp_off(), self.disabled)
var out = vec4(0.0, 0.0, 0.0, 0.0)
out = bp_put(out, mix(bp_dimc(), bp_off(), self.disabled), Finish.cover(abs(p.y - y) - 0.5 * px, px) * inx)
let ext = Finish.cover(min(abs(p.x - x0), abs(p.x - x1)) - 0.5 * px, px) * step(abs(p.y - y), 5.0)
out = bp_put(out, mix(bp_dimc(), bp_off(), self.disabled), ext)
let lo = min(self.origin_pos, self.slide_pos)
let hi = max(self.origin_pos, self.slide_pos)
let a = mix(x0, x1, mix(0.0, lo, self.arc_origin))
let b = mix(x0, x1, mix(self.slide_pos, hi, self.arc_origin))
let hx = Finish.snap(o.x + mix(x0, x1, self.slide_pos), px) - o.x
var ink = mix(bp_sky(), bp_sky_hi(), touch)
ink = mix(ink, bp_off(), self.disabled)
let run = Finish.cover(abs(p.y - y) - px, px) * step(a, p.x) * step(p.x, b - 4.0)
out = bp_put(out, ink, run)
out = bp_put(out, ink, bp_arrow(p, vec2(b, y), vec2(1.0, 0.0), 7.0, 2.6, px) * step(1.0, b - a))
out = bp_put(out, ink, bp_arrow(p, vec2(a, y), vec2(-1.0, 0.0), 7.0, 2.6, px) * step(1.0, b - a) * step(0.5, self.arc_origin))
let mark = Finish.cover(abs(p.x - hx) - 0.5 * px, px) * step(abs(p.y - y), 7.0)
out = bp_put(out, lc, mark)
return bp_focus(out, p, vec2(hx, y), vec2(3.0, 7.0), 1.0, px, self.focus * live)
}
// The round slider: the rail with the round thumb, between its label
// column and its value.
mod.widgets.SliderRoundFlat.clip_x = false
mod.widgets.SliderRoundFlat.clip_y = false
mod.widgets.SliderRoundFlat.draw_bg.vertex = fn() {
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(6.0, 6.0), self.rect_size + vec2(12.0, 12.0))
}
mod.widgets.SliderRoundFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * (self.rect_size + vec2(12.0, 12.0)) - vec2(6.0, 6.0)
return bp_slider(p, self.rect_pos, self.rect_size, px, self.rect_size.y * 0.5, self.label_size + 8.0, 46.0, self.slide_pos, 0.0, self.hover, self.focus, self.drag, self.disabled, 0.0, 1.0)
}
// FADERS. The slot is two hairlines 4 points apart closed at both ends,
// the value fills it in sky, a tick ladder stands on both sides, every
// tenth 3 points and every half 5; the cap is a square outlined block
// with three grip lines across it, the middle one the sky index.
mod.widgets.SliderFader.cap_size = 12.0
mod.widgets.SliderFader.track_inset = 6.0
mod.widgets.SliderFader.clip_x = false
mod.widgets.SliderFader.clip_y = false
mod.widgets.SliderFader.draw_bg.vertex = fn() {
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(6.0, 6.0), self.rect_size + vec2(12.0, 12.0))
}
mod.widgets.SliderFader.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p0 = self.pos * (self.rect_size + vec2(12.0, 12.0)) - vec2(6.0, 6.0)
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)
// Track space: x runs from the value's zero, y across.
let p = vec2(mix(p0.x, self.rect_size.y - p0.y, self.is_vertical), mix(p0.y, p0.x, self.is_vertical))
let live = 1.0 - self.disabled
let touch = max(max(self.hover, self.drag), self.focus) * live
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(across * 0.5, px)
let a0 = foot - 3.0
let a1 = foot + travel + 3.0
let inrun = step(a0, p.x) * step(p.x, a1)
var lc = mix(bp_ink(), bp_white(), touch)
lc = mix(lc, bp_off(), self.disabled)
let dimc = mix(bp_dimc(), bp_off(), self.disabled)
var out = vec4(0.0, 0.0, 0.0, 0.0)
let sd = Material.sd_box(p, vec2((a0 + a1) * 0.5, mid), vec2((a1 - a0) * 0.5, 2.0 + px), 0.0)
out = bp_put(out, bp_face(), Finish.cover(sd, px) * 0.9)
// The fill.
let fa = foot + self.fill_lo * travel
let fb = foot + self.fill_hi * travel
let fill = Finish.cover(sd + px, px) * step(fa, p.x) * step(p.x, fb)
out = bp_put(out, mix(bp_sky(), bp_off(), self.disabled), fill)
out = bp_put(out, lc, Finish.band(sd, 0.0, px, px) * inrun)
// Ticks on both sides.
let tq = (p.x - foot) / travel * 10.0
let ti = floor(tq + 0.5)
let tx = abs(tq - ti) * travel / 10.0
let major = min(step(abs(ti - 5.0), 0.5) + step(ti, 0.5) + step(9.5, ti), 1.0)
let tl = mix(3.0, 5.0, major)
let tb = abs(p.y - mid) - 6.0
let tick = Finish.cover(tx - 0.5 * px, px) * step(0.0, tb) * step(tb, tl) * step(-0.5, ti) * step(ti, 10.5)
out = bp_put(out, dimc, tick)
// The cap.
let at = Finish.snap(foot + self.slide_pos * travel, px)
let cap = Material.sd_box(p, vec2(at, mid), vec2(self.cap_px * 0.5, 11.0), 1.5)
let cin = Finish.cover(cap, px)
out = bp_put(out, mix(mix(bp_face(), bp_face_hi(), touch), bp_face_lo(), self.drag * live), cin)
let gq = abs(p.x - at)
let grip = Finish.cover(abs(gq - 3.0 * step(1.5, gq)) - 0.5 * px, px) * step(gq, 4.0) * Finish.cover(cap + 3.0, px)
let index = Finish.cover(gq - px, px) * Finish.cover(cap + 2.0, px)
out = bp_put(out, lc, grip)
out = bp_put(out, mix(mix(bp_sky(), bp_sky_hi(), touch), bp_off(), self.disabled), index)
out = bp_put(out, lc, Finish.band(cap, 0.0, 2.0 * px, px))
return bp_focus(out, p, vec2(at, mid), vec2(self.cap_px * 0.5, 11.0), 1.5, px, self.focus * live)
}
// ---------------------------------------------------------------------------
// ROTARIES. A knob is drawn as on a drawing: a tick scale round the
// outside (a tick every tenth of the travel, every fifth tick longer), the
// travel as a band outlined in hairlines and filled with sky over the lit
// stretch (from the start of the scale, or from the origin, to the value),
// the body a circle in the two-pixel line with a pointer in white and a
// centre mark, and short centre lines standing out at the sides.
// A dial 30 points or more in radius (`big`) is drawn as a cross-section:
// the left half is the knob's face, the right half is cut away to show the
// section, its housing wall hatched, the bearing balls on their race and
// the shaft hatched, the section plane a chain line down the middle; and
// two numbered balloons in the lower corners of its box point on leader
// lines at the housing and at a ball. Below 12 points in radius a knob
// keeps its band, body and pointer and drops the scale.
// Angles run clockwise from straight down, the way the scale reads: a
// quarter turn is the left, a half the top.
let bp_ang = fn(q: vec2) -> float {
var a = atan2(0.0 - q.x, q.y)
if a < 0.0 {
a = a + 6.2831853
}
return a
}
let bp_dir = fn(a: float) -> vec2 {
return vec2(0.0 - sin(a), cos(a))
}
// The part b0..b1 of an annulus centred on radius `rm`, `hw` either side,
// as a signed distance with square ends.
let bp_arcband = fn(rl: float, a: float, rm: float, hw: float, b0: float, b1: float) -> float {
let dr = abs(rl - rm) - hw
let da = max(b0 - a, a - b1) * rl
return max(dr, da)
}
// A chain line (long dash, gap, dot, gap), `t` points along it.
let bp_chain = fn(t: float) -> float {
let s = fract(t / 9.0) * 9.0
return max(step(s, 5.0), step(6.5, s) * step(s, 7.5))
}
let bp_knob = fn(p: vec2, c: vec2, rad: float, px: float, gap: float, pos: float, org: float, arc_org: float, hover: float, focus: float, drag: float, disabled: float, rs: vec2) -> vec4 {
let live = 1.0 - disabled
let hov = hover * live
let drg = drag * live
let big = step(30.0, rad)
let span = 6.2831853 - gap
let a0 = gap * 0.5
let a1 = a0 + span
let av = a0 + span * pos
let ao = a0 + span * org
let s0 = mix(a0, min(ao, av), arc_org)
let s1 = mix(av, max(ao, av), arc_org)
let q = p - c
let rl = length(q)
let a = bp_ang(q)
let lw = 2.0 * px
var ink = mix(bp_ink(), bp_white(), max(hov, drg))
ink = mix(ink, bp_off(), disabled)
var sky = mix(bp_sky(), bp_sky_hi(), drg)
sky = mix(sky, bp_off(), disabled)
let dimc = mix(bp_dimc(), bp_off(), disabled)
var out = vec4(0.0, 0.0, 0.0, 0.0)
// The scale.
let n = mix(10.0, 20.0, big)
let f = (a - a0) / span
let tq = f * n
let ti = floor(tq + 0.5)
let tx = abs(tq - ti) * span / n * rl
let major = step(fract(ti / 5.0 + 0.01), 0.05)
let scaled = step(12.0, rad)
let tl = rad * mix(mix(0.10, 0.17, major), mix(0.05, 0.09, major), big)
let tk = Finish.cover(tx - 0.5 * px, px) * step(rad - tl, rl) * step(rl, rad) * step(-0.5, ti) * step(ti, n + 0.5) * scaled
out = bp_put(out, dimc, tk)
// The travel band.
let tmax = rad * mix(0.17, 0.09, big) * scaled
let bo = rad - tmax - Finish.snap(mix(1.0, 1.5, big), px) * scaled
let bw = max(Finish.snap(rad * mix(mix(0.24, 0.15, scaled), 0.07, big), px), mix(3.0, 4.0, scaled) * px)
let rb = bo - bw
let rm = bo - bw * 0.5
let bd = bp_arcband(rl, a, rm, bw * 0.5, a0, a1)
let ld = bp_arcband(rl, a, rm, bw * 0.5 - px, s0, s1)
out = bp_put(out, bp_face(), Finish.cover(bd, px) * 0.85)
out = bp_put(out, sky, Finish.cover(ld, px) * step(0.001, s1 - s0))
out = bp_put(out, ink, Finish.band(bd, 0.0, px, px))
// The body.
let bdd = rl - rb
let bin = Finish.cover(bdd, px)
out = bp_put(out, mix(bp_face(), bp_face_hi(), hov * 0.6), bin)
let right = step(0.0, q.x) * big
// The section, in the right half of a big dial: the housing wall
// hatched, the bearing race and its balls, the shaft hatched.
let wall = step(rb * 0.80, rl) * Finish.cover(bdd + lw, px)
out = bp_put(out, ink, wall * bp_hatch(q, 2.5, px) * right * 0.55)
out = bp_put(out, ink, Finish.cover(abs(rl - rb * 0.80) - 0.5 * px, px) * right)
let race = Finish.cover(abs(abs(rl - rb * 0.6) - rb * 0.075) - 0.5 * px, px) * step(rl, rb * 0.79)
out = bp_put(out, dimc, race * right)
let bstep = 0.3926991
let ba = atan2(q.y, q.x)
let bk = clamp(floor(ba / bstep + 0.5), -3.0, 3.0)
let bc = vec2(cos(bk * bstep), sin(bk * bstep)) * rb * 0.6
let ball = Finish.cover(abs(length(q - bc) - rb * 0.055) - 0.5 * px, px)
out = bp_put(out, ink, ball * right)
let shaft = rl - rb * 0.18
out = bp_put(out, ink, Finish.cover(shaft, px) * bp_hatch(q, 1.6, px) * right * 0.7)
out = bp_put(out, ink, Finish.band(shaft, 0.0, px, px) * big)
// The section plane: a chain line down the middle.
let plane = Finish.cover(abs(q.x) - 0.5 * px, px) * step(abs(q.y), rb) * bp_chain(q.y + rb) * big
out = bp_put(out, dimc, plane)
// The body's line, and the centre lines standing out at the sides.
out = bp_put(out, ink, Finish.band(bdd, 0.0, lw, px))
let cl = Finish.cover(abs(q.y) - 0.5 * px, px) * step(rad + 1.0, abs(q.x)) * step(abs(q.x), rad + 4.5) * scaled
out = bp_put(out, dimc, cl)
// The centre mark.
let arm = max(rb * 0.16, 1.5)
let cm = max(Finish.cover(abs(q.y) - 0.5 * px, px) * step(abs(q.x), arm), Finish.cover(abs(q.x) - 0.5 * px, px) * step(abs(q.y), arm)) * (1.0 - big)
out = bp_put(out, dimc, cm)
let hline = Finish.cover(abs(q.y) - 0.5 * px, px) * step(q.x, 0.0) * step(0.0 - rb, q.x) * bp_chain(0.0 - q.x) * big
out = bp_put(out, dimc, hline)
// The pointer.
let pd = bp_seg(q, bp_dir(av) * rb * mix(0.22, 0.24, big), bp_dir(av) * (rb - Finish.snap(2.0, px)))
out = bp_put(out, ink, Finish.cover(pd - mix(1.0, 1.5, big) * px, px))
// The balloons of a big dial, in the lower corners of its box.
if big > 0.5 {
let br = max(5.5, 0.085 * rad)
let cap = br * 0.95
let b1 = vec2(c.x - min(0.98 * rad, c.x - br + 2.0), c.y + 0.98 * rad)
let b2 = vec2(c.x + min(0.98 * rad, rs.x - c.x - br + 2.0), c.y + 0.98 * rad)
let t1 = c + vec2(0.25, 0.86) * rb
let t2 = c + vec2(0.7071, 0.7071) * rb * 0.6
let u1 = normalize(t1 - b1)
let u2 = normalize(t2 - b2)
var bl = Finish.cover(abs(length(p - b1) - br) - 0.5 * px, px)
bl = max(bl, Finish.cover(abs(length(p - b2) - br) - 0.5 * px, px))
bl = max(bl, Finish.cover(bp_seg(p, b1 + u1 * br, t1) - 0.5 * px, px))
bl = max(bl, Finish.cover(bp_seg(p, b2 + u2 * br, t2) - 0.5 * px, px))
bl = max(bl, Finish.cover(min(length(p - t1), length(p - t2)) - 1.2, px))
out = bp_put(out, dimc, bl)
let gw = max(px, cap * 0.13)
let g1 = bp_char(p, b1 - vec2(cap * 1.9 / 6.0, cap * 0.5), cap, 1.0, gw, px)
let g2 = bp_char(p, b2 - vec2(cap * 2.0 / 6.0, cap * 0.5), cap, 2.0, gw, px)
out = bp_put(out, ink, max(g1, g2))
}
// Focus: the marquee round the body.
return bp_focus(out, p, c, vec2(rad, rad), rad, px, focus * live)
}
// The quad is grown by 8 points for the centre lines and the balloons. A
// box with no room for a dial of 4 points in radius draws nothing.
mod.widgets.RotaryFlat.clip_x = false
mod.widgets.RotaryFlat.clip_y = false
mod.widgets.RotaryFlat.draw_bg.vertex = fn() {
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(8.0, 8.0), self.rect_size + vec2(16.0, 16.0))
}
mod.widgets.RotaryFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * (self.rect_size + vec2(16.0, 16.0)) - vec2(8.0, 8.0)
let dia = min(self.rect_size.x, self.rect_size.y - 22.0)
let r = dia * 0.5 - 5.0
let c = vec2(Finish.snap(self.rect_pos.x + self.rect_size.x * 0.5, px), Finish.snap(self.rect_pos.y + 22.0 + dia * 0.5, px)) - self.rect_pos
return bp_knob(p, c, max(r, 4.0), px, self.gap * 0.017453, self.slide_pos, self.origin_pos, self.arc_origin, self.hover, self.focus, self.drag, self.disabled, self.rect_size) * step(4.0, r)
}
mod.widgets.RotaryKnob.clip_x = false
mod.widgets.RotaryKnob.clip_y = false
mod.widgets.RotaryKnob.draw_bg.vertex = fn() {
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(8.0, 8.0), self.rect_size + vec2(16.0, 16.0))
}
mod.widgets.RotaryKnob.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * (self.rect_size + vec2(16.0, 16.0)) - vec2(8.0, 8.0)
let dia = min(self.rect_size.x, self.rect_size.y)
let r = dia * 0.5 - 5.0
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 bp_knob(p, c, max(min(r, 29.0), 4.0), px, self.gap * 0.017453, self.slide_pos, self.origin_pos, self.arc_origin, self.hover, self.focus, self.drag, self.disabled, self.rect_size) * step(4.0, r)
}
mod.widgets.RotaryKnob.draw_text.color = #eef6ff
mod.widgets.RotaryKnob.draw_text.color_hover = #ffffff
mod.widgets.RotaryKnob.draw_text.color_focus = #ffffff
mod.widgets.RotaryKnob.draw_text.color_drag = #ffffff
mod.widgets.RotaryKnob.draw_text.color_disabled = #86a6cb
mod.widgets.RotaryKnob.draw_text.text_style = mod.theme.font_regular{font_size: 8.5}
// READOUTS. The value beside a dial is a boxed figure, as a drawing boxes
// a basic dimension: the face under the two-pixel line.
let bp_readout = fn(p: vec2, o: vec2, s: vec2, px: float) -> vec4 {
let lo = bp_lo(o, px)
let hi = bp_hi(o, s, px)
let d = Material.sd_box(p, (lo + hi) * 0.5, (hi - lo) * 0.5, 2.0)
let inside = Finish.cover(d, px)
var out = vec4(bp_face() * 0.9 * inside, 0.9 * inside)
return bp_put(out, bp_ink(), Finish.band(d, 0.0, 2.0 * px, px))
}
mod.widgets.RotaryFlat.text_input.min_height = 0.0
mod.widgets.RotaryFlat.text_input.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 3.0 bottom: 3.0}
mod.widgets.RotaryFlat.text_input.draw_text.color = #eef6ff
mod.widgets.RotaryFlat.text_input.draw_text.text_style = mod.theme.font_regular{font_size: 9.0}
// The value is selected while the dial is dragged, so typing replaces
// it; the selection is never painted, in any state, so the number
// stays readable under the hand.
mod.widgets.RotaryFlat.text_input.draw_selection.color = #0000
mod.widgets.RotaryFlat.text_input.draw_selection.color_hover = #0000
mod.widgets.RotaryFlat.text_input.draw_selection.color_focus = #0000
mod.widgets.RotaryFlat.text_input.draw_selection.color_down = #0000
mod.widgets.RotaryFlat.text_input.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return bp_readout(self.pos * self.rect_size, self.rect_pos, self.rect_size, px)
}
// The value inside a slider is a bare figure: the field row's height must
// not reach it, or its text drops below the rail.
mod.widgets.SliderMinimal.text_input.min_height = 0.0
mod.widgets.SliderFlat.text_input.min_height = 0.0
mod.widgets.SliderRoundFlat.text_input.min_height = 0.0
mod.widgets.SliderFlat.text_input.draw_text.color = #eef6ff
mod.widgets.SliderMinimal.text_input.draw_text.color = #eef6ff
mod.widgets.SliderRoundFlat.text_input.draw_text.color = #eef6ff
mod.widgets.SliderFlat.text_input.draw_text.text_style = mod.theme.font_regular{font_size: 9.5}
mod.widgets.SliderMinimal.text_input.draw_text.text_style = mod.theme.font_regular{font_size: 9.5}
mod.widgets.SliderRoundFlat.text_input.draw_text.text_style = mod.theme.font_regular{font_size: 9.5}
mod.widgets.ComboBoxFlat.input.min_height = 0.0
// ---------------------------------------------------------------------------
// TEXT FIELDS. The two-pixel line round a well the paper shows through, so
// the grid runs on under the field; under the pointer the line turns white,
// on focus it takes the sky and the marquee stands off it; read only, the
// line is dashed in the dim ink.
let bp_field = fn(p: vec2, o: vec2, s: vec2, px: float, hover: float, down: float, focus: float, disabled: float) -> vec4 {
let lo = bp_lo(o, px)
let hi = bp_hi(o, s, px)
let c = (lo + hi) * 0.5
let h = (hi - lo) * 0.5
let d = Material.sd_box(p, c, h, 2.0)
let live = 1.0 - disabled
let hv = hover * live
let fc = focus * live
let dn = down * live
let inside = Finish.cover(d, px)
var face = mix(bp_well(), bp_face_hi(), hv * 0.4)
face = mix(face, bp_face_lo(), dn)
let fa = mix(0.55, 0.3, disabled)
var out = vec4(face * fa * inside, fa * inside)
var lc = mix(bp_ink(), bp_white(), hv)
lc = mix(lc, bp_sky(), max(fc, dn))
lc = mix(lc, bp_off(), disabled)
let dash = mix(1.0, bp_dash(p.x + p.y, 5.0, 0.6), disabled)
out = bp_put(out, lc, Finish.band(d, 0.0, 2.0 * px, px) * dash)
return bp_focus(out, p, c, h, 2.0, px, fc)
}
mod.widgets.TextInputFlat.clip_x = false
mod.widgets.TextInputFlat.clip_y = false
mod.widgets.TextInputFlat.draw_bg.vertex = fn() {
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(5.0, 5.0), self.rect_size + vec2(10.0, 10.0))
}
mod.widgets.TextInputFlat.draw_bg.pixel = fn() {
if self.border_size < 0.01 {
return vec4(0.0, 0.0, 0.0, 0.0)
}
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * (self.rect_size + vec2(10.0, 10.0)) - vec2(5.0, 5.0)
return bp_field(p, self.rect_pos, self.rect_size, px, self.hover, self.down, self.focus, self.disabled)
}
mod.widgets.TextInputFlat.padding.left = 9.0
mod.widgets.TextInputFlat.padding.right = 9.0
mod.widgets.FieldWell.clip_x = false
mod.widgets.FieldWell.clip_y = false
mod.widgets.FieldWell.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return bp_field(self.pos * self.rect_size, self.rect_pos, self.rect_size, px, self.hover, 0.0, self.focus, self.disabled)
}
mod.widgets.NumberField.clip_x = false
mod.widgets.NumberField.clip_y = false
mod.widgets.NumberField.draw_bg.vertex = fn() {
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(5.0, 5.0), self.rect_size + vec2(10.0, 10.0))
}
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 + vec2(10.0, 10.0)) - vec2(5.0, 5.0)
return bp_field(p, self.rect_pos, self.rect_size, px, self.hover, 0.0, self.focus, self.disabled)
}
// The stepper: its own column behind a two-pixel divider, split across
// the middle by a hairline; the half under the pointer takes the wash at
// a third, the held half the recess hatch; the arrows are the white ink.
mod.widgets.NumberSpin.draw_text.color = #eef6ff
mod.widgets.NumberSpin.draw_text.text_style = mod.theme.font_regular{font_size: 6.0 line_spacing: 1.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 mid = Finish.snap(self.rect_pos.y + self.rect_size.y * 0.5, px) - self.rect_pos.y
let upper = step(p.y, mid)
let live = 1.0 - self.disabled
let hot = mix(max(0.0, 0.0 - self.hot), max(0.0, self.hot), upper) * live
let down = mix(max(0.0, 0.0 - self.down), max(0.0, self.down), upper) * live
let x0 = Finish.snap(self.rect_pos.x, px) - self.rect_pos.x
let a = 0.35 * hot * step(x0 + 2.0 * px, p.x) * step(2.0 * px, p.y) * step(p.y, self.rect_size.y - 2.0 * px)
var out = vec4(bp_wash() * a, a)
out = bp_put(out, bp_ink(), bp_hatch(p, 2.5, px) * down * 0.5 * step(x0 + 2.0 * px, p.x) * step(2.0 * px, p.y) * step(p.y, self.rect_size.y - 2.0 * px))
let groove = Finish.cover(abs(p.y - mid - 0.5 * px) - 0.5 * px, px) * step(x0 + 2.0 * px, p.x) * step(p.x, self.rect_size.x - 2.0 * px)
let side = step(x0, p.x) * step(p.x, x0 + 2.0 * px)
var lc = mix(bp_ink(), bp_off(), self.disabled)
out = bp_put(out, mix(bp_dimc(), bp_off(), self.disabled), groove)
return bp_put(out, lc, side)
}
// DROP-DOWNS. The button's face and line, a two-pixel divider 22 points
// from the right edge and a chevron in the cell it makes; open, the face
// takes the wash and the chevron turns over.
let bp_dropdown = fn(p: vec2, o: vec2, s: vec2, px: float, hover: float, down: float, focus: float, open: float, disabled: float) -> vec4 {
let live = 1.0 - disabled
var f = bp_button(p, o, s, px, hover, down * hover, focus, disabled, 0.0)
let lo = bp_lo(o, px)
let hi = bp_hi(o, s, px)
let inside = Finish.cover(Material.sd_box(p, (lo + hi) * 0.5, (hi - lo) * 0.5 - vec2(2.0 * px, 2.0 * px), 1.0), px)
f = bp_put(f, bp_wash(), inside * open * live * 0.8)
var lc = mix(bp_ink(), bp_white(), hover * live)
lc = mix(lc, bp_off(), disabled)
let dx = Finish.snap(o.x + s.x - 22.0, px) - o.x
let div = step(dx, p.x) * step(p.x, dx + 2.0 * px) * step(5.0, p.y) * step(p.y, s.y - 5.0)
f = bp_put(f, lc, div)
let c = vec2(s.x - 11.0, Finish.snap(o.y + s.y * 0.5, px) - o.y)
let fl = mix(1.0, -1.0, open)
let a = c + vec2(-3.5, -1.75 * fl)
let b = c + vec2(0.0, 1.75 * fl)
let e = c + vec2(3.5, -1.75 * fl)
let ch = min(bp_seg(p, a, b), bp_seg(p, b, e))
return bp_put(f, lc, Finish.cover(ch - 0.75 * px, px))
}
mod.widgets.DropDownFlat.clip_x = false
mod.widgets.DropDownFlat.clip_y = false
mod.widgets.DropDownFlat.padding.left = 10.0
mod.widgets.DropDownFlat.padding.right = 30.0
mod.widgets.DropDownFlat.draw_bg.vertex = fn() {
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(6.0, 6.0), self.rect_size + vec2(12.0, 12.0))
}
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 + vec2(12.0, 12.0)) - vec2(6.0, 6.0)
return bp_dropdown(p, self.rect_pos, self.rect_size, px, self.hover, self.down, self.focus, self.active, self.disabled)
}
mod.widgets.ComboBoxFlat.clip_x = false
mod.widgets.ComboBoxFlat.clip_y = false
mod.widgets.ComboBoxFlat.draw_bg.vertex = fn() {
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(6.0, 6.0), self.rect_size + vec2(12.0, 12.0))
}
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 + vec2(12.0, 12.0)) - vec2(6.0, 6.0)
return bp_dropdown(p, self.rect_pos, self.rect_size, px, self.hover, self.down, self.focus, 0.0, self.disabled)
}
// OPEN LISTS. A sheet of the face under the two-pixel line; a row under
// the hand takes the wash, the chosen row a sky bar at its left and the
// wash at a third.
let bp_menu = fn(p: vec2, size: vec2, px: float) -> vec4 {
let d = Material.sd_box(p, size * 0.5, size * 0.5 - vec2(0.5 * px, 0.5 * px), 2.0)
let inside = Finish.cover(d, px)
var out = vec4(bp_face() * inside, inside)
return bp_put(out, bp_ink(), Finish.band(d, 0.0, 2.0 * px, px))
}
let bp_row = fn(p: vec2, size: vec2, px: float, hover: float, active: float, disabled: float) -> vec4 {
let inb = step(3.0 * px, p.x) * step(p.x, size.x - 3.0 * px)
let a = max(0.75 * hover * (1.0 - disabled), 0.35 * active) * inb
var out = vec4(bp_wash() * a, a)
let bar = step(4.0 * px, p.x) * step(p.x, 7.0 * px) * step(3.0, p.y) * step(p.y, size.y - 3.0)
return bp_put(out, bp_sky(), bar * active)
}
mod.widgets.PopupMenuFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return bp_menu(self.pos * self.rect_size, self.rect_size, px)
}
mod.widgets.PopupMenuItem.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return bp_row(self.pos * self.rect_size, self.rect_size, px, self.hover, self.active, self.disabled)
}
mod.widgets.PopupMenuItem.draw_text.color = #eef6ff
mod.widgets.PopupMenuItem.draw_text.color_hover = #ffffff
mod.widgets.PopupMenuItem.draw_text.color_active = #ffffff
mod.widgets.ComboBoxFlat.draw_popup_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return bp_menu(self.pos * self.rect_size, self.rect_size, px)
}
mod.widgets.ComboBoxFlat.draw_item.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return bp_row(self.pos * self.rect_size, self.rect_size, px, self.hover, self.active, 0.0)
}
mod.widgets.ComboBoxFlat.draw_item_text.color = #eef6ff
// The select wears a button built before this sheet: it is given the
// drop-down's face, chevron and all, and the same grown quad.
mod.widgets.Select.clip_x = false
mod.widgets.Select.clip_y = false
mod.widgets.MultiSelect.clip_x = false
mod.widgets.MultiSelect.clip_y = false
mod.widgets.Select.face.clip_x = false
mod.widgets.Select.face.clip_y = false
mod.widgets.Select.face.padding = mod.turtle.Inset{left: 10.0 right: 30.0 top: 4.0 bottom: 4.0}
mod.widgets.Select.face.draw_bg.vertex = fn() {
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(6.0, 6.0), self.rect_size + vec2(12.0, 12.0))
}
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 + vec2(12.0, 12.0)) - vec2(6.0, 6.0)
return bp_dropdown(p, self.rect_pos, self.rect_size, px, self.hover, self.down, self.focus, 0.0, self.disabled)
}
mod.widgets.MultiSelect.face.clip_x = false
mod.widgets.MultiSelect.face.clip_y = false
mod.widgets.MultiSelect.face.padding = mod.turtle.Inset{left: 10.0 right: 30.0 top: 4.0 bottom: 4.0}
mod.widgets.MultiSelect.face.draw_bg.vertex = fn() {
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(6.0, 6.0), self.rect_size + vec2(12.0, 12.0))
}
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 + vec2(12.0, 12.0)) - vec2(6.0, 6.0)
return bp_dropdown(p, self.rect_pos, self.rect_size, px, self.hover, self.down, self.focus, 0.0, self.disabled)
}
// ---------------------------------------------------------------------------
// PROGRESS. A slot with round ends in the two-pixel line; the done part is
// section-hatched in the intent's ink and closed by a two-pixel cut line;
// the unknown amount is a dotted line along the slot, held still (a face
// may not read the pass clock).
let bp_hues = fn(i: float) -> vec3 {
var h = vec3(0.612, 0.847, 0.980)
if i > 0.5 { h = vec3(0.557, 0.941, 0.722) }
if i > 1.5 { h = vec3(1.0, 0.820, 0.400) }
if i > 2.5 { h = vec3(0.957, 0.420, 0.400) }
if i > 3.5 { h = vec3(0.612, 0.847, 0.980) }
if i > 4.5 { h = vec3(0.569, 0.773, 0.969) }
return h
}
mod.widgets.ProgressBarFlat.clip_x = false
mod.widgets.ProgressBarFlat.clip_y = false
mod.widgets.ProgressBarFlat.draw_bg.vertex = fn() {
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(4.0, 4.0), self.rect_size + vec2(8.0, 8.0))
}
mod.widgets.ProgressBarFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * (self.rect_size + vec2(8.0, 8.0)) - vec2(4.0, 4.0)
let o = self.rect_pos
let hue = bp_hues(self.intent)
let th = min(max(self.thickness, 8.0), self.rect_size.y)
let y0 = Finish.snap(o.y + (self.rect_size.y - th) * 0.5, px) - o.y
let y1 = y0 + Finish.snap(th, px)
let x0 = Finish.snap(o.x + self.rect_size.x * self.start, px) - o.x
let x1 = Finish.snap(o.x + self.rect_size.x * self.end, px) - o.x
let c = vec2((x0 + x1) * 0.5, (y0 + y1) * 0.5)
let h = vec2((x1 - x0) * 0.5, (y1 - y0) * 0.5)
let d = Material.sd_box(p, c, h, h.y)
let lw = 2.0 * px
let inside = Finish.cover(d, px) * self.track
var out = vec4(bp_face() * 0.85 * inside, 0.85 * inside)
let fi = d + lw + px
if self.value >= 0.0 {
let xv = Finish.snap(o.x + mix(x0, x1, clamp((self.value - self.start) / max(self.end - self.start, 0.0001), 0.0, 1.0)), px) - o.x
let done = Finish.cover(max(fi, p.x - xv), px)
out = bp_put(out, hue, done * bp_hatch(p, 3.0, px))
let cut = step(xv - 2.0 * px, p.x) * step(p.x, xv) * Finish.cover(d + lw, px) * step(x0 + 3.0, xv)
out = bp_put(out, hue, cut)
} else {
let u = p.x - x0
let dq = vec2(u - 4.0 * floor(u / 4.0 + 0.5), p.y - c.y)
out = bp_put(out, hue, Finish.cover(length(dq) - 0.9, px) * Finish.cover(fi, px))
}
out = bp_put(out, bp_ink(), Finish.band(d, 0.0, lw, px) * self.track)
return out * self.opacity
}
// RINGS, ARCS AND GAUGES. A ring is drawn as a circle in the dimension
// hairline with the reading over it as a stroke of the intent's ink, and
// where there is room a scale of 21 ticks inside it, every fifth longer.
// The unknown amount is a still quarter arc.
let bp_ring_seg = fn(rl: float, f: float, f0: float, f1: float, rc: float, w: float, sweep: float) -> float {
let dr = abs(rl - rc) - w * 0.5
let da = max(f0 - f, f - f1) * sweep * rl
return max(dr, da)
}
let bp_ring_ticks = fn(rl: float, f: float, f0: float, f1: float, r1: float, rad: float, sweep: float, px: float) -> vec2 {
let tq = f * 20.0
let ti = floor(tq + 0.5)
let tx = abs(tq - ti) * sweep / 20.0 * rl
let major = step(fract(ti / 5.0 + 0.01), 0.05)
let r0 = r1 - mix(0.06, 0.11, major) * rad
let tk = Finish.cover(tx - 0.5 * px, px) * step(r0, rl) * step(rl, r1) * step(-0.5, ti) * step(ti, 20.5) * step(f0 - 0.001, ti / 20.0) * step(ti / 20.0, f1 + 0.001)
return vec2(tk, major)
}
let bp_ring = fn(q: vec2, rad: float, inset: float, px: float, sweep: float, dir: float, f0: float, f1: float, value: float, ink: vec3, track: float, ticks: float) -> vec4 {
let w = clamp(floor(0.07 * 2.0 * rad + 0.5), 2.0, 5.0) * px
let rc = rad - inset - w * 0.5 - px
let rl = length(q)
let a0 = (6.2831853 - sweep) * 0.5
var f = (bp_ang(q) - a0) / max(sweep, 0.001)
f = mix(f, 1.0 - f, step(dir, 0.0))
var out = vec4(0.0, 0.0, 0.0, 0.0)
let tk = bp_ring_ticks(rl, f, f0, f1, rc - w * 0.5 - 2.0 * px, rad, sweep, px)
out = bp_put(out, bp_dimc(), tk.x * ticks)
let td = bp_ring_seg(rl, f, f0, f1, rc, px, sweep)
out = bp_put(out, bp_dimc(), Finish.cover(td, px) * track)
if value >= 0.0 {
let v = clamp(value, f0, f1)
let on = step(0.0005, v - f0)
let ld = bp_ring_seg(rl, f, f0, v, rc, w, sweep)
out = bp_put(out, ink, Finish.cover(ld, px) * on)
} else {
let qe = f0 + 0.25 * (f1 - f0)
let ld = bp_ring_seg(rl, f, f0, qe, rc, w, sweep)
out = bp_put(out, ink, Finish.cover(ld, px))
}
return out
}
mod.widgets.ProgressRingFlat.draw_bg.sweep = 4.712389
mod.widgets.ProgressRingFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * self.rect_size
let c = vec2(self.rect_size.x * 0.5, self.rect_size.y * self.center_y)
let rad = min(self.rect_size.x * 0.5, self.rect_size.y * self.center_y)
let ink = bp_hues(self.intent)
let ticks = step(44.0, 2.0 * rad) * step(self.inset, 0.01)
return bp_ring(p - c, rad, self.inset, px, self.sweep, self.direction, self.start, self.end, self.value, ink, self.track, ticks) * self.opacity
}
mod.widgets.ActivityRings.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * self.rect_size
let c = vec2(self.rect_size.x * 0.5, self.rect_size.y * self.center_y)
let rad = min(self.rect_size.x * 0.5, self.rect_size.y * self.center_y)
return bp_ring(p - c, rad, self.inset, px, self.sweep, self.direction, self.start, self.end, self.value, bp_sky(), self.track, 0.0) * self.opacity
}
// The gauge: the ring over 270 degrees, lit in sky to the warning mark,
// the amber pencil to the critical one, the red pencil above; the needle a
// white line with a small square tip on an outlined pivot. Laid flat, the
// progress slot with a needle across it.
let bp_zone = fn(f: float, warn: float, crit: float) -> vec3 {
var z = vec3(0.612, 0.847, 0.980)
if f > warn {
z = vec3(1.0, 0.820, 0.400)
}
if f > crit {
z = vec3(0.957, 0.420, 0.400)
}
return z
}
let bp_gauge_arc = fn(q: vec2, rad: float, px: float, sweep: float, v: float, warn: float, crit: float, needle: float) -> vec4 {
let w = clamp(floor(0.07 * 2.0 * rad + 0.5), 2.0, 5.0) * px
let rc = rad - w * 0.5 - px
let rl = length(q)
let a0 = (6.2831853 - sweep) * 0.5
let f = (bp_ang(q) - a0) / max(sweep, 0.001)
let z = bp_zone(f, warn, crit)
var out = vec4(0.0, 0.0, 0.0, 0.0)
let r1 = rc - w * 0.5 - 2.0 * px
let tk = bp_ring_ticks(rl, f, 0.0, 1.0, r1, rad, sweep, px)
out = bp_put(out, bp_dimc(), tk.x)
out = bp_put(out, bp_dimc(), Finish.cover(bp_ring_seg(rl, f, 0.0, 1.0, rc, px, sweep), px))
out = bp_put(out, z, Finish.cover(bp_ring_seg(rl, f, warn, 1.0, rc, w, sweep), px) * 0.35)
out = bp_put(out, z, Finish.cover(bp_ring_seg(rl, f, 0.0, v, rc, w, sweep), px) * step(0.001, v))
let av = a0 + v * sweep
let dir = vec2(0.0 - sin(av), cos(av))
let tip = dir * (r1 - 0.12 * rad)
let nd = bp_seg(q, dir * 3.0, tip)
out = bp_put(out, bp_ink(), Finish.cover(nd - 0.75 * px, px) * needle)
out = bp_put(out, bp_ink(), Finish.band(rl - 2.5, 0.0, px, px) * needle)
let sq = max(abs(q.x - tip.x), abs(q.y - tip.y)) - px
return bp_put(out, bp_white(), Finish.cover(sq, px) * needle)
}
let bp_gauge_bar = fn(p: vec2, o: vec2, s: vec2, px: float, v: float, warn: float, crit: float, needle: float) -> vec4 {
let bh = Finish.snap(min(8.0, s.y), px)
let y0 = Finish.snap(o.y + (s.y - bh) * 0.5, px) - o.y
let lo = vec2(Finish.snap(o.x, px) - o.x, y0)
let hi = vec2(floor((o.x + s.x) / px) * px - o.x, y0 + bh)
let d = Material.sd_box(p, (lo + hi) * 0.5, (hi - lo) * 0.5, bh * 0.5)
let f = clamp((p.x - lo.x) / max(hi.x - lo.x, 1.0), 0.0, 1.0)
let z = bp_zone(f, warn, crit)
let inside = Finish.cover(d, px)
var out = vec4(bp_face() * 0.85 * inside, 0.85 * inside)
let fi = d + 3.0 * px
let xv = mix(lo.x + 3.0 * px, hi.x - 3.0 * px, v)
out = bp_put(out, z, Finish.cover(max(fi, p.x - xv), px) * bp_hatch(p, 3.0, px) * step(0.001, v))
out = bp_put(out, bp_ink(), Finish.band(d, 0.0, 2.0 * px, px))
let nx = Finish.snap(o.x + xv, px) - o.x
let nl = Finish.cover(abs(p.x - nx) - 0.75 * px, px) * step(lo.y - 4.0, p.y) * step(p.y, hi.y + 4.0)
return bp_put(out, bp_white(), nl * needle)
}
mod.widgets.Gauge.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * self.rect_size
let v = clamp(self.value, 0.0, 1.0)
var out = vec4(0.0, 0.0, 0.0, 0.0)
if self.linear > 0.5 {
out = bp_gauge_bar(p, self.rect_pos, self.rect_size, px, v, self.zone_warn, self.zone_critical, self.needle)
} else {
let c = self.rect_size * 0.5
let rad = min(self.rect_size.x, self.rect_size.y) * 0.5
out = bp_gauge_arc(p - c, rad, px, self.sweep, v, self.zone_warn, self.zone_critical, self.needle)
}
return out * self.opacity
}
mod.widgets.Gauge.draw_range.color = #b3d2f2
// ---------------------------------------------------------------------------
// LAMPS. An LED set in a socket cut in the paper, drawn inside an outlined
// drafting circle with its centre marked by four short ticks outside it.
// The lens is a dome of coloured glass: lit, a white-hot core burning
// toward white, the most saturated colour at its rim and a tight halo in
// its own hue gone within the socket's ring; half lit, the same LED at
// lower current; out, dark tinted glass with a catch light along its
// lower rim. The socket is shaded under its upper edge and catches light
// along its lower one; a glint sits on the dome. Light is added, never
// painted.
let bp_led_add = fn(res: vec4, light: vec3, ground: vec3) -> vec4 {
let est = res.rgb + ground * (1.0 - res.a)
let add = (vec3(1.0, 1.0, 1.0) - exp(-light)) * max(vec3(1.0, 1.0, 1.0) - est, vec3(0.0, 0.0, 0.0))
return vec4(res.rgb + add, res.a)
}
let bp_led_full = fn(ink: vec3) -> vec3 {
return ink / max(max(ink.x, max(ink.y, ink.z)), 0.01)
}
let bp_led_hue = fn(ink: vec3, vivid: float) -> vec3 {
let f = bp_led_full(ink)
let mn = min(f.x, min(f.y, f.z))
let ch = 1.0 - mn
let pure = (f - vec3(mn, mn, mn)) / max(ch, 0.001)
return mix(f, pure, clamp(vivid, 0.0, 1.0) * 0.72 * smoothstep(0.03, 0.12, ch))
}
// The lamp: its lens a rounded box `hb` either side of `c` (round when the
// two are equal), `lit` its current, `ink` its colour, `bw` the socket's
// width round the lens, `ring` how far out the drafting outline stands.
let bp_led = fn(p: vec2, c: vec2, hb: vec2, lit: float, ink: vec3, vivid: float, px: float, bw: float, ring: float) -> vec4 {
let ground = vec3(0.082, 0.271, 0.467)
let rt = max(min(hb.x, hb.y), 0.4)
let d = Material.sd_box(p, c, hb, rt)
let ds = d - bw
let L = clamp(lit, 0.0, 1.0)
let b = bp_led_full(ink)
let hue = bp_led_hue(ink, vivid)
let pw = L * sqrt(L)
let white = vec3(1.0, 1.0, 1.0)
let ex = max(hb.x - hb.y, 0.0)
let ey = max(hb.y - hb.x, 0.0)
let qx = p.x - c.x
let qy = p.y - c.y
let q = vec2(qx - clamp(qx, 0.0 - ex, ex), qy - clamp(qy, 0.0 - ey, ey)) / rt
let t = clamp(length(q), 0.0, 1.0)
let nrm = q / max(length(q), 0.001)
var res = vec4(0.0, 0.0, 0.0, 0.0)
// The drafting outline and its centre ticks.
let rd = d - ring
let lc = vec3(0.890, 0.945, 1.0)
res = bp_put(res, lc, Finish.band(rd, 0.0, px, px) * 0.9)
let tkx = Finish.cover(abs(qy) - 0.5 * px, px) * step(hb.x + ring + 1.0, abs(qx)) * step(abs(qx), hb.x + ring + 3.0)
let tky = Finish.cover(abs(qx) - 0.5 * px, px) * step(hb.y + ring + 1.0, abs(qy)) * step(abs(qy), hb.y + ring + 3.0)
res = bp_put(res, vec3(0.569, 0.773, 0.969), max(tkx, tky))
// The socket: the paper cut away, shaded under its upper edge, a catch
// light along its lower one.
let a_s = Finish.cover(ds, px)
let gy = clamp(nrm.y * t, -1.0, 1.0)
var sc = ground * mix(0.42, 0.78, smoothstep(-0.9, 0.9, gy))
sc = mix(sc, min(ground * 1.1 + vec3(0.30, 0.30, 0.30), white), Finish.band(ds, 0.0, max(px, 0.45), px) * smoothstep(0.2, 0.95, nrm.y) * 0.85)
res = Finish.over(res, vec4(sc * a_s, a_s))
// The light, held inside the drafting outline.
if L > 0.001 {
let xl = max(d, 0.0)
let halo = exp(0.0 - xl / max(0.30 * rt, 0.4)) * 1.5 + exp(0.0 - xl / max(0.65 * rt, 0.7)) * 0.32
let hwin = 1.0 - smoothstep(bw * 0.6, ring - 0.5, xl)
res = bp_led_add(res, mix(hue, white, 0.15 * pw) * halo * pw * hwin, ground)
}
// The dome.
let top = smoothstep(-1.0, 0.6, q.y)
let pool = exp(0.0 - (q.y - 0.55) * (q.y - 0.55) / 0.09 - q.x * q.x / 0.30)
var on = mix(hue * mix(0.55, 0.8, L), mix(b, hue, 0.5), top)
on = mix(on, min(mix(b, hue, 0.4) + vec3(0.35, 0.35, 0.35), white), pool * 0.5)
on = mix(on, vec3(1.0, 0.995, 0.985), exp(0.0 - t * t / 0.26) * smoothstep(0.5, 1.0, L) * 0.95)
on = mix(on, hue * (0.82 + 0.18 * L), smoothstep(0.62, 1.0, t))
on = on * (0.42 + 0.58 * L)
let offc = mix(vec3(0.05, 0.07, 0.11), b * 0.35, 0.16)
var off = mix(offc * 0.62, offc * 1.08, top)
off = mix(off, min(offc + vec3(0.16, 0.16, 0.18), white), pool * 0.35)
off = off * mix(1.0, 0.75, smoothstep(0.75, 1.0, t))
let catchl = smoothstep(0.62, 0.9, t) * (1.0 - smoothstep(0.9, 1.0, t)) * smoothstep(0.2, 0.9, nrm.y)
off = off + vec3(0.30, 0.32, 0.36) * catchl
var col = mix(off, min(on, white), smoothstep(0.0, 0.35, L))
let e = length(vec2(q.x / 0.62, (q.y + 0.46) / 0.30))
let spec = (1.0 - smoothstep(0.55, 1.0, e)) * mix(1.0, 0.3, clamp((q.y + 0.76) / 0.6, 0.0, 1.0))
col = mix(col, white, spec * mix(0.8, 0.55, L) * smoothstep(0.9, 2.2, rt))
col = min(col, white)
let a_l = Finish.cover(d, px)
return Finish.over(res, vec4(col * a_l, a_l))
}
// The Lamp widget and the bar lamp. A dot is a little over a third of its
// box across with its socket and outline in the rest; a bar is a light
// pipe set three points in from the start of its box, so the whole lamp,
// outline and all, takes the first twenty points of its cell and leaves
// the rest of the row free.
mod.widgets.Lamp.halo = 0.3
mod.widgets.Lamp.halo_reach = 1.0
mod.widgets.Lamp.draw_bg.color_accent = uniform(#4fb8ff)
mod.widgets.Lamp.draw_bg.color_success = uniform(#2ee88a)
mod.widgets.Lamp.draw_bg.color_warning = uniform(#ffc21a)
mod.widgets.Lamp.draw_bg.color_error = uniform(#ff3a32)
mod.widgets.Lamp.draw_bg.vertex = fn() {
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size)
}
mod.widgets.Lamp.draw_bg.pixel = fn() {
let p = self.pos * self.rect_size
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let sz = self.rect_size
let bar = step(0.5, self.bar)
let m = min(sz.x, sz.y) * 0.5
let th = mix(m * 0.42, min(m * 0.26, 3.0), bar)
let hb = vec2(mix(th, 6.0, bar), th)
let bw = mix(clamp(0.3 * th, 1.2, 2.2), 1.3, bar)
let ring = bw + mix(Finish.snap(m * 0.16, px), 1.6, bar)
let c = vec2(mix(sz.x * 0.5, 3.0 + ring + hb.x, bar), sz.y * 0.5)
let res = bp_led(p, c, hb, self.lit, self.intent_color().rgb, 1.0 - step(3.5, self.intent), px, bw, ring)
return res * self.opacity
}
// LEVEL METERS. The two-pixel slot with round ends lying, square standing;
// lying and thick enough, its cells are dots one rung and a third apart;
// else short bars one rung apart. A cell is lit green, the amber pencil
// past the meter's first hot mark, the red pencil past its second; unlit,
// a faint ghost of its ink; the held peak in white. A meter given no rungs
// is one fill with the peak a white line across it. The over lamp is a
// small LED in its drafting circle beside the slot.
mod.widgets.LevelMeter.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p0 = self.pos * self.rect_size
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 p = vec2(mix(p0.x, self.rect_size.y - p0.y, self.vertical), mix(p0.y, p0.x, self.vertical))
let lamp_room = self.lamp * (across + 4.0)
let bar = max(4.0, along - lamp_room)
let hi = vec2(Finish.snap(bar, px), Finish.snap(across, px))
let thin = step(across, 7.5)
let dots = (1.0 - thin) * (1.0 - self.vertical)
let solid = step(self.segment, 0.5)
let r = mix(1.5, hi.y * 0.5, 1.0 - self.vertical)
let d = Material.sd_box(p, hi * 0.5, hi * 0.5, mix(r, 1.0, thin))
let lw = mix(2.0 * px, px, thin)
let inside = Finish.cover(d, px)
var out = vec4(bp_face() * 0.85 * inside, 0.85 * inside)
let pad = mix(3.0, 2.0, thin) * px + lw
let span = max(1.0, bar - pad * 2.0)
let pitch = mix(max(self.segment, 3.0), max(self.segment * 1.375, 4.5), dots)
let n = max(floor((span + 1.0) / pitch), 1.0)
let ci = floor((p.x - pad) / pitch)
let cx = pad + ci * pitch + pitch * 0.5
let share = mix((ci + 0.5) / n, (p.x - pad) / span, solid)
var hue = bp_green()
if share > self.hot_at {
hue = bp_amber()
}
if share > self.hot_to {
hue = bp_red()
}
let inbar = step(0.0, ci) * step(ci, n - 1.0)
let dotd = length(vec2(p.x - cx, p.y - hi.y * 0.5)) - min(hi.y * 0.5 - pad, 2.0)
let bard = Material.sd_box(p, vec2(cx, hi.y * 0.5), vec2(pitch * 0.5 - mix(1.0, 0.75, thin), hi.y * 0.5 - pad), 0.0)
let cd = mix(bard, dotd, dots)
let lit = step(share, self.level)
let held = step(abs(share - self.hold), 0.5 / n) * step(0.002, self.hold) * (1.0 - solid)
let ink = mix(mix(bp_face(), hue, 0.22), mix(hue, bp_white(), 0.5 * held), max(lit, held))
out = bp_put(out, ink, Finish.cover(cd, px) * inbar * (1.0 - solid))
// Solid, the reading is one fill and the mark a white line across it.
let fill = Finish.cover(max(d + lw + px, p.x - pad - self.level * span), px) * step(0.001, self.level)
out = bp_put(out, hue, fill * solid)
let mk = Finish.cover(abs(p.x - pad - self.hold * span) - px, px) * Finish.cover(d + lw, px) * step(0.002, self.hold)
out = bp_put(out, bp_white(), mk * solid)
out = bp_put(out, bp_ink(), Finish.band(d, 0.0, lw, px))
if self.lamp > 0.5 {
let lr = across * 0.5
let lc = vec2(bar + 4.0 + lr, across * 0.5)
let led = bp_led(p, lc, vec2(lr * 0.45, lr * 0.45), step(0.5, self.over), vec3(1.0, 0.227, 0.196), 1.0, px, lr * 0.18, lr * 0.75)
out = Finish.over(out, led)
}
return out * self.opacity
}
mod.widgets.LevelMeterColumn.width = 14.0
// SCROLL BARS. The rail is a slim slot in hairlines; the thumb a two-pixel
// sky line inside it, three pixels and white under the hand.
mod.widgets.ScrollBar.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p0 = self.pos * self.rect_size
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 p = vec2(mix(p0.x, p0.y, self.is_vertical), mix(p0.y, p0.x, self.is_vertical))
let o = mix(self.rect_pos.y, self.rect_pos.x, self.is_vertical)
let mid = Finish.snap(o + across * 0.5, px) - o
let rail = Material.sd_box(p, vec2(along * 0.5, mid), vec2(along * 0.5 - 2.0, 2.5), 2.5)
var out = vec4(bp_face() * 0.6 * Finish.cover(rail, px), 0.6 * Finish.cover(rail, px))
out = bp_put(out, bp_dimc(), Finish.band(rail, 0.0, px, px) * 0.8)
let a = along * self.norm_scroll
let b = a + along * self.norm_handle
let warm = max(self.hover, self.drag)
let w = mix(1.0, 1.5, warm) * px
let d = Material.sd_box(p, vec2((a + b) * 0.5, mid), vec2(max((b - a) * 0.5 - 4.0, 2.0), w), w)
out = bp_put(out, mix(bp_sky(), bp_white(), warm), Finish.cover(d, px))
return out * self.opacity
}
// ---------------------------------------------------------------------------
// TABS. On the tab strip every tab is an outlined arrow, pointing on to the
// next; a radio tab is a plain box that shows its line only under the
// pointer and while chosen. The chosen tab takes the wash, the two-pixel
// line and a short sky bar laid on its foot line under its title (`mark`;
// a dock's tab, whose title sits low on its bar, goes without).
let bp_tab = fn(p: vec2, o: vec2, s: vec2, px: float, hover: float, focus: float, active: float, down: float, arrow: float, mark: float) -> vec4 {
let lo = bp_lo(o, px) + vec2(0.0, Finish.snap(1.0, px))
let hi = bp_hi(o, s, px) - vec2(Finish.snap(2.0, px) * arrow, Finish.snap(2.0, px))
let c = (lo + hi) * 0.5
let h = (hi - lo) * 0.5
let k = h.y * 0.36
// The arrow: a box whose right end is a point.
let tip = vec2(hi.x, c.y)
let n1 = normalize(vec2(h.y, 0.0 - k))
let n2 = normalize(vec2(h.y, k))
var da = max(max(lo.x - p.x, lo.y - p.y), p.y - hi.y)
da = max(da, max(dot(p - tip, n1), dot(p - tip, n2)))
let db = Material.sd_box(p, c, h, 2.0)
let d = mix(db, da, arrow)
let inside = Finish.cover(d, px)
let wash = (0.4 * hover * (1.0 - active) + 0.85 * active) * inside
var out = vec4(mix(bp_wash(), bp_face_lo(), down * (1.0 - active)) * wash, wash)
out = bp_put(out, bp_face(), inside * 0.85 * arrow * (1.0 - max(active, hover)))
let shown = max(max(arrow, active), hover * 0.8)
var lc = mix(bp_ink(), bp_white(), max(hover, active))
out = bp_put(out, lc, Finish.band(d, 0.0, mix(px, 2.0 * px, max(active, arrow)), px) * shown)
let ux0 = lo.x + 9.0
let ux1 = lo.x + 9.0 + max((hi.x - lo.x - 18.0 - 12.0 * arrow) * 0.45, 10.0)
let ul = step(ux0, p.x) * step(p.x, ux1) * step(hi.y - 3.0 * px, p.y) * step(p.y, hi.y)
out = bp_put(out, bp_sky(), ul * active * mark)
return bp_focus(out, p, c, h, 2.0, px, focus * (1.0 - active))
}
mod.widgets.Tabs.height = 28.0
mod.widgets.Tabs.clip_x = false
mod.widgets.Tabs.clip_y = false
mod.widgets.Tabs.draw_tab_active.vertex = fn() {
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(5.0, 5.0), self.rect_size + vec2(10.0, 10.0))
}
mod.widgets.Tabs.draw_tab_active.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * (self.rect_size + vec2(10.0, 10.0)) - vec2(5.0, 5.0)
return bp_tab(p, self.rect_pos, self.rect_size, px, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0)
}
mod.widgets.Tabs.draw_tab_hover.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return bp_tab(self.pos * self.rect_size, self.rect_pos, self.rect_size, px, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0)
}
mod.widgets.Tabs.draw_tab.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return bp_tab(self.pos * self.rect_size, self.rect_pos, self.rect_size, px, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0)
}
mod.widgets.Tabs.draw_bg.pixel = fn() {
return vec4(0.0, 0.0, 0.0, 0.0)
}
mod.widgets.Tabs.draw_sep.pixel = fn() {
return vec4(0.0, 0.0, 0.0, 0.0)
}
mod.widgets.Tabs.draw_text.color = #eef6ff
mod.widgets.Tabs.draw_text_active.color = #ffffff
mod.widgets.Tabs.draw_text_active.text_style = mod.theme.font_regular{font_size: mod.theme.font_size_p}
mod.widgets.Tabs.draw_mark.color = #eef6ff
// The dock's tab: the plain box, no bar.
mod.widgets.Tab.clip_x = false
mod.widgets.Tab.clip_y = false
mod.widgets.Tab.draw_bg.vertex = fn() {
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(5.0, 5.0), self.rect_size + vec2(10.0, 10.0))
}
mod.widgets.Tab.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * (self.rect_size + vec2(10.0, 10.0)) - vec2(5.0, 5.0)
return bp_tab(p, self.rect_pos, self.rect_size, px, self.hover, 0.0, self.active, 0.0, 0.0, 0.0)
}
mod.widgets.Tab.draw_text.color = #d6e8fb
mod.widgets.Tab.draw_text.color_hover = #ffffff
mod.widgets.Tab.draw_text.color_active = #ffffff
// The radio tab reads as a tab.
mod.widgets.RadioButtonTabFlat.clip_x = false
mod.widgets.RadioButtonTabFlat.clip_y = false
mod.widgets.RadioButtonTabFlat.draw_bg.vertex = fn() {
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(5.0, 5.0), self.rect_size + vec2(10.0, 10.0))
}
mod.widgets.RadioButtonTabFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * (self.rect_size + vec2(10.0, 10.0)) - vec2(5.0, 5.0)
let live = 1.0 - self.disabled
return bp_tab(p, self.rect_pos, self.rect_size, px, self.hover * live, self.focus * live, self.active * live, self.down * live, 0.0, 1.0)
}
mod.widgets.RadioButtonTabFlat.draw_text.color = #d6e8fb
mod.widgets.RadioButtonTabFlat.draw_text.color_hover = #ffffff
mod.widgets.RadioButtonTabFlat.draw_text.color_active = #ffffff
mod.widgets.RadioButtonTabFlat.draw_text.color_disabled = #86a6cb
// SEGMENTED. The two-pixel line round the row over the face; the chosen
// segment is drawn pushed in: a box of its own inside the row in the wash,
// the recess hatch inside its top and left edges and the sky underline,
// its title lit white.
mod.widgets.SegmentedControl.segment_height = 26.0
mod.widgets.SegmentedControlFlat.segment_height = 26.0
mod.widgets.SegmentedControlFlat.segment_padding = 12.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 o = self.rect_pos
// Both far edges are floored to a device pixel, so the lower and the
// right line stay inside the quad.
let lo = bp_lo(o, px)
let hi = vec2(floor((o.x + self.rect_size.x) / px) * px, floor((o.y + self.rect_size.y) / px) * px) - o
let d = Material.sd_box(p, (lo + hi) * 0.5, (hi - lo) * 0.5, 2.0)
let inside = Finish.cover(d, px)
var out = vec4(bp_face() * 0.9 * inside, 0.9 * inside)
return bp_put(out, bp_ink(), Finish.band(d, 0.0, 2.0 * px, px))
}
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 o = self.rect_pos
let lo = bp_lo(o, px) + vec2(Finish.snap(3.0, px), Finish.snap(3.0, px))
let hi = bp_hi(o, self.rect_size, px) - vec2(Finish.snap(3.0, px), Finish.snap(3.0, px))
let c = (lo + hi) * 0.5
let d = Material.sd_box(p, c, (hi - lo) * 0.5, 1.5)
let inside = Finish.cover(d, px)
var out = vec4(bp_wash() * inside, inside)
out = bp_put(out, bp_ink(), bp_recess(p, lo, d, 2.0 * px, 2.5, px) * 0.5)
out = bp_put(out, bp_ink(), Finish.band(d, 0.0, 2.0 * px, px))
let uw = max((hi.x - lo.x) * 0.22, 6.0)
let ul = step(abs(p.x - c.x), uw) * step(hi.y - 3.0 * px, p.y) * step(p.y, hi.y)
return bp_put(out, bp_sky(), ul)
}
mod.widgets.SegmentedControlFlat.draw_text.color = #eef6ff
mod.widgets.SegmentedControlFlat.draw_text_selected.color = #ffffff
mod.widgets.SegmentedControlFlat.draw_text_selected.text_style = mod.theme.font_bold{font_size: 9.5}
// ---------------------------------------------------------------------------
// THE GROUND under everything: the window is a sheet of cyanotype paper.
// The print's tone drifts a few percent in broad soft swells and a finer
// mottle, and falls off a little toward the sheet's edges; short paper
// fibres and grit lie in it at the device pixel. On it a drafting grid of
// one-device-pixel lines, a minor line every 18 points and a stronger major
// line every fifth, counted from the frame. The frame is a two-pixel
// border 3 points in and a hairline 7 points in, with a zone tick between
// them at every major line, and a drafting title block hangs from its top
// line, its lettering generic.
let bp_title_block = fn(col: vec3, p: vec2, lo: vec2, px: float) -> vec3 {
let q = p - lo
var c = col
let ink = vec3(0.890, 0.945, 1.0)
// Its rules: the outer line and the cell lines.
let box = Material.sd_box(q, vec2(180.0, 11.0), vec2(180.0, 11.0), 0.0)
let edge = Finish.band(box, 0.0, 2.0 * px, px)
let cells = Finish.cover(min(min(abs(q.x - 190.0), abs(q.x - 260.0)), abs(q.x - 310.0)) - 0.5 * px, px) * step(q.y, 22.0)
c = mix(c, ink, max(edge, cells) * 0.9)
// The lettering: the small labels, then the values.
let wl = px
var t = bp_text(q, vec2(3.0, 3.0), 3.6, 1.0, wl, px)
t = max(t, bp_text(q, vec2(193.0, 3.0), 3.6, 2.0, wl, px))
t = max(t, bp_text(q, vec2(263.0, 3.0), 3.6, 4.0, wl, px))
t = max(t, bp_text(q, vec2(313.0, 3.0), 3.6, 8.0, wl, px))
let wv = max(px, 0.9)
t = max(t, bp_text(q, vec2(36.0, 11.0), 7.0, 0.0, wv, px))
t = max(t, bp_text(q, vec2(211.0, 11.5), 6.5, 3.0, wv, px))
t = max(t, bp_text(q, vec2(277.0, 11.5), 6.5, 5.0, wv, px))
c = mix(c, ink, t * 0.95)
// The revision in red pencil: an A in a delta.
let tc = vec2(342.0, 13.6)
let tq = q - tc
let tri = max(abs(tq.x) * 0.866 - tq.y * 0.5 - 3.3, tq.y - 3.3)
let rp = vec3(0.957, 0.420, 0.400)
c = mix(c, rp, Finish.cover(abs(tri) - 0.5, px) * 0.95)
c = mix(c, rp, bp_text(q, tc - vec2(1.13, 1.4), 3.4, 9.0, max(px, 0.75), px) * 0.95)
return c
}
let bp_ground = fn(p: vec2, size: vec2, px: float) -> vec3 {
var col = bp_paper()
let broad = Finish.fbm(p / 380.0 + vec2(3.1, 7.7)) - 0.5
let mott = Finish.fbm(p / 64.0 + vec2(11.3, 2.9)) - 0.5
col = col * (1.0 + 0.10 * broad + 0.04 * mott)
let e = min(min(p.x, size.x - p.x), min(p.y, size.y - p.y))
col = col * (0.95 + 0.05 * smoothstep(0.0, 160.0, e))
let fib = Finish.noise(vec2(p.x / 7.0, p.y / 1.3)) - 0.5
let grit = Finish.noise(p / 1.7 + vec2(5.0, 1.0)) - 0.5
col = col * (1.0 + 0.03 * fib + 0.025 * grit)
let paper = col
// The grid, counted in device pixels from the frame's hairline, so
// every line is exactly one pixel wide.
let g0 = Finish.snap(7.0, px)
let cell = floor(18.0 / px + 0.5)
let big = cell * 5.0
let ix = floor((p.x - g0) / px)
let iy = floor((p.y - g0) / px)
let mx = ix - cell * floor(ix / cell)
let my = iy - cell * floor(iy / cell)
let jx = ix - big * floor(ix / big)
let jy = iy - big * floor(iy / big)
let minor = max(step(mx, 0.5), step(my, 0.5))
let major = max(step(jx, 0.5), step(jy, 0.5))
let ingrid = step(g0, p.x) * step(g0, p.y) * step(p.x, size.x - g0) * step(p.y, size.y - g0)
col = col + (vec3(0.020, 0.026, 0.026) * minor * (1.0 - major) + vec3(0.052, 0.062, 0.056) * major) * ingrid
// The frame.
let ink = vec3(0.890, 0.945, 1.0)
let o1 = Finish.snap(3.0, px)
let dO = Material.sd_box(p, size * 0.5, size * 0.5 - vec2(o1, o1), 0.0)
let dI = Material.sd_box(p, size * 0.5, size * 0.5 - vec2(g0, g0), 0.0)
let outer = Finish.band(dO, 0.0, 2.0 * px, px)
let inner = Finish.band(dI, 0.0, px, px)
let margin = step(dO, 0.0 - 2.0 * px) * step(0.0, dI)
let side = step(p.x, g0) + step(size.x - g0, p.x)
let zx = abs(ix - big * floor(ix / big + 0.5))
let zy = abs(iy - big * floor(iy / big + 0.5))
let zt = margin * max(step(zx, 0.5) * (1.0 - min(side, 1.0)), step(zy, 0.5) * min(side, 1.0))
col = mix(col, ink, max(outer, inner) * 0.85 + zt * 0.6)
// The title block: a strip hung from the frame's top line in the
// window's caption band, between the window's title and its buttons,
// where nothing of the content ever reaches; on a window too narrow
// to hold it clear of the title, none.
let tlo = vec2(size.x - 510.0, g0)
let room = step(1200.0, size.x)
if room * step(tlo.x - 1.0, p.x) * step(tlo.y, p.y) * step(p.x, tlo.x + 361.0) * step(p.y, tlo.y + 23.0) > 0.5 {
col = bp_title_block(paper, p, tlo, px)
}
return col + vec3(1.0, 1.0, 1.0) * Finish.grain(p / px, 1.0 / 255.0)
}
mod.widgets.Window.show_bg = true
mod.widgets.Window.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return vec4(bp_ground(self.pos * self.rect_size, self.rect_size, px), 1.0)
}
// DIVIDERS: a hairline of the dimension ink between two extension ticks.
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 line = step(y - px, p.y) * step(p.y, y)
let tick = step(min(p.x, self.rect_size.x - p.x), px) * step(abs(p.y - y + 0.5 * px), 3.0)
let a = max(line, tick)
return vec4(bp_dimc() * 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 line = step(x - px, p.x) * step(p.x, x)
let tick = step(min(p.y, self.rect_size.y - p.y), px) * step(abs(p.x - x + 0.5 * px), 3.0)
let a = max(line, tick)
return vec4(bp_dimc() * a, a)
}
// A splitter is a centre line, a chain of the dimension ink; under the
// hand or dragged, a solid two-pixel line of sky along its grip.
mod.widgets.Splitter.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * self.rect_size
let v = step(0.5, self.is_vertical)
let mid = self.rect_size * 0.5
let s = mix(p.x, p.y, v)
let t = mix(p.y - mid.y, p.x - mid.x, v)
let chain = Finish.cover(abs(t) - 0.5 * px, px) * bp_chain(s)
var out = vec4(bp_dimc() * chain * 0.7, chain * 0.7)
let hv = clamp(max(self.hover, max(self.drag, self.focus)), 0.0, 1.0)
let lm = mix(self.rect_size.x, self.rect_size.y, v) * 0.5
let grip = Finish.cover(abs(t) - px, px) * step(abs(s - lm), self.bar_size * 0.5)
return bp_put(out, bp_sky(), grip * hv)
}
// ---------------------------------------------------------------------------
// SURFACES. A panel is drawn as a raised slab: its face is the paper with
// the grid showing faintly through, in the two-pixel line, and its depth
// is drawn as two hatched sides falling away to the lower right, with a
// width dimension under it, figure and all, where it is big enough to
// carry one. The inset panel is a well: the two-pixel line, a band of
// section hatching inside it and a second line round the sunken floor.
let bp_panel = fn(p: vec2, o: vec2, s: vec2, px: float) -> vec4 {
let lo = bp_lo(o, px)
let hi = bp_hi(o, s, px) - vec2(Finish.snap(6.0, px), Finish.snap(6.0, px))
let c = (lo + hi) * 0.5
let h = (hi - lo) * 0.5
let d = Material.sd_box(p, c, h, 2.0)
let dep = Finish.snap(6.0, px)
let lw = 2.0 * px
let inside = Finish.cover(d, px)
var out = vec4(bp_face() * 0.55 * inside, 0.55 * inside)
// The sides: the right side and the foot, swept down to the right.
let xr = p.x - hi.x
let rs = step(0.0, xr) * step(xr, dep) * step(lo.y + xr, p.y) * step(p.y, hi.y + xr)
let yb = p.y - hi.y
let bs = step(0.0, yb) * step(yb, dep) * step(lo.x + yb, p.x) * step(p.x, hi.x + yb)
let sides = max(rs, bs) * (1.0 - inside)
out = bp_put(out, bp_face(), sides * 0.85)
out = bp_put(out, bp_ink(), rs * (1.0 - inside) * bp_hatch(vec2(0.0 - p.x, p.y), 2.2, px) * 0.75)
out = bp_put(out, bp_ink(), bs * (1.0 - inside) * bp_hatch(p, 2.2, px) * 0.75)
var e = bp_seg(p, vec2(hi.x - 1.0, lo.y + 1.0), vec2(hi.x + dep, lo.y + dep))
e = min(e, bp_seg(p, vec2(hi.x + dep, lo.y + dep), vec2(hi.x + dep, hi.y + dep)))
e = min(e, bp_seg(p, vec2(lo.x + dep, hi.y + dep), vec2(hi.x + dep, hi.y + dep)))
e = min(e, bp_seg(p, vec2(lo.x + 1.0, hi.y - 1.0), vec2(lo.x + dep, hi.y + dep)))
out = bp_put(out, bp_ink(), Finish.cover(e - 0.75 * px, px))
out = bp_put(out, bp_ink(), Finish.cover(bp_seg(p, vec2(hi.x, hi.y), vec2(hi.x + dep, hi.y + dep)) - 0.5 * px, px))
out = bp_put(out, bp_ink(), Finish.band(d, 0.0, lw, px))
let room = step(120.0, s.x) * step(60.0, s.y)
// The width dimension under the slab: extension lines down from the
// face's two lower corners, a dimension line between them with
// arrowheads at both ends, broken for its figure, the face's width in
// points.
let wy = hi.y + dep + 13.0
let fw = hi.x - lo.x
let wex = Finish.cover(abs(p.x - lo.x) - 0.5 * px, px) * step(hi.y + 2.0, p.y) * step(p.y, wy + 3.0) + Finish.cover(abs(p.x - hi.x) - 0.5 * px, px) * step(hi.y + dep + 1.5, p.y) * step(p.y, wy + 3.0)
let gapw = 16.0
let wl = Finish.cover(abs(p.y - wy) - 0.5 * px, px) * step(lo.x, p.x) * step(p.x, hi.x) * step(gapw, abs(p.x - (lo.x + hi.x) * 0.5))
let wa = max(bp_arrow(p, vec2(lo.x, wy), vec2(-1.0, 0.0), 5.0, 1.7, px), bp_arrow(p, vec2(hi.x, wy), vec2(1.0, 0.0), 5.0, 1.7, px))
let wt = bp_num(p, vec2((lo.x + hi.x) * 0.5, wy), 6.0, fw, max(px, 0.75), px)
out = bp_put(out, bp_dimc(), max(max(wex, wl), wa) * room)
out = bp_put(out, bp_ink(), wt * room)
return out
}
let bp_inset = fn(p: vec2, o: vec2, s: vec2, px: float) -> vec4 {
let lo = bp_lo(o, px)
let hi = bp_hi(o, s, px)
let c = (lo + hi) * 0.5
let h = (hi - lo) * 0.5
let d = Material.sd_box(p, c, h, 2.0)
let bw = Finish.snap(min(6.0, 0.12 * min(h.x, h.y) * 2.0), px)
let di = Material.sd_box(p, c, h - vec2(bw, bw), 1.0)
let lw = 2.0 * px
let inside = Finish.cover(d, px)
var out = vec4(bp_face() * 0.55 * inside, 0.55 * inside)
let band = inside * (1.0 - Finish.cover(di, px))
out = bp_put(out, bp_ink(), band * bp_hatch(p, 2.4, px) * 0.75)
out = bp_put(out, bp_well(), Finish.cover(di + lw, px) * 0.35)
out = bp_put(out, bp_ink(), Finish.band(d, 0.0, lw, px))
return bp_put(out, bp_ink(), Finish.ring_out(di, 0.0, lw, px) * inside)
}
mod.widgets.PanelView.clip_x = false
mod.widgets.PanelView.clip_y = false
mod.widgets.PanelView.draw_bg.vertex = fn() {
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(4.0, 4.0), self.rect_size + vec2(8.0, 30.0))
}
mod.widgets.PanelView.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * (self.rect_size + vec2(8.0, 30.0)) - vec2(4.0, 4.0)
if self.sunken > 0.5 {
return bp_inset(p, self.rect_pos, self.rect_size, px)
}
return bp_panel(p, self.rect_pos, self.rect_size + vec2(6.0, 6.0), px)
}
// Cards are sheets laid one on another: the face in the two-pixel line,
// corners of three points, and a second sheet's line showing past its
// lower right edge, 4 points down and across.
mod.widgets.Card.draw_bg.vertex = fn() {
self.rect_size3 = self.rect_size + vec2(12.0, 12.0)
self.rect_shift = vec2(0.0, 0.0)
return self.clip_and_transform_vertex(self.rect_pos - vec2(6.0, 6.0), self.rect_size + vec2(12.0, 12.0))
}
mod.widgets.Card.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * (self.rect_size + vec2(12.0, 12.0)) - vec2(6.0, 6.0)
let o = self.rect_pos
let lo = bp_lo(o, px)
let hi = bp_hi(o, self.rect_size, px) - vec2(Finish.snap(4.0, px), Finish.snap(4.0, px))
let c = (lo + hi) * 0.5
let h = (hi - lo) * 0.5
let off = vec2(Finish.snap(4.0, px), Finish.snap(4.0, px))
let d = Material.sd_box(p, c, h, 3.0)
let d2 = Material.sd_box(p, c + off, h, 3.0)
let hv = self.hover
let lw = 2.0 * px
let outlined = step(0.01, self.border_size)
let filled = step(0.1, Finish.lum(self.color.rgb)) * (1.0 - outlined)
var face = mix(bp_face(), self.color.rgb, filled * 0.0)
face = mix(face, bp_face_hi(), hv * 0.6)
face = mix(face, bp_face_lo(), self.down)
let behind = Finish.cover(d2, px) * (1.0 - Finish.cover(d, px))
var out = vec4(bp_face() * 0.85 * behind, 0.85 * behind)
out = bp_put(out, bp_ink(), Finish.band(d2, 0.0, lw, px) * (1.0 - Finish.cover(d, px)))
let inside = Finish.cover(d, px)
out = bp_put(out, face, inside * 0.85)
return bp_put(out, mix(bp_ink(), bp_white(), hv), Finish.band(d, 0.0, lw, px))
}
// A card's title is a surface caption like a panel's.
mod.widgets.H4.draw_text.color = #ffffff
mod.widgets.H4.draw_text.text_style = mod.theme.font_bold{font_size: 10.0}
// LIST ROWS. 22 points tall and clear; the pointer brings a faint wash;
// the chosen row takes the wash, a dashed hairline round it and a sky bar
// at its left end, as a drawing flags the item a note refers to.
mod.widgets.ListItem.height = 22.0
mod.widgets.ListItemOne.height = 22.0
mod.widgets.ListItem.padding = mod.turtle.Inset{left: 12.0 right: 8.0 top: 0.0 bottom: 0.0}
mod.widgets.ListItemOne.padding = mod.turtle.Inset{left: 12.0 right: 8.0 top: 0.0 bottom: 0.0}
mod.widgets.ListItem.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * self.rect_size
let o = self.rect_pos
let live = 1.0 - self.disabled
let lo = bp_lo(o, px) + vec2(0.0, Finish.snap(1.0, px))
let hi = vec2(floor((o.x + self.rect_size.x) / px) * px, floor((o.y + self.rect_size.y) / px) * px) - o - vec2(Finish.snap(3.0, px), Finish.snap(1.0, px))
let sel = self.selected * live
let d = Material.sd_box(p, (lo + hi) * 0.5, (hi - lo) * 0.5, 1.0)
let inside = Finish.cover(d, px)
let wash = (0.4 * self.hover + 0.6 * self.down) * (1.0 - sel) * live + 0.7 * sel
var out = vec4(bp_wash() * wash * inside, wash * inside)
out = bp_put(out, bp_ink(), Finish.band(d, 0.0, px, px) * bp_dash(p.x + p.y, 5.0, 0.6) * sel)
let bar = step(lo.x + 2.0 * px, p.x) * step(p.x, lo.x + 5.0 * px) * step(lo.y + 3.0, p.y) * step(p.y, hi.y - 3.0)
return bp_put(out, bp_sky(), bar * sel)
}
mod.widgets.ListItem.draw_text.color = #eef6ff
mod.widgets.ListItemOne.draw_text.color = #eef6ff
mod.widgets.ListItem.draw_text_2.color = #b3d2f2
// The rows of the side tree stand on the paper, clear; the open row takes
// the wash, a dashed hairline round it and the sky bar at its left end.
mod.widgets.FileTreeNode.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(Finish.snap(1.0, px), Finish.snap(1.0, px))
let hi = self.rect_size - vec2(Finish.snap(2.0, px), Finish.snap(1.0, px))
let d = Material.sd_box(p, (lo + hi) * 0.5, (hi - lo) * 0.5, 1.0)
let inside = Finish.cover(d, px)
var out = vec4(bp_wash() * 0.7 * inside, 0.7 * inside) * self.active
out = bp_put(out, bp_ink(), Finish.band(d, 0.0, px, px) * bp_dash(p.x + p.y, 5.0, 0.6) * self.active)
let bar = step(lo.x + 2.0 * px, p.x) * step(p.x, lo.x + 5.0 * px) * step(lo.y + 3.0, p.y) * step(p.y, hi.y - 3.0)
return bp_put(out, bp_sky(), bar * self.active)
}
mod.widgets.FileTreeNode.draw_text.color_active = #ffffff
mod.widgets.FileTreeNode.draw_icon.color_active = #ffffff
mod.widgets.FileTree.filler.pixel = fn() {
return vec4(0.0, 0.0, 0.0, 0.0)
}
// The rounded view keeps its caller's colours with a true device-pixel
// edge; a view given no line of its own is outlined in a faint hairline of
// the ink, as every surface on a drawing is.
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 lower = vec2(self.border_inset.x, self.border_inset.y)
let upper = self.rect_size - vec2(self.border_inset.z, self.border_inset.w)
let c = (lower + upper) * 0.5
let h = max((upper - lower) * 0.5, vec2(0.5, 0.5))
let r = min(self.border_radius, min(h.x, h.y))
let d = Material.sd_box(p, c, h, r)
var fill = self.color
if self.color_2.x > -0.5 {
let dir = if self.gradient_fill_horizontal > 0.5 self.pos.x else self.pos.y
fill = mix(self.color, self.color_2, dir)
}
var stroke = self.border_color
if self.border_color_2.x > -0.5 {
let dir = if self.gradient_border_horizontal > 0.5 self.pos.x else self.pos.y
stroke = mix(self.border_color, self.border_color_2, dir)
}
var col = vec4(fill.rgb * fill.a, fill.a)
let has = step(0.001, self.border_size)
let bw = max(self.border_size, px) * has + px * (1.0 - has)
let sa = mix(0.55 * step(0.5, fill.a), stroke.a, has)
let sc = mix(vec3(0.890, 0.945, 1.0), stroke.rgb, has)
col = bp_put(col, sc, Finish.band(d, 0.0, bw, px) * sa)
return col * Finish.cover(d, px)
}
// CHIPS. A small box in the two-pixel line over the face; the pointer
// lifts it, a press recesses it.
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 lo = bp_lo(o, px) + vec2(0.0, Finish.snap(1.0, px))
let hi = vec2(floor((o.x + self.rect_size.x) / px) * px - o.x, Finish.snap(o.y + self.rect_size.y - 1.0, px) - o.y)
let d = Material.sd_box(p, (lo + hi) * 0.5, (hi - lo) * 0.5, 2.0)
var face = mix(bp_face(), bp_face_hi(), self.hover)
face = mix(face, bp_face_lo(), self.down)
let inside = Finish.cover(d, px)
var out = vec4(face * 0.9 * inside, 0.9 * inside)
out = bp_put(out, bp_ink(), bp_recess(p, lo, d, 2.0 * px, 2.5, px) * self.down * 0.5)
return bp_put(out, mix(bp_ink(), bp_white(), self.hover), Finish.band(d, 0.0, 2.0 * px, px))
}
mod.widgets.ChipFlat.draw_text.color = #eef6ff
mod.widgets.ChipFlat.palette.on_neutral_container = #eef6ff
mod.widgets.Chip.palette.on_neutral_container = #eef6ff
// BADGES. A count is a boxed figure: the face in the two-pixel line, the
// figure in white.
mod.widgets.BadgePalette.neutral = #11416f
mod.widgets.BadgePalette.on_neutral = #eef6ff
mod.widgets.BadgeFlat.draw_text.text_style = mod.theme.font_regular{font_size: 8.5}
mod.widgets.Badge.draw_text.text_style = mod.theme.font_regular{font_size: 8.5}
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(18.0, self.rect_size.y)
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y + (self.rect_size.y - bh) * 0.5, px)) - o
let hi = vec2(floor((o.x + self.rect_size.x) / px) * px - o.x, lo.y + Finish.snap(bh, px))
let d = Material.sd_box(p, (lo + hi) * 0.5, (hi - lo) * 0.5, 1.5)
let a = Finish.cover(d, px) * self.color.a
var out = vec4(self.color.rgb * a, a)
return bp_put(out, bp_ink(), Finish.band(d, 0.0, 2.0 * px, px))
}
// SPINNERS: the arc face; the stock face turns on the pass clock, which
// only the library's own shader may read.
mod.widgets.SpinnerFlat.face = mod.widgets.SpinnerFace.Arc
// ---------------------------------------------------------------- band
// THE BAND is the sheet's own slider with two thumbs: the same rail, scale
// and fill, the value lit between two round thumbs with their centre
// marks. The widget's grab geometry is set to the thumbs' own, so what is
// drawn is what is dragged.
mod.widgets.RangeSliderFlat.clip_x = false
mod.widgets.RangeSliderFlat.clip_y = false
mod.widgets.RangeSliderFlat.track_inset = 0.0
mod.widgets.RangeSliderFlat.handle_size = 13.0
mod.widgets.RangeSliderFlat.draw_bg.vertex = fn() {
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(6.0, 6.0), self.rect_size + vec2(12.0, 12.0))
}
mod.widgets.RangeSliderFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * (self.rect_size + vec2(12.0, 12.0)) - vec2(6.0, 6.0)
let far = bp_slider(p, self.rect_pos, self.rect_size, px, 23.0, 6.5, 6.5, self.end_pos, self.start_pos, self.hot_end, self.focus, self.drag * self.hot_end, self.disabled, 1.0, 1.0)
let near = bp_slider(p, self.rect_pos, self.rect_size, px, 23.0, 6.5, 6.5, self.start_pos, self.end_pos, self.hot_start, self.focus, self.drag * self.hot_start, self.disabled, 1.0, 1.0)
let mid = 6.5 + (self.start_pos + self.end_pos) * 0.5 * (self.rect_size.x - 13.0)
return mix(near, far, step(mid, p.x))
}
// ---------------------------------------------------------------- screens and meter
// THE READOUTS AND THE SCREEN sit in a drafting window: the paper a shade
// deeper, framed by the two-pixel line and a hairline 2.5 points inside
// it, with no glass over it. The figures are the white ink.
let bp_window = fn(p: vec2, lo: vec2, hi: vec2, px: float, face: vec3) -> vec4 {
let c = (lo + hi) * 0.5
let h = (hi - lo) * 0.5
let d = Material.sd_box(p, c, h, 2.0)
let inside = Finish.cover(d, px)
var out = vec4(face * inside, inside)
let di = Material.sd_box(p, c, h - vec2(2.5, 2.5), 1.0)
out = bp_put(out, bp_dimc(), Finish.band(di, 0.0, px, px) * 0.75)
return bp_put(out, bp_ink(), Finish.band(d, 0.0, 2.0 * px, px))
}
mod.widgets.ScreenView.clip_x = false
mod.widgets.ScreenView.clip_y = false
mod.widgets.ScreenView.padding = mod.turtle.Inset{top: 8.0 right: 10.0 bottom: 8.0 left: 10.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 lo = bp_lo(self.rect_pos, px)
let hi = bp_hi(self.rect_pos, self.rect_size, px)
return bp_window(p, lo, hi, px, self.color.rgb)
}
mod.widgets.ScreenView.sheen = 0.0
mod.widgets.ScreenView.draw_glass.pixel = fn() {
return vec4(0.0, 0.0, 0.0, 0.0)
}
// THE NEEDLE METER is the same window: its scale a hairline arc with a
// tick at every division, the red zone a two-pixel band of red pencil
// along the arc, the needle a white line from an outlined pivot with a
// centre mark.
mod.widgets.NeedleMeter.clip_x = false
mod.widgets.NeedleMeter.clip_y = false
mod.widgets.NeedleMeter.draw_bg.color = uniform(#0f3b69)
mod.widgets.NeedleMeter.draw_bg.color_scale = uniform(#e3f1ff)
mod.widgets.NeedleMeter.draw_bg.color_zone = uniform(#f46b66)
mod.widgets.NeedleMeter.draw_bg.color_needle = uniform(#ffffff)
mod.widgets.NeedleMeter.draw_bg.pixel = fn() {
let p = self.pos * self.rect_size
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let lo = bp_lo(self.rect_pos, px)
let hi = bp_hi(self.rect_pos, self.rect_size, px)
var out = bp_window(p, lo, hi, px, self.color.rgb)
let pv = self.pivot()
let rr = self.scale_radius()
let q = p - pv
let r = length(q)
let ang = atan2(q.x, 0.0 - q.y)
let sweep = self.sweep * 0.017453293
let t = ang / sweep + 0.5
let on_scale = step(-0.0001, t) * step(t, 1.0001)
let inside = Finish.cover(Material.sd_box(p, (lo + hi) * 0.5, (hi - lo) * 0.5 - vec2(3.0, 3.0), 1.0), px)
// The scale.
let arc = Finish.cover(abs(r - rr) - px * 0.5, px) * on_scale
let n = max(floor(self.majors + 0.5), 1.0) * max(floor(self.minors + 0.5), 1.0)
let k = floor(clamp(t, 0.0, 1.0) * n + 0.5)
let ak = (k / n - 0.5) * sweep
let perp = abs(q.x * cos(ak) + q.y * sin(ak))
let mn = max(floor(self.minors + 0.5), 1.0)
let major = 1.0 - step(0.5, k - mn * floor(k / mn + 0.0001))
let len = mix(self.tick_minor, self.tick_major, major)
let radial = max(rr - len - r, r - rr)
let tick = Finish.cover(perp - px * mix(0.5, 1.0, major), px) * Finish.cover(radial, px) * step(0.0, 0.0 - q.y * cos(ak) + q.x * sin(ak))
out = bp_put(out, self.color_scale.rgb, max(arc, tick) * inside)
if self.zone < 0.999 {
let zone_t = Finish.cover((self.zone - t) * sweep * r, px) * Finish.cover((t - 1.0) * sweep * r, px)
let zone_r = Finish.cover(max(rr + 2.0 * px - r, r - rr - 2.5), px)
out = bp_put(out, self.color_zone.rgb, zone_t * zone_r * inside)
}
// The needle, from the pivot to just past the scale.
let na = self.needle_angle(self.value)
let nd = vec2(sin(na), 0.0 - cos(na))
let along = dot(q, nd)
let across = abs(q.x * nd.y - q.y * nd.x)
let needle = Finish.cover(across - px * 0.75, px) * Finish.cover(along - (rr + 3.0), px) * Finish.cover(0.0 - along + self.pivot_size, px)
out = bp_put(out, self.color_needle.rgb, needle * inside)
let pd = r - self.pivot_size
out = bp_put(out, self.color.rgb, Finish.cover(pd, px) * inside)
out = bp_put(out, self.color_scale.rgb, Finish.band(pd, 0.0, 2.0 * px, px) * inside)
let cm = max(Finish.cover(abs(q.x) - 0.5 * px, px) * step(abs(q.y), self.pivot_size * 0.55), Finish.cover(abs(q.y) - 0.5 * px, px) * step(abs(q.x), self.pivot_size * 0.55))
out = bp_put(out, bp_dimc(), cm * inside)
return out * self.opacity
}
mod.widgets.NeedleMeter.draw_label.color = #eef6ff
true