makepad/widgets/sheets/themes/molded/widgets.splash

2311 lines
125 KiB
Text

use mod.prelude.widgets_internal.*
// Light-grey matte moulded plastic, lit from straight above. A control is a
// raised CAP (button, thumb, knob, toggle knob) or a recessed WELL (field,
// track, check box, channel). Caps: a one device pixel hairline, light at the
// top and near black at the foot, a white line just inside the top, a convex
// face and a darker foot. Wells: a dark rim and a shade under the top edge.
// Both sit in a countersink, a hole milled with an angled rim round them,
// drawn outside the layout box: the slope above in shade, the slope below
// lit, a white lip where it meets the housing below. Every line is one
// device pixel, snapped.
// A ROW OF FIELDS IS ONE HEIGHT. Left to themselves the library's fields each
// come out at a size of their own -- in the widget catalogue's toolbar the
// search box, the number beside it and the drop down after it stood at three
// different heights -- and a sheet that reshapes the text box alone widens
// that gap instead of closing it. So the box metrics are stated ONCE here and
// handed to each field below, and a field added to the library later joins
// the row by being named here rather than by having these numbers copied
// into it. A field is a WELL and draws its seat (one device row above) and
// its lip (two below) inside its own box, since it clips its own text; so
// the box is 28 and the visible well 26, the height of a button's cap,
// whose seat falls outside its box.
let field_height = 28.0
let field_pad_v = mod.theme.mspace_1.top
let field_radius = 4.0
// A hairline, for the few stock fields the faces below do not redraw.
let field_border = 0.34
// The room a Fit field takes. A minimum height rather than a height,
// because a line of text is shorter than a row, and the padding with it:
// where a field is Fit, the padding IS its height, and two fields padded
// differently part company again the moment the type grows.
let field_room = fn(w) {
w.min_height = field_height
w.padding.top = field_pad_v
w.padding.bottom = field_pad_v
}
// The box a field draws. Given the draw object and not the widget, because
// not every field draws its own box, and because writing a corner onto a
// draw struct that has no corner would add a slot its shader never reads.
let field_face = fn(d) {
d.border_radius = field_radius
d.border_size = field_border
}
// A field that states a frame and lays its own parts out inside it: a number
// with a stepper, a date, a time. The height is all these take. A padding
// here would NOT hold the line off the box -- each of them measures its
// parts against the turtle's whole rect and draws them from the content
// origin, so the padding displaces the line instead of insetting it, and the
// number came out sitting on the bottom edge with its arrows split around it.
let field_frame = fn(w) {
w.min_height = field_height
}
field_room(mod.widgets.TextInput)
field_face(mod.widgets.TextInput.draw_bg)
field_room(mod.widgets.TextInputFlat)
field_room(mod.widgets.ComboBox)
field_face(mod.widgets.ComboBox.draw_bg)
field_room(mod.widgets.DropDown)
field_face(mod.widgets.DropDown.draw_bg)
field_room(mod.widgets.DropDown2)
field_face(mod.widgets.DropDown2.draw_bg)
field_room(mod.widgets.FieldWell)
field_face(mod.widgets.FieldWell.draw_bg)
field_room(mod.widgets.TagField)
field_face(mod.widgets.TagField.draw_bg)
field_frame(mod.widgets.NumberField)
// The number's box strokes its own hairline and declares no border size.
mod.widgets.NumberField.draw_bg.border_radius = field_radius
// The drag-number's corner is written into its own shader, so it takes the
// room and keeps the box it draws.
field_frame(mod.widgets.ValueInput)
field_frame(mod.widgets.DateField)
field_frame(mod.widgets.TimeField)
// A picker is a date field in an anchor: the field inside fills the frame.
field_frame(mod.widgets.DatePicker)
field_frame(mod.widgets.DateRangePicker)
// The chrome-less input a well holds must impose no height of its own. It is
// a TextInput, so it inherits the minimum above, and a minimum as tall as the
// whole field, applied inside a well already that tall, pushed the line out
// through the bottom of the box.
mod.widgets.WellInput.min_height = 0.0
mod.widgets.Window.pass.clear_color = #dadbdd
// Plain views stop clipping, so a seat, a lip or a contact shadow may fall
// into the gap beside its control. Scroll views keep their own clip.
mod.widgets.View.clip_x = false
mod.widgets.View.clip_y = false
// ---------------------------------------------------------------------------
// SHARED PARTS. Distances in points, negative inside, y down; `px` is one
// device pixel in points. Ink #1c1e22 is the family's shadow.
let g3 = fn(v: float) -> vec3 {
return vec3(v, v, v)
}
// A rounded box, true radius.
let md_rbox = fn(p: vec2, c: vec2, h: vec2, r: float) -> float {
let rr = clamp(r, 0.0, min(h.x, h.y))
let q = abs(p - c) - h + vec2(rr, rr)
return min(max(q.x, q.y), 0.0) + length(max(q, vec2(0.0, 0.0))) - rr
}
// A rounded box with a radius per corner (tl, tr, br, bl).
let md_rbox4 = fn(p: vec2, c: vec2, h: vec2, r4: vec4) -> float {
let q0 = p - c
let rt = mix(r4.y, r4.x, step(q0.x, 0.0))
let rb = mix(r4.z, r4.w, step(q0.x, 0.0))
let r = clamp(mix(rb, rt, step(q0.y, 0.0)), 0.0, min(h.x, h.y))
let q = abs(q0) - h + vec2(r, r)
return min(max(q.x, q.y), 0.0) + length(max(q, vec2(0.0, 0.0))) - r
}
// The corners a button group asks for: a corner written as 0 is a square
// join, any other the family's radius.
let md_corners = fn(tl: float, tr: float, br: float, bl: float, r: float) -> vec4 {
return vec4(mix(r, step(0.5, tl) * r, step(0.0, tl)), mix(r, step(0.5, tr) * r, step(0.0, tr)), mix(r, step(0.5, br) * r, step(0.0, br)), mix(r, step(0.5, bl) * r, step(0.0, bl)))
}
let md_seg = fn(p: vec2, a: vec2, b: vec2) -> float {
let pa = p - a
let ba = b - a
let k = clamp(dot(pa, ba) / max(dot(ba, ba), 0.0001), 0.0, 1.0)
return length(pa - ba * k)
}
let md_grow = fn(rp: vec2, rs: vec2, m: float) -> vec4 {
return vec4(rp.x - m, rp.y - m, rs.x + 2.0 * m, rs.y + 2.0 * m)
}
// The housing's texture, in window device pixels: a 45 degree pin-stripe of
// three device pixels rising to the right (sd 0.009) under a free mono grain
// (sd 0.004). `w` is a point in window space.
let md_tex = fn(w: vec2, px: float) -> float {
let q = w / px
return 0.013 * cos((floor(q.x) + floor(q.y)) * 2.0943951) + Finish.grain(q, 0.010)
}
// The countersink round a cap or a well, outside its edge `d`: the hole it
// sits in is milled with an angled rim `w` points wide. Lit from straight
// above, the slope over the control faces away from the light and lies in
// shade, a little deeper toward the control; the slope under it faces the
// light and is lit; the sides take a faint shade, so the rim reads all round.
// A dark crease where the slope meets the control; where it meets the
// housing a crisp white lip below (one device pixel at 0.9, one at 0.35)
// and a faint dark edge above. `yrel` is the point's height from the
// control's middle, `hy` its half height; `shade` and `lip` scale the two.
let md_sink = fn(d: float, yrel: float, hy: float, w: float, px: float, shade: float, lip: float) -> vec4 {
let up = smoothstep(hy * 0.1, hy * 0.9, -yrel)
let low = smoothstep(hy * 0.1, hy * 0.9, yrel)
let ring = Finish.cover(d - w, px) * (1.0 - Finish.cover(d, px))
let t = clamp(d / max(w, 0.5), 0.0, 1.0)
let k = shade / 0.16
var sh = ring * shade * (1.5 * up * (1.0 - 0.25 * t) + 0.35 * (1.0 - low))
sh = max(sh, Finish.ring_out(d, 0.0, px, px) * 0.30 * k * (0.4 + 0.6 * up))
sh = max(sh, Finish.ring_out(d, w, w + px, px) * 0.18 * k * up)
let li = max(ring * lip * 0.28 * low * (0.6 + 0.4 * t), (Finish.ring_out(d, w, w + px, px) * 0.9 + Finish.ring_out(d, w + px, w + 2.0 * px, px) * 0.35) * lip * low)
let a = sh + li * (1.0 - sh)
let c = vec3(0.110, 0.118, 0.133) * sh * (1.0 - li) + vec3(li, li, li)
return vec4(c, a)
}
// The countersink of a cap that may stand in a row: its slopes run on past
// the cap's ends as one band above and one below and fade out from two to
// six points beyond, where a neighbour's begin, so a row of buttons sits in
// one countersink with its floor between them; a cap alone ends softly.
let md_sink_run = fn(p: vec2, c: vec2, h: vec2, px: float, shade: float, lip: float) -> vec4 {
let de = md_rbox(p, c, h + vec2(16.0, 0.0), 0.0)
let beyond = abs(p.x - c.x) - h.x
let fade = 1.0 - smoothstep(2.0, 6.0, beyond)
return md_sink(de, p.y - c.y, h.y, clamp(h.y * 0.3, 2.0, 2.8), px, shade, lip) * fade
}
// The seat every control takes: the countersink two to three points wide by
// its size (the room its quad grows by holds the rim and its lip).
let md_seat = fn(d: float, yrel: float, hy: float, px: float, shade: float, lip: float) -> vec4 {
return md_sink(d, yrel, hy, clamp(hy * 0.3, 2.0, 2.8), px, shade, lip)
}
// A cap's face, premultiplied: the hairline (light at the top, near black at
// the foot), the white line inside the top fading over the corner arcs, the
// convex face and the darker foot band. Pressed, the face turns inside out
// and a shade falls in under its top edge.
let md_cap = fn(p: vec2, c: vec2, h: vec2, r4: vec4, px: float, hover: float, press: float, dis: float, tint: vec4) -> vec4 {
let d = md_rbox4(p, c, h, r4)
let r = max(max(r4.x, r4.y), max(r4.z, r4.w))
let yin = p.y - (c.y - h.y)
let t = clamp(yin / max(2.0 * h.y, 1.0), 0.0, 1.0)
var face = mix(mix(vec3(0.961, 0.965, 0.969), vec3(0.886, 0.890, 0.898), clamp(t * 2.0, 0.0, 1.0)), vec3(0.796, 0.800, 0.812), clamp(t * 2.0 - 1.0, 0.0, 1.0))
let foot = max(2.0, h.y * 0.2)
face = mix(face, vec3(0.663, 0.671, 0.682), smoothstep(2.0 * h.y - foot, 2.0 * h.y, yin) * 0.6)
let pf = mix(vec3(0.749, 0.753, 0.765), vec3(0.890, 0.894, 0.902), smoothstep(0.0, 1.0, t))
face = mix(face, pf, press)
// a coloured cap is the same plastic dyed: the grey's shading over the hue
face = mix(face, tint.rgb * (Finish.lum(face) / 0.886), tint.a)
face = mix(face, vec3(0.110, 0.118, 0.133), clamp(Finish.inset(p, c, h, r, vec2(0.0, 1.0), 1.2), 0.0, 1.0) * 0.20 * press)
face = Finish.lift(face, 0.36 * hover * (1.0 - press))
face = mix(face, vec3(0.902, 0.906, 0.910), dis)
let across = 1.0 - smoothstep(h.x - r, h.x - r * 0.15, abs(p.x - c.x))
let hl = Finish.band(d, px, 2.0 * px, px) * mix(1.0, across, step(0.5, r)) * step(yin, max(r, 2.0 * px))
face = mix(face, vec3(1.0, 1.0, 1.0), hl * mix(mix(0.85, 0.35, press), 0.4, dis) * mix(1.0, 0.6, tint.a))
var olc = mix(vec3(0.557, 0.565, 0.576), vec3(0.212, 0.220, 0.231), smoothstep(0.15, 0.85, t))
olc = mix(olc, tint.rgb * mix(0.62, 0.30, smoothstep(0.15, 0.85, t)), tint.a)
face = mix(face, olc, Finish.band(d, 0.0, px, px) * mix(1.0, 0.45, dis))
let a = Finish.cover(d, px)
return vec4(face * a, a)
}
// The two contact shadows under a riding cap: a tight one and a soft one,
// both cast straight down and gone within three points.
let md_contact = fn(p: vec2, c: vec2, h: vec2, r: float, amt: float) -> vec4 {
let t = Finish.drop(p, c, h, r, vec2(0.0, 0.8), 0.5) * 0.30 * amt
let s = Finish.drop(p, c, h, r, vec2(0.0, 1.2), 0.8) * 0.12 * amt
let a = t + s * (1.0 - t)
return vec4(0.110 * a, 0.118 * a, 0.133 * a, a)
}
// Focus: one device pixel of lamp orange two points outside the edge.
let md_focus = fn(d: float, px: float, focus: float) -> vec4 {
let f = Finish.ring_out(d, 2.0, 2.0 + px, px) * 0.85 * focus
return vec4(0.918 * f, 0.353 * f, 0.067 * f, f)
}
// The inside of a well: its fill, the shade falling in under the top edge,
// a dark line just inside the top, and the hairline rim.
let md_well_s = fn(p: vec2, c: vec2, h: vec2, r: float, px: float, fill: vec3, shade: float, rim: vec3, sigma: float) -> vec3 {
let d = md_rbox(p, c, h, r)
let sh = clamp(Finish.inset(p, c, h, r, vec2(0.0, 1.0), sigma), 0.0, 1.0)
var col = mix(fill, vec3(0.110, 0.118, 0.133), sh * shade)
let yin = p.y - (c.y - h.y)
col = mix(col, vec3(0.110, 0.118, 0.133), Finish.band(d, px, 2.0 * px, px) * (1.0 - smoothstep(0.0, h.y, yin)) * 0.35)
col = mix(col, rim, Finish.band(d, 0.0, px, px))
return col
}
let md_well = fn(p: vec2, c: vec2, h: vec2, r: float, px: float, fill: vec3, shade: float, rim: vec3) -> vec3 {
let d = md_rbox(p, c, h, r)
let sh = clamp(Finish.inset(p, c, h, r, vec2(0.0, 1.0), 1.0), 0.0, 1.0)
var col = mix(fill, vec3(0.110, 0.118, 0.133), sh * shade)
let yin = p.y - (c.y - h.y)
col = mix(col, vec3(0.110, 0.118, 0.133), Finish.band(d, px, 2.0 * px, px) * (1.0 - smoothstep(0.0, h.y, yin)) * 0.35)
col = mix(col, rim, Finish.band(d, 0.0, px, px))
return col
}
// An indicator lamp set into a cap or the housing: the plain lamp colour, a
// shade lighter at the centre, a darker rim, a light socket lip under it.
// Unlit it is a grey dimple. `glow` is the halo one device pixel out (held
// at 0.30 or less); it is gone within two points.
let md_led = fn(p: vec2, lc: vec2, rad: float, px: float, ink: vec3, lit: float, glow: float) -> vec4 {
let q = p - lc
let d = length(q) - rad
let k = clamp(length(q) / max(rad, 0.001), 0.0, 1.0)
let v = clamp(q.y / max(rad, 0.001) * 0.5 + 0.5, 0.0, 1.0)
let on = mix(Finish.lift(ink, 0.10), ink * 0.80, smoothstep(0.35, 1.0, k))
let off = mix(vec3(0.540, 0.549, 0.565), vec3(0.620, 0.627, 0.643), v)
var col = mix(off, on, lit)
let rim = mix(vec3(0.290, 0.298, 0.314), ink * 0.55, lit)
col = mix(col, rim, Finish.band(d, 0.0, px, px) * mix(0.9, 0.5, v))
let a = Finish.cover(d, px)
let ha = glow * lit * exp(-max(d - px, 0.0) / 0.55) * step(0.0, d) * (1.0 - a)
var out = vec4(ink * ha, ha)
let lip = Finish.ring_out(d, 0.0, px, px) * smoothstep(-0.2, 0.5, q.y / max(rad, 0.001)) * 0.7
out = Finish.over(out, vec4(lip, lip, lip, lip))
return Finish.over(out, vec4(col * a, a))
}
// ---------------------------------------------------------------------------
// BUTTONS. One face for the family; `kind` picks the dressing:
// 0 the seated cap, 1 the cap with a lit orange lamp (primary), 2 the cap
// with its lamp dark (secondary), 4 the cap with a lit red lamp (danger),
// 5 an engraved frame that becomes a cap under the pointer (outline),
// 6 bare until touched (flatter). The quad is grown by `m` for the seat,
// the lip and the focus ring; `row` 1 runs the countersink on into a
// neighbour's (the buttons), 0 rings the cap alone (tabs, chips, drop-downs).
let md_button = fn(pos: vec2, size: vec2, o: vec2, px: float, hover: float, down: float, focus: float, dis: float, loading: float, anim: float, r4: vec4, kind: float, m: float, tint: vec4, row: float) -> vec4 {
let p = pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let live = 1.0 - dis
let press = down * live
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o
let hi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y, px)) - o
let c = (lo + hi) * 0.5
let h = (hi - lo) * 0.5
let d = md_rbox4(p, c, h, r4)
var out = vec4(0.0, 0.0, 0.0, 0.0)
if kind < 4.5 {
if row > 0.5 {
out = md_sink_run(p, c, h, px, mix(0.16, 0.08, dis), mix(1.0, 0.5, dis))
} else {
out = md_seat(d, p.y - c.y, h.y, px, mix(0.16, 0.08, dis), mix(1.0, 0.5, dis))
}
out = Finish.over(out, md_cap(p, c, h, r4, px, hover * live, press, dis, tint))
if kind > 0.5 {
let lc = vec2(lo.x + 9.5, c.y)
let red = step(3.5, kind)
let ink = mix(vec3(0.918, 0.353, 0.067), vec3(0.910, 0.098, 0.059), red)
let lit = (1.0 - step(1.5, kind) * step(kind, 2.5)) * live
out = Finish.over(out, md_led(p, lc, 2.5, px, ink, lit, 0.28))
}
} else {
let bare = step(5.5, kind)
let show = clamp(hover * live * 0.7 + press, 0.0, 1.0)
if row > 0.5 {
out = md_sink_run(p, c, h, px, mix(0.16, 0.08, dis), mix(1.0, 0.5, dis))
} else {
out = md_seat(d, p.y - c.y, h.y, px, 0.16 * show, show)
}
out = Finish.over(out, md_cap(p, c, h, r4, px, 0.0, press, dis, tint) * show)
if bare < 0.5 {
// at rest an engraved frame: a dark hairline, a white line
// under its lower half and inside its upper half
let rest = 1.0 - show
let fr = Finish.band(d, 0.0, px, px) * mix(0.85, 0.4, dis) * rest
out = Finish.over(out, vec4(0.416 * fr, 0.424 * fr, 0.439 * fr, fr))
let li = Finish.ring_out(d, 0.0, px, px) * smoothstep(0.0, h.y * 0.85, p.y - c.y) * 0.85 * rest
let hl = Finish.band(d, px, 2.0 * px, px) * smoothstep(0.0, h.y * 0.85, c.y - p.y) * 0.6 * rest
out = Finish.over(out, vec4(li + hl, li + hl, li + hl, li + hl))
}
}
out = Finish.over(out, md_focus(d, px, focus * live))
// The wait spinner, which the stock face drew and this one must too.
if loading > 0.0 {
let sdf = Sdf2d.viewport(p)
let rr = max(min(h.x, h.y) - 5.0, 2.0)
let a0 = anim * 2.0 * PI
sdf.arc_round_caps(c.x, c.y, rr, a0, a0 + 1.5 * PI, 1.2)
sdf.fill(vec4(0.333, 0.341, 0.357, loading))
out = Finish.over(out, sdf.result)
}
return out
}
// Every button template: the root and the sizes share its draw object, so
// they wear the plain cap; the roles are specialised below.
mod.widgets.ButtonFlat.clip_x = false
mod.widgets.ButtonFlat.clip_y = false
mod.widgets.ButtonFlat.draw_bg.vertex = fn() {
let g = md_grow(self.rect_pos, self.rect_size, 8.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.ButtonFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return self.host_over(md_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, md_corners(self.border_radius_tl, self.border_radius_tr, self.border_radius_br, self.border_radius_bl, 5.0), 0.0, 8.0, vec4(0.0, 0.0, 0.0, 0.0), 1.0), 8.0)
}
mod.widgets.Button.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return self.host_over(md_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, md_corners(self.border_radius_tl, self.border_radius_tr, self.border_radius_br, self.border_radius_bl, 5.0), 0.0, 8.0, vec4(0.0, 0.0, 0.0, 0.0), 1.0), 8.0)
}
mod.widgets.ButtonPrimary.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return self.host_over(md_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, md_corners(self.border_radius_tl, self.border_radius_tr, self.border_radius_br, self.border_radius_bl, 5.0), 1.0, 8.0, vec4(0.0, 0.0, 0.0, 0.0), 1.0), 8.0)
}
mod.widgets.ButtonSecondary.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return self.host_over(md_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, md_corners(self.border_radius_tl, self.border_radius_tr, self.border_radius_br, self.border_radius_bl, 5.0), 2.0, 8.0, vec4(0.0, 0.0, 0.0, 0.0), 1.0), 8.0)
}
mod.widgets.ButtonTertiary.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return self.host_over(md_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, md_corners(self.border_radius_tl, self.border_radius_tr, self.border_radius_br, self.border_radius_bl, 5.0), 0.0, 8.0, vec4(0.0, 0.0, 0.0, 0.0), 1.0), 8.0)
}
mod.widgets.ButtonDanger.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return self.host_over(md_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, md_corners(self.border_radius_tl, self.border_radius_tr, self.border_radius_br, self.border_radius_bl, 5.0), 4.0, 8.0, vec4(0.0, 0.0, 0.0, 0.0), 1.0), 8.0)
}
mod.widgets.ButtonOutline.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return self.host_over(md_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, md_corners(self.border_radius_tl, self.border_radius_tr, self.border_radius_br, self.border_radius_bl, 5.0), 5.0, 8.0, vec4(0.0, 0.0, 0.0, 0.0), 1.0), 8.0)
}
mod.widgets.ButtonFlatter.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return self.host_over(md_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, md_corners(self.border_radius_tl, self.border_radius_tr, self.border_radius_br, self.border_radius_bl, 5.0), 6.0, 8.0, vec4(0.0, 0.0, 0.0, 0.0), 1.0), 8.0)
}
// Templates that draw a face of their own on top of these roots keep the
// plain quad: the grown one is only for the faces written here.
mod.widgets.CloseButton.draw_bg.vertex = fn() {
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size)
}
mod.widgets.CopyButton.draw_bg.vertex = fn() {
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size)
}
mod.widgets.BurgerButton.draw_bg.vertex = fn() {
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size)
}
mod.widgets.ButtonFloating.draw_bg.vertex = fn() {
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size)
}
mod.widgets.ButtonFloatingSm.draw_bg.vertex = fn() {
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size)
}
mod.widgets.glass.CutButton.draw_bg.vertex = fn() {
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size)
}
// Geometry: one 26 point row, 12 points either side of the label; the lamp
// takes its room from the left padding of the faces that carry one.
mod.widgets.ButtonFlat.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 4.0 bottom: 4.0}
mod.widgets.Button.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 4.0 bottom: 4.0}
mod.widgets.ButtonPrimary.padding = mod.turtle.Inset{left: 19.0 right: 12.0 top: 4.0 bottom: 4.0}
mod.widgets.ButtonSecondary.padding = mod.turtle.Inset{left: 19.0 right: 12.0 top: 4.0 bottom: 4.0}
mod.widgets.ButtonDanger.padding = mod.turtle.Inset{left: 19.0 right: 12.0 top: 4.0 bottom: 4.0}
mod.widgets.ButtonTertiary.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 4.0 bottom: 4.0}
mod.widgets.ButtonOutline.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 4.0 bottom: 4.0}
mod.widgets.ButtonIcon.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 4.0 bottom: 4.0}
mod.widgets.Button.icon_walk = mod.turtle.Walk{width: 14.0 height: Fit}
mod.widgets.ButtonFlat.min_height = 26.0
mod.widgets.Button.min_height = 26.0
mod.widgets.ButtonXs.min_height = 0.0
// The 18 point button: 6 points either side, a cap height of 0.32 of it.
mod.widgets.ButtonXs.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 0.0 bottom: 0.0}
mod.widgets.ButtonXs.draw_text.text_style.font_size = 6.0
mod.widgets.ButtonSm.min_height = 0.0
// Label ink: the working grey on every cap; the lamp says the role, not the
// ink. Button words are medium, primary and danger semibold.
mod.widgets.ButtonFlat.draw_text.text_style.font_family = mod.theme.font_label.font_family
mod.widgets.ButtonPrimary.draw_text.color = #34373b
mod.widgets.ButtonPrimary.draw_text.color_hover = #25282c
mod.widgets.ButtonPrimary.draw_text.color_down = #25282c
mod.widgets.ButtonPrimary.draw_text.color_focus = #34373b
mod.widgets.ButtonPrimary.draw_text.color_disabled = #a0a2a6
mod.widgets.ButtonPrimary.draw_icon.color = #34373b
mod.widgets.ButtonPrimary.draw_text.text_style = mod.theme.font_bold{font_size: 8.5}
mod.widgets.ButtonSecondary.draw_text.color = #34373b
mod.widgets.ButtonSecondary.draw_text.color_hover = #25282c
mod.widgets.ButtonSecondary.draw_text.color_down = #25282c
mod.widgets.ButtonSecondary.draw_text.color_focus = #34373b
mod.widgets.ButtonSecondary.draw_icon.color = #34373b
mod.widgets.ButtonSecondary.draw_text.text_style = mod.theme.font_label{font_size: 8.5}
mod.widgets.ButtonTertiary.draw_text.color = #34373b
mod.widgets.ButtonTertiary.draw_text.color_hover = #25282c
mod.widgets.ButtonTertiary.draw_text.color_down = #25282c
mod.widgets.ButtonTertiary.draw_text.color_focus = #34373b
mod.widgets.ButtonTertiary.draw_icon.color = #34373b
mod.widgets.ButtonTertiary.draw_text.text_style = mod.theme.font_label{font_size: 8.5}
mod.widgets.ButtonDanger.draw_text.color = #34373b
mod.widgets.ButtonDanger.draw_text.color_hover = #25282c
mod.widgets.ButtonDanger.draw_text.color_down = #25282c
mod.widgets.ButtonDanger.draw_text.color_focus = #34373b
mod.widgets.ButtonDanger.draw_icon.color = #34373b
mod.widgets.ButtonDanger.draw_text.text_style = mod.theme.font_bold{font_size: 8.5}
mod.widgets.ButtonOutline.draw_text.color = #34373b
mod.widgets.ButtonOutline.draw_text.color_hover = #25282c
mod.widgets.ButtonOutline.draw_text.color_down = #25282c
mod.widgets.ButtonOutline.draw_text.color_focus = #34373b
mod.widgets.ButtonOutline.draw_text.color_disabled = #a0a2a6
mod.widgets.ButtonOutline.draw_icon.color = #34373b
mod.widgets.ButtonFlat.draw_bg.loading_color = #55575b
// ---------------------------------------------------------------------------
// CHECK BOXES. A 14 point cap in a seat, white and convex while clear; set,
// it is pressed in and carries an engraved check, dark with a light line
// under it. The quad grows four points for the seat and the focus ring.
mod.widgets.CheckBoxFlat.clip_x = false
mod.widgets.CheckBoxFlat.clip_y = false
mod.widgets.CheckBoxFlat.draw_bg.vertex = fn() {
let g = md_grow(self.rect_pos, self.rect_size, 4.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.CheckBoxCustom.draw_bg.vertex = fn() {
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size)
}
mod.widgets.CheckBoxFlat.padding = mod.turtle.Inset{left: 0.0 right: 3.0 top: 3.0 bottom: 3.0}
mod.widgets.CheckBoxFlat.label_walk.margin = mod.turtle.Inset{left: 22.0 right: 3.0 top: 0.0 bottom: 0.0}
mod.widgets.CheckBoxFlat.draw_bg.size = uniform(14.0)
mod.widgets.CheckBoxFlat.min_height = 24.0
mod.widgets.CheckBoxFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = 4.0
let size = self.rect_size
let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let o = self.rect_pos
let sz = self.size
let live = 1.0 - self.disabled
let x0 = mix(1.0, size.x - sz - 1.0, self.mark_at_end)
let lo = vec2(Finish.snap(o.x + x0, px), Finish.snap(o.y + (size.y - sz) * 0.5, px)) - o
let h = vec2(sz * 0.5, sz * 0.5)
let c = lo + h
let r = mix(3.0, h.y, step(20.0, self.border_radius))
let d = md_rbox(p, c, h, r)
let t = clamp((p.y - lo.y) / sz, 0.0, 1.0)
let on = max(self.active, self.mixed)
let sunk = clamp(on + self.down * live * 0.7, 0.0, 1.0)
var face = mix(vec3(1.0, 1.0, 1.0), vec3(0.831, 0.835, 0.847), smoothstep(0.0, 1.0, t))
face = mix(face, mix(vec3(0.722, 0.725, 0.737), vec3(0.863, 0.867, 0.878), t), sunk)
face = mix(face, vec3(0.110, 0.118, 0.133), clamp(Finish.inset(p, c, h, r, vec2(0.0, 0.8), 0.9), 0.0, 1.0) * 0.22 * sunk)
face = Finish.lift(face, (0.55 * (1.0 - sunk) + 0.18 * sunk) * self.hover * live)
face = mix(face, vec3(0.910, 0.914, 0.918), self.disabled * 0.8)
let up = 1.0 - smoothstep(-0.5, 0.5, p.y - (lo.y + r))
face = mix(face, vec3(1.0, 1.0, 1.0), Finish.band(d, px, 2.0 * px, px) * up * 0.85 * (1.0 - sunk) * live)
let rim = mix(mix(vec3(0.478, 0.486, 0.502), vec3(0.290, 0.298, 0.314), t), vec3(0.290, 0.298, 0.314), self.hover * live)
face = mix(face, rim, Finish.band(d, 0.0, px, px) * mix(1.0, 0.45, self.disabled))
face = mix(face, vec3(0.784, 0.196, 0.184), Finish.band(d, 0.0, 2.0 * px, px) * self.error)
// the check, or the mixed dash: a 1.5 point stroke with a light line
// one device pixel under it
let k = sz / 14.0
let m0 = lo + vec2(3.6, 7.2) * k
let m1 = lo + vec2(6.0, 9.7) * k
let m2 = lo + vec2(10.5, 4.4) * k
let ck = min(md_seg(p, m0, m1), md_seg(p, m1, m2)) - 0.75 * k
let ck_lit = min(md_seg(p - vec2(0.0, px), m0, m1), md_seg(p - vec2(0.0, px), m1, m2)) - 0.75 * k
let d0 = lo + vec2(4.0, 7.0) * k
let d1 = lo + vec2(10.0, 7.0) * k
let dash = md_seg(p, d0, d1) - 0.75 * k
let dash_lit = md_seg(p - vec2(0.0, px), d0, d1) - 0.75 * k
let show_ck = self.active * (1.0 - self.mixed)
let mk = mix(Finish.cover(ck, px) * show_ck, Finish.cover(dash, px), self.mixed)
let mk_lit = mix(Finish.cover(ck_lit, px) * show_ck, Finish.cover(dash_lit, px), self.mixed)
face = mix(face, vec3(1.0, 1.0, 1.0), mk_lit * 0.5 * live)
let ink = mix(mix(vec3(0.204, 0.216, 0.231), vec3(0.784, 0.196, 0.184), self.error), vec3(0.627, 0.635, 0.651), self.disabled)
face = mix(face, ink, mk)
let a = Finish.cover(d, px)
var out = md_seat(d, p.y - c.y, h.y, px, mix(0.16 + 0.12 * self.hover * live, 0.08, self.disabled), mix(1.0, 0.5, self.disabled))
out = Finish.over(out, vec4(face * a, a))
return Finish.over(out, md_focus(d, px, self.focus * live))
}
// ---------------------------------------------------------------------------
// TOGGLES. A 38 by 9 point pill well, grey while off and lit lamp orange
// while on, carrying a 22 by 14 point dished knob that rides on it with its
// contact shadow and an engraved dot toward the free end.
mod.widgets.ToggleFlat.label_walk.margin = mod.turtle.Inset{left: 46.0 right: 3.0 top: 0.0 bottom: 0.0}
mod.widgets.ToggleFlat.draw_bg.size = 14.0
mod.widgets.ToggleFlat.min_height = 24.0
mod.widgets.ToggleFlat.draw_bg.pill_aspect = 2.7143
mod.widgets.ToggleFlat.draw_bg.knob_inset = 7.0
mod.widgets.ToggleFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = 4.0
let size = self.rect_size
let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let o = self.rect_pos
let live = 1.0 - self.disabled
let tw = 38.0
let x0 = mix(1.0, size.x - tw - 1.0, self.mark_at_end)
let lo = vec2(Finish.snap(o.x + x0, px), Finish.snap(o.y + (size.y - 14.0) * 0.5, px)) - o
// the well
let c = lo + vec2(tw * 0.5, 7.0)
let h = vec2(tw * 0.5, 4.5)
let r = h.y
let d = md_rbox(p, c, h, r)
let on = self.active
let grey = mix(vec3(0.659, 0.663, 0.678), vec3(0.800, 0.804, 0.812), self.disabled)
let lamp = mix(vec3(0.918, 0.353, 0.067), vec3(0.851, 0.694, 0.612), self.disabled)
var fill = mix(grey, lamp, on)
// the lit well is a shade deeper in its upper band
fill = mix(fill, mix(fill, vec3(0.824, 0.325, 0.047), on), 1.0 - smoothstep(-2.5, 0.0, p.y - c.y))
fill = Finish.lift(fill, 0.26 * self.hover * live)
let rimc = mix(mix(vec3(0.416, 0.424, 0.439), vec3(0.478, 0.196, 0.047), on), vec3(0.784, 0.196, 0.184), self.error)
let col = md_well(p, c, h, r, px, fill, mix(0.30, 0.12, self.disabled), rimc)
let a = Finish.cover(d, px)
var out = md_seat(d, p.y - c.y, h.y, px, 0.16, mix(1.0, 0.5, self.disabled))
out = Finish.over(out, vec4(col * a, a))
// the knob
let kt = clamp(mix(on, self.drag_pos, self.drag), 0.0, 1.0)
let kh = vec2(11.0, 7.0)
let kx = Finish.snap(o.x + lo.x + kt * (tw - 22.0), px) - o.x
let kc = vec2(kx + 11.0, lo.y + 7.0)
let kd = md_rbox(p, kc, kh, 7.0)
let press = self.down * live
// under the pointer the knob stands a little prouder: its shadow deepens
out = Finish.over(out, md_contact(p, kc, kh, 7.0, (1.0 + 0.5 * self.hover * live - press * 0.8) * mix(1.0, 0.35, self.disabled)))
let kt2 = clamp((p.y - (kc.y - 7.0)) / 14.0, 0.0, 1.0)
// dished: darker at the top, a convex rim ring round it
var face = mix(vec3(0.776, 0.780, 0.788), vec3(0.933, 0.937, 0.945), smoothstep(0.1, 0.9, kt2))
let rim_ring = Finish.band(kd, px, px + 1.0, px)
face = mix(face, mix(vec3(0.980, 0.980, 0.984), vec3(0.878, 0.882, 0.890), kt2), rim_ring)
face = Finish.lift(face, 0.45 * self.hover * live * (1.0 - press) - 0.10 * press)
face = mix(face, vec3(0.902, 0.906, 0.910), self.disabled * 0.7)
// the engraved dot toward the free end
let dc = kc + vec2(mix(5.5, -5.5, kt), 0.0)
let dd = length(p - dc) - 1.4
face = mix(face, vec3(1.0, 1.0, 1.0), Finish.ring_out(dd, 0.0, px, px) * step(dc.y, p.y) * 0.8 * live)
face = mix(face, mix(vec3(0.478, 0.486, 0.502), vec3(0.745, 0.749, 0.761), smoothstep(-1.4, 1.4, p.y - dc.y)), Finish.cover(dd, px) * live)
let up = 1.0 - smoothstep(-0.5, 0.5, p.y - (kc.y - 3.0))
face = mix(face, vec3(1.0, 1.0, 1.0), Finish.band(kd, px, 2.0 * px, px) * up * 0.85 * live)
let olc = mix(vec3(0.557, 0.565, 0.576), vec3(0.212, 0.220, 0.231), smoothstep(0.15, 0.85, kt2))
face = mix(face, olc, Finish.band(kd, 0.0, px, px) * mix(1.0, 0.45, self.disabled))
let ka = Finish.cover(kd, px)
out = Finish.over(out, vec4(face * ka, ka))
return Finish.over(out, md_focus(d, px, self.focus * live))
}
// ---------------------------------------------------------------------------
// RADIOS. The check box made round: a convex white cap while clear; chosen,
// it is pressed in with a dark dot at its centre, lit from below.
let md_radio = fn(p: vec2, c: vec2, rad: float, px: float, hover: float, down: float, focus: float, active: float, dis: float) -> vec4 {
let live = 1.0 - dis
let q = p - c
let d = length(q) - rad
let t = clamp(q.y / rad * 0.5 + 0.5, 0.0, 1.0)
let sunk = clamp(active + down * live * 0.7, 0.0, 1.0)
var face = mix(vec3(1.0, 1.0, 1.0), vec3(0.831, 0.835, 0.847), smoothstep(0.0, 1.0, t))
face = mix(face, mix(vec3(0.722, 0.725, 0.737), vec3(0.863, 0.867, 0.878), t), sunk)
face = mix(face, vec3(0.110, 0.118, 0.133), clamp(Finish.inset(p, c, vec2(rad, rad), rad, vec2(0.0, 0.8), 0.9), 0.0, 1.0) * 0.22 * sunk)
face = Finish.lift(face, (0.55 * (1.0 - sunk) + 0.18 * sunk) * hover * live)
face = mix(face, vec3(0.910, 0.914, 0.918), dis * 0.8)
let up = 1.0 - smoothstep(-0.3, 0.1, q.y / rad)
face = mix(face, vec3(1.0, 1.0, 1.0), Finish.band(d, px, 2.0 * px, px) * up * 0.85 * (1.0 - sunk) * live)
// the dot: 0.36 of the diameter, a light line under it
let dr = rad * 0.36
let dd = length(q) - dr
face = mix(face, vec3(1.0, 1.0, 1.0), Finish.ring_out(dd, 0.0, px, px) * step(0.0, q.y) * 0.6 * active * live)
face = mix(face, mix(vec3(0.204, 0.216, 0.231), vec3(0.627, 0.635, 0.651), dis), Finish.cover(dd, px) * active)
let rim = mix(mix(vec3(0.478, 0.486, 0.502), vec3(0.290, 0.298, 0.314), t), vec3(0.290, 0.298, 0.314), hover * live)
face = mix(face, rim, Finish.band(d, 0.0, px, px) * mix(1.0, 0.45, dis))
let a = Finish.cover(d, px)
var out = md_seat(d, q.y, rad, px, mix(0.16 + 0.12 * hover * live, 0.08, dis), mix(1.0, 0.5, dis))
out = Finish.over(out, vec4(face * a, a))
return Finish.over(out, md_focus(d, px, focus * live))
}
mod.widgets.RadioButtonFlat.clip_x = false
mod.widgets.RadioButtonFlat.clip_y = false
mod.widgets.RadioButtonFlat.draw_bg.vertex = fn() {
let g = md_grow(self.rect_pos, self.rect_size, 4.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.glass.RadioButton.draw_bg.vertex = fn() {
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size)
}
mod.widgets.RadioButtonFlat.padding = mod.turtle.Inset{left: 0.0 right: 3.0 top: 3.0 bottom: 3.0}
mod.widgets.RadioButtonFlat.label_walk.margin = mod.turtle.Inset{left: 22.0 right: 3.0 top: 0.0 bottom: 0.0}
mod.widgets.RadioButtonFlat.draw_bg.size = uniform(14.0)
mod.widgets.RadioButtonFlat.min_height = 24.0
mod.widgets.RadioButtonFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = 4.0
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let o = self.rect_pos
let sz = self.size
let lo = vec2(Finish.snap(o.x + 1.0, px), Finish.snap(o.y + (self.rect_size.y - sz) * 0.5, px)) - o
let c = lo + vec2(sz * 0.5, sz * 0.5)
return md_radio(p, c, sz * 0.5, px, self.hover, self.down, self.focus, self.active, self.disabled)
}
// The flatter radio is its word alone: chosen, the word sits in a shallow
// pressed-in pocket; under the pointer the housing lifts behind it.
mod.widgets.RadioButtonFlatter.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = 4.0
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let o = self.rect_pos
let live = 1.0 - self.disabled
let lo = vec2(Finish.snap(o.x - 3.0, px), Finish.snap(o.y + 1.0, px)) - o
let hi = vec2(Finish.snap(o.x + self.rect_size.x + 3.0, px), Finish.snap(o.y + self.rect_size.y - 1.0, px)) - o
let h = (hi - lo) * 0.5
let c = lo + h
let d = md_rbox(p, c, h, 4.0)
let lift = Finish.cover(d, px) * 0.25 * self.hover * live * (1.0 - self.active)
var out = vec4(lift, lift, lift, lift)
let t = clamp((p.y - lo.y) / max(2.0 * h.y, 1.0), 0.0, 1.0)
var col = mix(vec3(0.765, 0.769, 0.780), vec3(0.851, 0.855, 0.863), t)
col = mix(col, vec3(0.110, 0.118, 0.133), clamp(Finish.inset(p, c, h, 4.0, vec2(0.0, 1.0), 1.0), 0.0, 1.0) * 0.18)
col = mix(col, vec3(0.416, 0.424, 0.439), Finish.band(d, 0.0, px, px) * 0.6)
let a = Finish.cover(d, px) * self.active
out = Finish.over(out, md_seat(d, p.y - c.y, h.y, px, 0.0, self.active))
out = Finish.over(out, vec4(col * a, a))
return Finish.over(out, md_focus(d, px, self.focus * live))
}
// Radio tabs: each a cap of its own; the chosen one is held pressed in.
// The label lays out from the top of the 26 point cap: 6 above it puts
// the capitals on the cap's middle.
mod.widgets.RadioButtonTabFlat.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 6.0 bottom: 2.0}
mod.widgets.RadioButtonTabFlat.label_walk.margin = mod.turtle.Inset{left: 0.0 right: 0.0 top: 0.0 bottom: 0.0}
mod.widgets.RadioButtonTabFlat.min_height = 26.0
mod.widgets.RadioButtonTabFlat.draw_bg.vertex = fn() {
let g = md_grow(self.rect_pos, self.rect_size, 4.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.RadioButtonTabFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let held = clamp(max(self.active, self.down), 0.0, 1.0)
return md_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, held, self.focus, self.disabled, 0.0, 0.0, vec4(5.0, 5.0, 5.0, 5.0), 0.0, 4.0, vec4(0.0, 0.0, 0.0, 0.0), 0.0)
}
mod.widgets.RadioButtonTabFlat.draw_text.color = #4a4d52
mod.widgets.RadioButtonTabFlat.draw_text.color_active = #25282c
mod.widgets.RadioButtonTabFlat.draw_text.color_disabled = #a0a2a6
// ---------------------------------------------------------------------------
// SLIDERS. A 6 point pill well in a seat, the graphite fill inside it, and
// a 22 by 14 point riding thumb with five engraved grip ridges. The quad of
// every slider grows four points for the thumb's contact shadow, the seat
// and the focus ring.
mod.widgets.SliderMinimal.clip_x = false
mod.widgets.SliderMinimal.clip_y = false
mod.widgets.SliderMinimal.draw_bg.vertex = fn() {
let g = md_grow(self.rect_pos, self.rect_size, 4.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
// The riding pill thumb: contact shadows, two-tone hairline, the white line
// inside the top, a convex face and the grip ridges (a dark and a light
// device pixel on a two point pitch, over the middle 60 % of its height).
let md_thumb = fn(p: vec2, c: vec2, h: vec2, o: vec2, px: float, hover: float, press: float, dis: float, ridges: float) -> vec4 {
let live = 1.0 - dis
let r = min(h.x, h.y)
var out = md_contact(p, c, h, r, (1.0 + 0.5 * hover * live - press * 0.75) * mix(1.0, 0.35, dis))
let d = md_rbox(p, c, h, r)
let t = clamp((p.y - (c.y - h.y)) / (2.0 * h.y), 0.0, 1.0)
var face = mix(vec3(0.973, 0.976, 0.984), vec3(0.851, 0.855, 0.863), smoothstep(0.05, 1.0, t))
face = mix(face, vec3(0.690, 0.694, 0.706), smoothstep(0.82, 1.0, t) * 0.45)
face = mix(face, mix(vec3(0.776, 0.780, 0.792), vec3(0.910, 0.914, 0.922), t), press)
face = Finish.lift(face, 0.45 * hover * live * (1.0 - press))
face = mix(face, vec3(0.902, 0.906, 0.910), dis * 0.7)
if ridges > 0.5 {
let fi = clamp(floor((p.x - c.x) / 2.0 + 0.5), -(ridges - 1.0) * 0.5, (ridges - 1.0) * 0.5)
let rx = Finish.snap(o.x + c.x + fi * 2.0 - px, px) - o.x
let iny = step(abs(p.y - c.y), h.y * 0.6)
let dk = step(rx, p.x) * step(p.x, rx + px) * iny
let lt = step(rx + px, p.x) * step(p.x, rx + 2.0 * px) * iny
face = mix(face, vec3(0.776, 0.780, 0.792), dk * live)
face = mix(face, vec3(1.0, 1.0, 1.0), lt * 0.6 * live)
}
let up = 1.0 - smoothstep(-0.5, 0.5, p.y - (c.y - h.y * 0.4))
face = mix(face, vec3(1.0, 1.0, 1.0), Finish.band(d, px, 2.0 * px, px) * up * mix(0.85, 0.35, press) * live)
let olc = mix(vec3(0.557, 0.565, 0.576), vec3(0.212, 0.220, 0.231), smoothstep(0.15, 0.85, t))
face = mix(face, olc, Finish.band(d, 0.0, px, px) * mix(1.0, 0.45, dis))
let a = Finish.cover(d, px)
return Finish.over(out, vec4(face * a, a))
}
// The track: a pill well in its seat, filled with graphite between `f0`
// and `f1`, the fill a convex strip one device pixel in from the rim.
let md_track = fn(p: vec2, c: vec2, h: vec2, px: float, f0: float, f1: float, well: vec3, dis: float, pale: float) -> vec4 {
let r = h.y
let d = md_rbox(p, c, h, r)
var col = md_well(p, c, h, r, px, well, mix(0.30, 0.12, dis), vec3(0.416, 0.424, 0.439))
let fh = vec2((f1 - f0) * 0.5 + h.y - px, h.y - px)
let fc = vec2((f0 + f1) * 0.5, c.y)
let fd = md_rbox(p, fc, fh, fh.y)
let ft = clamp((p.y - (fc.y - fh.y)) / max(2.0 * fh.y, 0.5), 0.0, 1.0)
var fcol = mix(vec3(0.549, 0.557, 0.573), vec3(0.333, 0.341, 0.357), ft)
fcol = mix(fcol, mix(vec3(0.851, 0.855, 0.867), vec3(0.639, 0.647, 0.663), ft), pale)
fcol = mix(fcol, vec3(0.200, 0.204, 0.212), Finish.band(fd, 0.0, px, px) * smoothstep(0.5, 0.9, ft))
fcol = mix(fcol, vec3(1.0, 1.0, 1.0), Finish.band(fd, 0.0, px, px) * (1.0 - smoothstep(0.1, 0.5, ft)) * 0.25)
fcol = mix(fcol, vec3(0.760, 0.765, 0.776), dis * 0.8)
col = mix(col, fcol, Finish.cover(fd, px) * step(0.01, f1 - f0 + 0.02))
let a = Finish.cover(d, px)
var out = md_seat(d, p.y - c.y, h.y, px, 0.16, mix(1.0, 0.5, dis))
return Finish.over(out, vec4(col * a, a))
}
mod.widgets.SliderFlat.height = 32.0
mod.widgets.SliderFlat.clip_x = false
mod.widgets.SliderFlat.clip_y = false
mod.widgets.SliderFlat.draw_bg.vertex = fn() {
let g = md_grow(self.rect_pos, self.rect_size, 4.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.SliderFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = 4.0
let size = self.rect_size
let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let o = self.rect_pos
let live = 1.0 - self.disabled
let cy = Finish.snap(o.y + size.y - 8.0, px) - o.y
let x0 = Finish.snap(o.x + 1.0, px) - o.x
let x1 = Finish.snap(o.x + size.x - 1.0, px) - o.x
let w = x1 - x0
let span = w - 22.0
let kx = x0 + 11.0 + self.slide_pos * span
let ox = x0 + 11.0 + self.origin_pos * span
let f0 = mix(x0 + 3.0, min(ox, kx), self.arc_origin)
let f1 = mix(kx, max(ox, kx), self.arc_origin)
let tc = vec2((x0 + x1) * 0.5, cy)
// under the pointer the well lightens a shade with the thumb
let well = Finish.lift(vec3(0.659, 0.663, 0.678), 0.22 * max(self.hover, self.drag) * live)
var out = md_track(p, tc, vec2(w * 0.5, 3.0), px, f0, f1, well, self.disabled, 0.0)
let kc = vec2(Finish.snap(o.x + kx, px) - o.x, cy)
let kh = vec2(11.0, 7.0)
out = Finish.over(out, md_thumb(p, kc, kh, o, px, max(self.hover, self.focus * 0.5), self.drag * live, self.disabled, 5.0))
return Finish.over(out, md_focus(md_rbox(p, kc, kh, 7.0), px, self.focus * live))
}
mod.widgets.SliderFlat.text_input.draw_text.text_style = mod.theme.font_regular{font_size: 7.75}
// The range slider: the slider's well in its countersink, the graphite fill
// between the two ends, and a riding thumb on each end, pressed in while it
// is the one held. The widget lays the ends out with these numbers.
mod.widgets.RangeSliderFlat.handle_size = 22.0
mod.widgets.RangeSliderFlat.track_inset = 1.0
mod.widgets.RangeSliderFlat.draw_bg.vertex = fn() {
let g = md_grow(self.rect_pos, self.rect_size, 4.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.RangeSliderFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = 4.0
let size = self.rect_size
let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let o = self.rect_pos
let live = 1.0 - self.disabled
let top = self.label_px
let cy = Finish.snap(o.y + top + (size.y - top) * 0.5, px) - o.y
let x0 = Finish.snap(o.x + 1.0, px) - o.x
let x1 = Finish.snap(o.x + size.x - 1.0, px) - o.x
let w = x1 - x0
let span = max(size.x - self.inset_px * 2.0 - self.handle_px, 1.0)
let sx = self.inset_px + self.handle_px * 0.5 + self.start_pos * span
let ex = self.inset_px + self.handle_px * 0.5 + self.end_pos * span
let well = Finish.lift(vec3(0.659, 0.663, 0.678), 0.22 * max(self.hover, self.drag) * live)
var out = md_track(p, vec2((x0 + x1) * 0.5, cy), vec2(w * 0.5, 3.0), px, sx, ex, well, self.disabled, 0.0)
let kh = vec2(self.handle_px * 0.5, 7.0)
let k0 = vec2(Finish.snap(o.x + sx, px) - o.x, cy)
let k1 = vec2(Finish.snap(o.x + ex, px) - o.x, cy)
out = Finish.over(out, md_thumb(p, k0, kh, o, px, max(self.hot_start, self.focus * 0.5) * live, self.drag * self.hot_start * live, self.disabled, 5.0))
out = Finish.over(out, md_thumb(p, k1, kh, o, px, max(self.hot_end, self.focus * 0.5) * live, self.drag * self.hot_end * live, self.disabled, 5.0))
return out
}
// The minimal slider: an engraved hairline groove (a dark device pixel over
// a light one), a graphite thread in it and a small round riding cap.
mod.widgets.SliderMinimal.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = 4.0
let size = self.rect_size
let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let o = self.rect_pos
let live = 1.0 - self.disabled
let cy = Finish.snap(o.y + size.y - 6.0, px) - o.y
let kr = 5.0
let kx = kr + self.slide_pos * (size.x - 2.0 * kr)
let inx = step(0.0, p.x) * step(p.x, size.x)
let dark = step(cy - px, p.y) * step(p.y, cy) * inx
let light = step(cy, p.y) * step(p.y, cy + px) * inx
let thread = step(cy - 2.0 * px, p.y) * step(p.y, cy) * step(p.x, kx) * inx
var out = vec4(0.0, 0.0, 0.0, 0.0)
out = Finish.over(out, vec4(light * 0.8, light * 0.8, light * 0.8, light * 0.8))
let da = dark * 0.45 * mix(1.0, 0.5, self.disabled)
out = Finish.over(out, vec4(0.110 * da, 0.118 * da, 0.133 * da, da))
let tcol = mix(vec3(0.333, 0.341, 0.357), vec3(0.690, 0.694, 0.706), self.disabled)
out = Finish.over(out, vec4(tcol * thread, thread))
let kc = vec2(Finish.snap(o.x + kx, px) - o.x, cy - 0.5 * px)
let kh = vec2(kr, kr)
out = Finish.over(out, md_thumb(p, kc, kh, o, px, self.hover, self.drag * live, self.disabled, 0.0))
return Finish.over(out, md_focus(length(p - kc) - kr, px, self.focus * live))
}
mod.widgets.SliderMinimal.text_input.draw_text.text_style = mod.theme.font_regular{font_size: 7.75}
// The round slider: a light pill channel beside its label, a graphite
// capsule in it and a small round riding cap on the capsule's end; the value
// reads on the channel.
mod.widgets.SliderRoundFlat.height = 16.0
mod.widgets.SliderRoundFlat.clip_x = false
mod.widgets.SliderRoundFlat.clip_y = false
mod.widgets.SliderRoundFlat.draw_bg.vertex = fn() {
let g = md_grow(self.rect_pos, self.rect_size, 4.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.SliderRoundFlat.text_input.margin = mod.turtle.Inset{left: 0.0 right: 8.0 top: 0.0 bottom: 0.0}
mod.widgets.SliderRoundFlat.text_input.width = 30.0
mod.widgets.SliderRoundFlat.text_input.min_height = 0.0
mod.widgets.SliderRoundFlat.text_input.padding = mod.turtle.Inset{left: 0.0 right: 0.0 top: 0.0 bottom: 0.0}
mod.widgets.SliderRoundFlat.text_input.draw_text.text_style = mod.theme.font_regular{font_size: 7.75}
mod.widgets.SliderRoundFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = 4.0
let size = self.rect_size
let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let o = self.rect_pos
let live = 1.0 - self.disabled
let x0 = Finish.snap(o.x + self.label_size, px) - o.x
let y0 = Finish.snap(o.y + 1.0, px) - o.y
// the channel ends before the value's column (30 wide, 8 from the end)
let x1 = Finish.snap(o.x + size.x - 44.0, px) - o.x
let y1 = Finish.snap(o.y + size.y - 1.0, px) - o.y
let h = vec2((x1 - x0) * 0.5, (y1 - y0) * 0.5)
let c = vec2(x0, y0) + h
let ins = 2.0
let kr = h.y - ins
let kx = x0 + ins + kr + self.slide_pos * (2.0 * h.x - 2.0 * ins - 2.0 * kr)
let well = Finish.lift(vec3(0.910, 0.910, 0.914), 0.3 * self.hover * live)
var out = md_track(p, c, h, px, x0 + ins, kx + kr, well, self.disabled, 1.0)
let kc = vec2(Finish.snap(o.x + kx, px) - o.x, c.y)
out = Finish.over(out, md_thumb(p, kc, vec2(kr, kr), o, px, self.hover, self.drag * live, self.disabled, 0.0))
return Finish.over(out, md_focus(md_rbox(p, c, h, h.y), px, self.focus * live))
}
// ---------------------------------------------------------------------------
// FADERS. A black slot three points wide with a light lip on its lower (or
// right) side, engraved ticks either side, and a riding cap 22 points across
// and 12 along the travel, banded light at the top, a centre line engraved
// across it.
mod.widgets.SliderFader.cap_size = 12.0
mod.widgets.SliderFader.clip_x = false
mod.widgets.SliderFader.clip_y = false
mod.widgets.SliderFader.draw_bg.vertex = fn() {
let g = md_grow(self.rect_pos, self.rect_size, 4.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.SliderFader.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = 4.0
let size = self.rect_size
let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let o = self.rect_pos
let live = 1.0 - self.disabled
let vert = self.is_vertical
// track space: `a` along the travel from its zero, `b` across
let a = mix(p.x, size.y - p.y, vert)
let b = mix(p.y, p.x, vert)
let along = mix(size.x, size.y, vert)
let across = mix(size.y, size.x, vert)
let travel = max(along - self.inset_px * 2.0 - self.cap_px, 1.0)
let foot = self.inset_px + self.cap_px * 0.5
let mid = floor(across * 0.5 / px) * px
// the slot
let sw = 3.0
let q = vec2(a, b)
let sc = vec2(along * 0.5, mid)
let sh = vec2(along * 0.5 - self.inset_px, sw * 0.5)
let sd = md_rbox(q, sc, sh, 0.5)
// the slot sits in a countersink, its rim lit from straight above on the
// screen (the top end of a standing slot is the side in shade)
let yscr = mix(b - mid, along * 0.5 - a, vert)
let hscr = mix(sh.y, sh.x, vert)
var out = md_sink(sd, yscr, max(hscr, sw * 0.5), 2.0, px, mix(0.16, 0.08, self.disabled), mix(1.0, 0.5, self.disabled))
let sa = Finish.cover(sd, px)
let scol = mix(vec3(0.165, 0.173, 0.188), vec3(0.500, 0.506, 0.518), self.disabled)
out = Finish.over(out, vec4(scol * sa, sa))
// the value: a one point graphite line in the slot, from the origin
// (or the foot) to the cap; flat, no halo
let va0 = foot + self.fill_lo * travel
let va1 = foot + self.fill_hi * travel
let vl = step(va0, a) * step(a, va1) * (1.0 - smoothstep(0.5 - px * 0.5, 0.5 + px * 0.5, abs(b - mid))) * sa * live
out = Finish.over(out, vec4(0.333 * vl, 0.341 * vl, 0.357 * vl, vl))
// ticks: a long one every quarter of the travel, a short one between,
// two points off the slot on both sides
// three short ones between two long ones, whatever the travel
let nt = 16.0
let f = (a - foot) / travel * nt
let fi = floor(f + 0.5)
let da = abs(a - (foot + fi / nt * travel))
let major = 1.0 - step(0.1, abs(fi * 0.25 - floor(fi * 0.25 + 0.5)))
let tl = mix(3.0, 5.0, major)
let off = abs(b - mid) - sw * 0.5 - 3.5
let in_rng = step(-0.5, fi) * step(fi, nt + 0.5)
let tk = (1.0 - smoothstep(px * 0.5 - 0.01, px * 0.5 + 0.01, da)) * step(0.0, off) * step(off, tl) * in_rng * 0.55 * mix(1.0, 0.5, self.disabled)
out = Finish.over(out, vec4(0.204 * tk, 0.216 * tk, 0.231 * tk, tk))
// the cap, drawn in screen space so its light stays on top
let at = foot + self.slide_pos * travel
let cw = min(22.0, across)
let cs = mix(vec2(at, mid), vec2(mid, size.y - at), vert)
let ch = mix(vec2(self.cap_px * 0.5, cw * 0.5), vec2(cw * 0.5, self.cap_px * 0.5), vert)
let cc = vec2(Finish.snap(o.x + cs.x, px) - o.x, Finish.snap(o.y + cs.y, px) - o.y)
let cd = md_rbox(p, cc, ch, 2.0)
let press = self.drag * live
out = Finish.over(out, md_contact(p, cc, ch, 2.0, (1.0 - press * 0.6) * mix(1.0, 0.35, self.disabled)))
let t = clamp((p.y - (cc.y - ch.y)) / (2.0 * ch.y), 0.0, 1.0)
var face = mix(vec3(0.941, 0.945, 0.949), vec3(0.827, 0.831, 0.843), t)
face = mix(face, vec3(0.984, 0.984, 0.988), 1.0 - step(0.15, t))
face = mix(face, vec3(0.749, 0.753, 0.765), step(0.8, t))
face = Finish.lift(face, 0.25 * max(self.hover, self.focus * 0.5) * live * (1.0 - press) - 0.05 * press)
face = mix(face, vec3(0.902, 0.906, 0.910), self.disabled * 0.7)
// the centre line across the cap, engraved
let ly = Finish.snap(o.y + cc.y, px) - o.y
let lx = Finish.snap(o.x + cc.x, px) - o.x
let lin_h = step(ly - px, p.y) * step(p.y, ly) * step(abs(p.x - cc.x), ch.x - 1.0)
let lit_h = step(ly, p.y) * step(p.y, ly + px) * step(abs(p.x - cc.x), ch.x - 1.0)
let lin_v = step(lx - px, p.x) * step(p.x, lx) * step(abs(p.y - cc.y), ch.y - 1.0)
let lit_v = step(lx, p.x) * step(p.x, lx + px) * step(abs(p.y - cc.y), ch.y - 1.0)
face = mix(face, vec3(0.110, 0.118, 0.133), mix(lin_h, lin_v, 1.0 - vert) * 0.55)
face = mix(face, vec3(1.0, 1.0, 1.0), mix(lit_h, lit_v, 1.0 - vert) * 0.5)
let up = 1.0 - smoothstep(-0.5, 0.5, p.y - (cc.y - ch.y + 2.0))
face = mix(face, vec3(1.0, 1.0, 1.0), Finish.band(cd, px, 2.0 * px, px) * up * 0.85 * live)
let olc = mix(vec3(0.557, 0.565, 0.576), vec3(0.212, 0.220, 0.231), smoothstep(0.15, 0.85, t))
face = mix(face, olc, Finish.band(cd, 0.0, px, px) * mix(1.0, 0.45, self.disabled))
let ca = Finish.cover(cd, px)
out = Finish.over(out, vec4(face * ca, ca))
return Finish.over(out, md_focus(cd, px, self.focus * live))
}
// ---------------------------------------------------------------------------
// KNOBS. A matte riding cap with its two contact shadows, a two-tone
// hairline, a white arc over its top, a linear face falling from light to
// mid grey with a darker foot, and a concave dimple for the index, turned
// with the value. Round it, a 2 point value arc: the track a faint engraving,
// the value lamp orange, flat, never lit.
let md_knob = fn(p: vec2, c: vec2, rad: float, px: float, ang: float, hover: float, press: float, dis: float) -> vec4 {
let dia = rad * 2.0
let hh = vec2(rad, rad)
let live = 1.0 - dis
let fade = mix(1.0, 0.35, dis)
let tight = Finish.drop(p, c, hh, rad, vec2(0.0, max(dia * 0.04, 0.8)), max(dia * 0.02, 0.5)) * 0.30 * fade * (1.0 - press * 0.5)
let soft = Finish.drop(p, c, hh, rad, vec2(0.0, max(dia * 0.05, 1.2)), max(dia * 0.045, 0.9)) * 0.16 * fade
let sa = tight + soft * (1.0 - tight)
var out = vec4(0.110 * sa, 0.118 * sa, 0.133 * sa, sa)
let q = p - c
let d = length(q) - rad
let t = clamp(q.y / rad * 0.5 + 0.5, 0.0, 1.0)
var face = mix(vec3(0.941, 0.941, 0.945), vec3(0.698, 0.702, 0.714), clamp((t - 0.05) / 0.9, 0.0, 1.0))
face = mix(face, vec3(0.498, 0.498, 0.498), Finish.band(d, 0.0, dia * 0.06, px) * smoothstep(0.55, 0.95, t) * 0.5)
face = Finish.lift(face, 0.22 * hover * live * (1.0 - press) - 0.05 * press)
face = mix(face, vec3(0.890, 0.894, 0.898), dis * 0.7)
// the dimple: 0.16 of the diameter, 0.33 of it out from the centre
let dir = vec2(-sin(ang), cos(ang))
let dc = c + dir * dia * 0.33
let dr = max(dia * 0.08, 1.5)
let dq = p - dc
let dd = length(dq) - dr
let dim = mix(vec3(0.784, 0.788, 0.796), vec3(0.863, 0.867, 0.875), smoothstep(-dr, dr, dq.y))
face = mix(face, mix(face, dim, 0.85), Finish.cover(dd, px) * live)
face = mix(face, vec3(0.557, 0.565, 0.576), Finish.band(dd, 0.0, px, px) * (1.0 - smoothstep(-0.3, 0.3, dq.y / dr)) * 0.7 * live)
face = mix(face, vec3(1.0, 1.0, 1.0), Finish.ring_out(dd, 0.0, px, px) * smoothstep(-0.2, 0.4, dq.y / dr) * 0.6 * live)
// the white arc over the top 120 degrees
let up = smoothstep(-0.2, -0.6, q.y / rad)
face = mix(face, vec3(1.0, 1.0, 1.0), Finish.band(d, px, 2.0 * px, px) * up * 0.85 * live)
let olc = mix(vec3(0.592, 0.592, 0.592), vec3(0.161, 0.161, 0.161), smoothstep(0.15, 0.85, t))
face = mix(face, olc, Finish.band(d, 0.0, px, px) * mix(1.0, 0.45, dis))
let a = Finish.cover(d, px)
return Finish.over(out, vec4(face * a, a))
}
// The value arc: `ar` its middle radius, `aw` its width; the gap is centred
// at the bottom and the value runs clockwise from its left end, or from the
// origin when the knob is bipolar.
let md_arc = fn(p: vec2, c: vec2, ar: float, aw: float, px: float, v0: float, v1: float, gap: float, dis: float) -> vec4 {
let start = gap * 0.5
let sweep = 2.0 * PI - gap
let q = p - c
var ang = atan2(-q.x, q.y)
if ang < 0.0 {
ang = ang + 2.0 * PI
}
let rd = abs(length(q) - ar) - aw * 0.5
let in_arc = step(start, ang) * step(ang, start + sweep)
let e0 = c + vec2(-sin(start), cos(start)) * ar
let e1 = c + vec2(-sin(start + sweep), cos(start + sweep)) * ar
let track_d = min(mix(1000.0, rd, in_arc), min(length(p - e0), length(p - e1)) - aw * 0.5)
let a0 = start + sweep * v0
let a1 = start + sweep * v1
let l0 = c + vec2(-sin(a0), cos(a0)) * ar
let l1 = c + vec2(-sin(a1), cos(a1)) * ar
let lit_in = step(a0, ang) * step(ang, a1)
let lit_d = min(mix(1000.0, rd, lit_in), min(length(p - l0), length(p - l1)) - aw * 0.5)
let ta = Finish.cover(track_d, px) * 0.13
var out = vec4(0.110 * ta, 0.118 * ta, 0.133 * ta, ta)
let lt = Finish.ring_out(track_d, 0.0, px, px) * step(0.0, q.y) * 0.5 * step(length(q), ar + aw)
out = Finish.over(out, vec4(lt, lt, lt, lt))
// the value at 0.85: the knob group is the page's accent, not its loudest
let la = Finish.cover(lit_d, px) * 0.85
let ink = mix(vec3(0.918, 0.353, 0.067), vec3(0.749, 0.690, 0.659), dis)
return Finish.over(out, vec4(ink * la, la))
}
mod.widgets.RotaryKnob.clip_x = false
mod.widgets.RotaryKnob.clip_y = false
mod.widgets.RotaryKnob.draw_bg.vertex = fn() {
let g = md_grow(self.rect_pos, self.rect_size, 4.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.RotaryKnob.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = 4.0
let size = self.rect_size
let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let o = self.rect_pos
let live = 1.0 - self.disabled
let dia = min(size.x, size.y)
let rr = dia * 0.5 - 1.0
let c = vec2(Finish.snap(o.x + size.x * 0.5, px) - o.x, Finish.snap(o.y + size.y - dia * 0.5, px) - o.y)
let gap = self.gap * PI / 180.0
let aw = 2.0
let ar = floor((rr - aw * 0.5) / px) * px
let rad = floor(ar / 1.18 / px) * px
let lo = min(self.origin_pos, self.slide_pos)
let hi = max(self.origin_pos, self.slide_pos)
let v0 = mix(0.0, lo, self.arc_origin)
let v1 = mix(self.slide_pos, hi, self.arc_origin)
// the hole the knob sits in, milled with its rim inside the value arc,
// and the arc printed on the housing round it
let kw = max(ar - aw * 0.5 - rad - 0.5, 1.0)
var out = md_sink(length(p - c) - rad, p.y - c.y, rad, kw, px, mix(0.16, 0.08, self.disabled), mix(1.0, 0.5, self.disabled))
out = Finish.over(out, md_arc(p, c, ar, aw, px, v0, v1, gap, self.disabled))
let ang = gap * 0.5 + (2.0 * PI - gap) * self.slide_pos
out = Finish.over(out, md_knob(p, c, rad, px, ang, max(self.hover, self.focus * 0.5), self.drag * live, self.disabled))
return Finish.over(out, md_focus(length(p - c) - ar - aw * 0.5, px, self.focus * live))
}
// Knob captions are hardware labels: small semibold words in a mid grey.
mod.widgets.RotaryKnob.draw_text.color = #56595d
mod.widgets.RotaryKnob.draw_text.text_style = mod.theme.font_bold{font_size: 7.1}
mod.widgets.RotaryFlat.clip_x = false
mod.widgets.RotaryFlat.clip_y = false
mod.widgets.RotaryFlat.draw_bg.vertex = fn() {
let g = md_grow(self.rect_pos, self.rect_size, 4.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.RotaryFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = 4.0
let size = self.rect_size
let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let o = self.rect_pos
let live = 1.0 - self.disabled
let room = vec2(size.x, size.y - 24.0)
let dia = min(room.x, room.y)
let rr = dia * 0.5 - 1.0
let c = vec2(Finish.snap(o.x + size.x * 0.5, px) - o.x, Finish.snap(o.y + 24.0 + room.y * 0.5, px) - o.y)
let gap = self.gap * PI / 180.0
let aw = 2.0
let ar = floor((rr - aw * 0.5) / px) * px
let rad = floor(ar / 1.18 / px) * px
let lo = min(self.origin_pos, self.slide_pos)
let hi = max(self.origin_pos, self.slide_pos)
let v0 = mix(0.0, lo, self.arc_origin)
let v1 = mix(self.slide_pos, hi, self.arc_origin)
// the hole the knob sits in, milled with its rim inside the value arc,
// and the arc printed on the housing round it
let kw = max(ar - aw * 0.5 - rad - 0.5, 1.0)
var out = md_sink(length(p - c) - rad, p.y - c.y, rad, kw, px, mix(0.16, 0.08, self.disabled), mix(1.0, 0.5, self.disabled))
out = Finish.over(out, md_arc(p, c, ar, aw, px, v0, v1, gap, self.disabled))
let ang = gap * 0.5 + (2.0 * PI - gap) * self.slide_pos
out = Finish.over(out, md_knob(p, c, rad, px, ang, max(self.hover, self.focus * 0.5), self.drag * live, self.disabled))
return Finish.over(out, md_focus(length(p - c) - ar - aw * 0.5, px, self.focus * live))
}
// The dial's readout: a small positive liquid-crystal window cut into the
// housing: a dark bezel line, a white glass edge inside it, the grey-green
// glass falling darker toward its foot, and dark digits with no glow.
let md_lcd = fn(p: vec2, size: vec2, o: vec2, px: float) -> vec4 {
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o + vec2(0.0, px)
let hi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y, px)) - o - vec2(0.0, 2.0 * px)
let h = (hi - lo) * 0.5
let c = lo + h
let r = 3.0
let d = md_rbox(p, c, h, r)
let t = clamp((p.y - lo.y) / max(2.0 * h.y, 1.0), 0.0, 1.0)
var col = mix(mix(vec3(0.890, 0.898, 0.882), vec3(0.812, 0.820, 0.800), clamp(t * 2.0, 0.0, 1.0)), vec3(0.706, 0.714, 0.694), clamp(t * 2.0 - 1.0, 0.0, 1.0))
// a weak sheen across the upper left, cut hard on its diagonal
let sheen = step((p.x - lo.x) * 0.35 + (p.y - lo.y), h.y * 0.9)
col = mix(col, vec3(1.0, 1.0, 1.0), sheen * 0.10)
col = mix(col, vec3(1.0, 1.0, 1.0), Finish.band(d, px, 2.0 * px, px) * 0.70)
col = mix(col, vec3(0.110, 0.118, 0.133), Finish.band(d, 0.0, px, px) * 0.75)
let a = Finish.cover(d, px)
var out = md_seat(d, p.y - c.y, h.y, px, 0.16, 1.0)
return Finish.over(out, vec4(col * a, a))
}
mod.widgets.RotaryFlat.text_input.padding = mod.turtle.Inset{left: 5.0 right: 5.0 top: 2.0 bottom: 2.0}
mod.widgets.RotaryFlat.text_input.min_height = 20.0
mod.widgets.RotaryFlat.text_input.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return md_lcd(self.pos * self.rect_size, self.rect_size, self.rect_pos, px)
}
mod.widgets.RotaryFlat.text_input.draw_text.color = #3a3d3f
mod.widgets.RotaryFlat.text_input.draw_text.color_hover = #3a3d3f
mod.widgets.RotaryFlat.text_input.draw_text.color_focus = #3a3d3f
mod.widgets.RotaryFlat.text_input.draw_text.color_empty = #6c6f73
mod.widgets.RotaryFlat.text_input.draw_text.text_style = mod.theme.font_label{font_size: 9.25}
// ---------------------------------------------------------------------------
// TEXT FIELDS. A white well: a dark rim (darker along the top), a shade
// falling in under the top edge, a dark line inside the top. A field clips
// its own text, so its countersink is milled inside its own box, the well
// 2.4 points in, and the focus ring lies inside the rim.
let md_field = fn(p: vec2, size: vec2, o: vec2, px: float, hover: float, focus: float, dis: float, r: float) -> vec4 {
// the countersink is milled inside the box: the well stands 2.4 points in
let ins = 2.4
let lo = vec2(Finish.snap(o.x + ins, px), Finish.snap(o.y + ins, px)) - o + vec2(0.0, px)
let hi = vec2(Finish.snap(o.x + size.x - ins, px), Finish.snap(o.y + size.y - ins, px)) - o - vec2(0.0, 2.0 * px)
let h = (hi - lo) * 0.5
let c = lo + h
let d = md_rbox(p, c, h, r)
let t = clamp((p.y - lo.y) / max(2.0 * h.y, 1.0), 0.0, 1.0)
let well = mix(Finish.lift(vec3(0.969, 0.969, 0.973), 0.6 * hover), vec3(0.933, 0.937, 0.941), dis)
var rim = mix(vec3(0.306, 0.314, 0.329), vec3(0.416, 0.424, 0.439), smoothstep(0.0, 0.6, t))
rim = mix(rim, vec3(0.337, 0.345, 0.361), hover * (1.0 - dis))
rim = mix(rim, vec3(0.306, 0.314, 0.329), focus)
rim = mix(rim, vec3(0.663, 0.671, 0.682), dis)
// the shade falls about four device rows in, as in the reference well
var col = md_well_s(p, c, h, r, px, well, mix(0.32, 0.10, dis), rim, 1.5)
col = mix(col, vec3(0.918, 0.353, 0.067), Finish.band(d, px, 2.0 * px, px) * focus * 0.85)
let a = Finish.cover(d, px)
var out = md_sink(d, p.y - c.y, h.y, ins - 2.0 * px, px, 0.16, mix(1.0, 0.5, dis))
return Finish.over(out, vec4(col * a, a))
}
mod.widgets.TextInputFlat.padding = mod.turtle.Inset{left: 8.0 right: 8.0 top: 4.0 bottom: 4.0}
mod.widgets.TextInputFlat.draw_bg.pixel = fn() {
// The value field inside a slider or a well draws no box of its own.
if self.color.a < 0.01 {
return vec4(0.0, 0.0, 0.0, 0.0)
}
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return md_field(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, self.hover, self.focus, self.disabled, 4.0)
}
mod.widgets.TextInputFlat.draw_text.color_empty = #6c6f73
mod.widgets.TextInputFlat.draw_text.color_disabled = #a0a2a6
mod.widgets.TextInputFlat.draw_cursor.color = #34373b
// NUMBER FIELDS. The white well, and on its right a 14 point cap column,
// split by an engraved line, each half with a small engraved triangle; the
// half under the pointer lifts, the held half is pressed in.
mod.widgets.NumberField.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return md_field(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, self.hover, self.focus, self.disabled, 4.0)
}
mod.widgets.NumberField.pad_left = 8.0
mod.widgets.NumberSpin.width = 14.0
mod.widgets.NumberSpin.draw_text.color = #00000000
mod.widgets.NumberSpin.draw_text.color_disabled = #00000000
let md_tri = fn(p: vec2, c: vec2, hw: float, hh: float, dir: float) -> float {
// a triangle hw wide either side, 2 hh tall, apex up (dir -1) or down (1)
let y = (p.y - c.y) * dir
let e = (abs(p.x - c.x) * 2.0 * hh + hw * (y - hh)) / sqrt(4.0 * hh * hh + hw * hw)
return max(-hh - y, e)
}
mod.widgets.NumberSpin.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * self.rect_size
let o = self.rect_pos
let live = 1.0 - self.disabled
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o + vec2(0.0, px)
let hi = vec2(Finish.snap(o.x + self.rect_size.x, px), Finish.snap(o.y + self.rect_size.y, px)) - o - vec2(0.0, 2.0 * px)
let mid = Finish.snap(o.y + (lo.y + hi.y) * 0.5, px) - o.y
let upper = step(p.y, mid)
let hot = mix(max(0.0, -self.hot), max(0.0, self.hot), upper) * live
let dn = mix(max(0.0, -self.down), max(0.0, self.down), upper) * live
// each half is a cap of its own, square where it meets the well
let hc = vec2((lo.x + hi.x) * 0.5, mix((mid + hi.y) * 0.5, (lo.y + mid) * 0.5, upper))
let hh = vec2((hi.x - lo.x) * 0.5, mix((hi.y - mid) * 0.5, (mid - lo.y) * 0.5, upper))
let cap = md_cap(p, hc, hh, vec4(0.0, 0.0, 0.0, 0.0), px, hot, dn, self.disabled, vec4(0.0, 0.0, 0.0, 0.0))
// the outer corners follow the field's radius
let outer = md_rbox(p, (lo + hi) * 0.5 - vec2(3.0, 0.0), (hi - lo) * 0.5 + vec2(3.0, 0.0), 4.0)
var out = cap * Finish.cover(outer, px)
// the engraved triangles, 6 by 3.5 points, a light line under each
let cx = (lo.x + hi.x) * 0.5
let tu = md_tri(p, vec2(cx, (lo.y + mid) * 0.5 + dn * px * upper), 3.0, 1.75, -1.0)
let td = md_tri(p, vec2(cx, (mid + hi.y) * 0.5 + dn * px * (1.0 - upper)), 3.0, 1.75, 1.0)
let tl = Finish.cover(min(md_tri(p - vec2(0.0, px), vec2(cx, (lo.y + mid) * 0.5), 3.0, 1.75, -1.0), md_tri(p - vec2(0.0, px), vec2(cx, (mid + hi.y) * 0.5), 3.0, 1.75, 1.0)), px) * 0.5 * live
out = Finish.over(out, vec4(tl, tl, tl, tl))
let ti = Finish.cover(min(tu, td), px)
let ink = mix(vec3(0.420, 0.427, 0.443), vec3(0.690, 0.694, 0.706), self.disabled)
return Finish.over(out, vec4(ink * ti, ti))
}
// The drag-number: the white well, its track a pale wash behind dark digits.
mod.widgets.ValueInput.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * self.rect_size
var out = md_field(p, self.rect_size, self.rect_pos, px, max(self.hover, self.drag), self.focus, 0.0, 4.0)
let d = md_rbox(p, self.rect_size * 0.5, self.rect_size * 0.5 - vec2(2.0 * px, 2.0 * px), 3.0)
let wx = 2.0 * px + (self.rect_size.x - 4.0 * px) * max(self.fill, 0.0)
let wash = step(0.0, self.fill) * step(p.x, wx) * Finish.cover(d, px) * 0.10
return Finish.over(out, vec4(0.110 * wash, 0.118 * wash, 0.133 * wash, wash))
}
mod.widgets.ValueInput.draw_text.color = #34373b
mod.widgets.ValueInput.draw_text.text_style = mod.theme.font_label{font_size: 8.5 line_spacing: 1.0}
mod.widgets.ValueInput.arrow_color = #707377
mod.widgets.ValueInput.text_input.draw_bg.color = #00000000
// ---------------------------------------------------------------------------
// DROP-DOWNS. The button stack with a 22 point arrow cell on the right, set
// off by an engraved divider; the arrow is a small engraved triangle. Open,
// the cap is held pressed in.
// `ins` 1: the drop-down stands in the field row, whose box is two points
// taller than a cap for the seat and lip a well keeps inside; its cap takes
// the well's rows (one device pixel in at the top, two at the foot), so it
// stands as tall as the fields and the buttons beside it.
let md_dropdown = fn(pos0: vec2, size0: vec2, o0: vec2, px: float, hover: float, held: float, focus: float, dis: float, ins: float) -> vec4 {
let m = 4.0
let p = pos0 * (size0 + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) - vec2(0.0, px * ins)
let size = size0 - vec2(0.0, 3.0 * px * ins)
let o = o0 + vec2(0.0, px * ins)
let pos = (p + vec2(m, m)) / (size + vec2(2.0 * m, 2.0 * m))
var out = md_button(pos, size, o, px, hover, held, focus, dis, 0.0, 0.0, vec4(5.0, 5.0, 5.0, 5.0), 0.0, m, vec4(0.0, 0.0, 0.0, 0.0), 0.0)
let live = 1.0 - dis
let dx = Finish.snap(o.x + size.x - 22.0, px) - o.x
let in_y = step(size.y * 0.15, p.y) * step(p.y, size.y * 0.85)
let div_d = step(dx - px, p.x) * step(p.x, dx) * in_y * mix(0.75, 0.35, dis)
let div_l = step(dx, p.x) * step(p.x, dx + px) * in_y * 0.8
out = Finish.over(out, vec4(0.416 * div_d, 0.416 * div_d, 0.424 * div_d, div_d))
out = Finish.over(out, vec4(0.886 * div_l, 0.886 * div_l, 0.894 * div_l, div_l))
let dn = held * live * px
let cc = vec2(Finish.snap(o.x + size.x - 11.0, px) - o.x, size.y * 0.5 + dn)
let lit = Finish.cover(md_tri(p - vec2(0.0, px), cc, 3.5, 2.0, 1.0), px) * 0.5 * live
out = Finish.over(out, vec4(lit, lit, lit, lit))
let tri = Finish.cover(md_tri(p, cc, 3.5, 2.0, 1.0), px)
let ink = mix(vec3(0.420, 0.427, 0.443), vec3(0.690, 0.694, 0.706), dis)
return Finish.over(out, vec4(ink * tri, tri))
}
mod.widgets.DropDownFlat.padding = mod.turtle.Inset{left: 8.0 right: 30.0 top: 4.0 bottom: 4.0}
mod.widgets.DropDown.padding = mod.turtle.Inset{left: 8.0 right: 30.0 top: 4.0 bottom: 4.0}
mod.widgets.DropDownFlat.clip_x = false
mod.widgets.DropDownFlat.clip_y = false
mod.widgets.DropDownFlat.draw_bg.vertex = fn() {
let g = md_grow(self.rect_pos, self.rect_size, 4.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.DropDownFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return md_dropdown(self.pos, self.rect_size, self.rect_pos, px, self.hover, max(self.down, self.active), self.focus, self.disabled, 1.0)
}
// The select wears a button of its own, built before this sheet: it is given
// the drop-down's face, its grown quad, and no clip, here by name.
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.MultiSelect.face.clip_x = false
mod.widgets.MultiSelect.face.clip_y = false
mod.widgets.Select.face.draw_bg.vertex = fn() {
let g = md_grow(self.rect_pos, self.rect_size, 4.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.MultiSelect.face.draw_bg.vertex = fn() {
let g = md_grow(self.rect_pos, self.rect_size, 4.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.Select.face.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return md_dropdown(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, 0.0)
}
mod.widgets.MultiSelect.face.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
return md_dropdown(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, 0.0)
}
mod.widgets.Select.face.padding.right = 30.0
mod.widgets.MultiSelect.face.padding.right = 30.0
// The combo box: the white well one types into, with a cap for its arrow
// joined at the right end behind an engraved divider.
mod.widgets.ComboBox.padding = mod.turtle.Inset{left: 8.0 right: 30.0 top: 4.0 bottom: 4.0}
mod.widgets.ComboBox.input.min_height = 0.0
mod.widgets.ComboBox.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * self.rect_size
let size = self.rect_size
let o = self.rect_pos
let live = 1.0 - self.disabled
var out = md_field(p, size, o, px, self.hover, self.focus, self.disabled, 4.0)
let lo = vec2(Finish.snap(o.x + size.x - 22.0, px), Finish.snap(o.y, px)) - o + vec2(0.0, px)
let hi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y, px)) - o - vec2(0.0, 2.0 * px)
let kh = (hi - lo) * 0.5
let kc = lo + kh
let held = self.down * self.hover * live
let cap = md_cap(p, kc, kh, vec4(0.0, 0.0, 0.0, 0.0), px, self.hover * live, held, self.disabled, vec4(0.0, 0.0, 0.0, 0.0))
let outer = md_rbox(p, kc - vec2(3.0, 0.0), kh + vec2(3.0, 0.0), 4.0)
out = Finish.over(out, cap * Finish.cover(outer, px))
let cc = vec2(kc.x, kc.y + held * px)
let lit = Finish.cover(md_tri(p - vec2(0.0, px), cc, 3.5, 2.0, 1.0), px) * 0.5 * live
out = Finish.over(out, vec4(lit, lit, lit, lit))
let tri = Finish.cover(md_tri(p, cc, 3.5, 2.0, 1.0), px)
let ink = mix(vec3(0.420, 0.427, 0.443), vec3(0.690, 0.694, 0.706), self.disabled)
return Finish.over(out, vec4(ink * tri, tri))
}
// ---------------------------------------------------------------------------
// SEGMENTS AND TABS. One cap across the strip; the chosen segment is pressed
// in, with an engraved line at each side where it meets its neighbours.
let md_strip = fn(p: vec2, size: vec2, o: vec2, px: float, m: float) -> vec4 {
let q = p
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o
let hi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y, px)) - o
let c = (lo + hi) * 0.5
let h = (hi - lo) * 0.5
let d = md_rbox(q, c, h, 5.0)
var out = md_seat(d, q.y - c.y, h.y, px, 0.16, 1.0)
return Finish.over(out, md_cap(q, c, h, vec4(5.0, 5.0, 5.0, 5.0), px, 0.0, 0.0, 0.0, vec4(0.0, 0.0, 0.0, 0.0)))
}
// The chosen cell of a strip: pressed in, filling the strip from one device
// pixel under its top outline to one above its foot, square at its sides
// (the joins with its neighbours), no outline of its own; each side is the
// strip's engraved divider pair, a mid grey device column and a pale one.
// `lo` and `hi` are the cell's corners in the quad's own points.
let md_cell = fn(p: vec2, lo: vec2, hi: vec2, px: float) -> vec4 {
let c = (lo + hi) * 0.5
let h = (hi - lo) * 0.5
let d = md_rbox(p, c, h, 0.0)
let t = clamp((p.y - lo.y) / max(2.0 * h.y, 1.0), 0.0, 1.0)
var face = mix(vec3(0.722, 0.725, 0.737), vec3(0.882, 0.886, 0.894), smoothstep(0.0, 1.0, t))
// the shade under the top edge only: the sides are joins, not walls
let top = clamp(Finish.inset(p, c + vec2(0.0, 0.0), h + vec2(4.0, 0.0), 0.0, vec2(0.0, 1.0), 1.2), 0.0, 1.0)
face = mix(face, vec3(0.110, 0.118, 0.133), top * 0.20)
let a = Finish.cover(d, px)
let lx = p.x - lo.x
let rx = hi.x - p.x
let dk = (step(0.0, lx) * step(lx, px) + step(px, rx) * step(rx, 2.0 * px)) * a
let lt = (step(px, lx) * step(lx, 2.0 * px) + step(0.0, rx) * step(rx, px)) * a
face = mix(face, vec3(0.702, 0.706, 0.714), dk)
face = mix(face, vec3(0.859, 0.863, 0.871), lt)
return vec4(face * a, a)
}
mod.widgets.SegmentedControlFlat.segment_height = 22.0
mod.widgets.SegmentedControlFlat.draw_bg.vertex = fn() {
let g = md_grow(self.rect_pos, self.rect_size, 4.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.SegmentedControlFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = 4.0
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
return md_strip(p, self.rect_size, self.rect_pos, px, 0.0)
}
// The pill is the segment's own box, two points in from the strip on every
// side: its quad grows two points up and down so the cell reaches the
// strip's outline. The widget does not say which segment is first or last,
// so the cell keeps the segment's width and its sides stay square.
mod.widgets.SegmentedControlFlat.draw_pill.vertex = fn() {
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(0.0, 2.0), self.rect_size + vec2(0.0, 4.0))
}
mod.widgets.SegmentedControlFlat.draw_pill.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let o = self.rect_pos
let size = self.rect_size
let p = self.pos * (size + vec2(0.0, 4.0)) - vec2(0.0, 2.0)
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y - 2.0, px) + px) - o
let hi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y + 2.0, px) - px) - o
return md_cell(p, lo, hi, px)
}
mod.widgets.SegmentedControlFlat.draw_text.color = #4a4d52
mod.widgets.SegmentedControlFlat.draw_text_selected.color = #25282c
// The tab strip: the same cap; the tab in use is the pressed-in segment,
// a hovered tab lifts a little, the quiet ones are split by engraved lines.
mod.widgets.Tabs.height = 26.0
mod.widgets.Tabs.draw_bg.vertex = fn() {
let g = md_grow(self.rect_pos, self.rect_size, 4.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.Tabs.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = 4.0
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
return md_strip(p, self.rect_size, self.rect_pos, px, 0.0)
}
mod.widgets.Tabs.draw_tab.pixel = fn() {
return vec4(0.0, 0.0, 0.0, 0.0)
}
mod.widgets.Tabs.draw_tab_hover.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * self.rect_size
let h = self.rect_size * 0.5 - vec2(px, 2.0 * px)
let d = md_rbox(p, self.rect_size * 0.5, h, 3.0)
let a = Finish.cover(d, px) * 0.22
return vec4(a, a, a, a)
}
mod.widgets.Tabs.draw_tab_active.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let o = self.rect_pos
let size = self.rect_size
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px) + px) - o
let hi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y, px) - px) - o
return md_cell(self.pos * size, lo, hi, px)
}
mod.widgets.Tabs.draw_sep.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let x = self.pos.x * self.rect_size.x
// the strip's divider pair, the same as at the sides of the chosen cell
let dk = step(x, px)
let lt = step(px, x) * step(x, 2.0 * px)
return vec4(0.702 * dk + 0.859 * lt, 0.706 * dk + 0.863 * lt, 0.714 * dk + 0.871 * lt, dk + lt)
}
mod.widgets.Tabs.draw_mark.color = #4a4d52
mod.widgets.Tabs.draw_mark.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * self.rect_size
let o = self.rect_pos
let c = vec2(Finish.snap(o.x + self.rect_size.x * 0.5, px), Finish.snap(o.y + self.rect_size.y * 0.5, px)) - o
// 7 points across, a 1 point stroke; the plus is the cross turned
let a = mix(0.7853981634, 0.0, self.plus)
let r = mix(3.4, 3.5, self.plus)
let u = vec2(cos(a), sin(a)) * r
let v = vec2(-sin(a), cos(a)) * r
let dd = min(md_seg(p, c - u, c + u), md_seg(p, c - v, c + v)) - 0.5
let dl = min(md_seg(p - vec2(0.0, px), c - u, c + u), md_seg(p - vec2(0.0, px), c - v, c + v)) - 0.5
let l = Finish.cover(dl, px) * 0.5 * self.color.a
let k = Finish.cover(dd, px) * self.color.a
return Finish.over(vec4(l, l, l, l), vec4(self.color.rgb * k, k))
}
mod.widgets.Tabs.draw_text.color = #4a4d52
mod.widgets.Tabs.draw_text_active.color = #25282c
// The strip centres a 14 point line box, which leaves the capitals high in
// a 26 point cap: they are dropped to the cap's middle.
mod.widgets.Tabs.draw_text.text_style.top_drop = 0.22
mod.widgets.Tabs.draw_text_active.text_style.top_drop = 0.22
// ---------------------------------------------------------------------------
// PROGRESS. An 8 point pill channel lighter than the housing, in its seat;
// the value a convex graphite strip with a dark foot line. The unknown value
// is a still 45 degree hatch across the whole channel: a face may not run a
// clock.
mod.widgets.ProgressBarFlat.height = 8.0
mod.widgets.ProgressBarFlat.draw_bg.thickness = uniform(8.0)
mod.widgets.ProgressBarFlat.clip_x = false
mod.widgets.ProgressBarFlat.clip_y = false
mod.widgets.ProgressBarFlat.draw_bg.vertex = fn() {
let g = md_grow(self.rect_pos, self.rect_size, 2.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.ProgressBarFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = 2.0
let size = self.rect_size
let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let o = self.rect_pos
let th = min(self.thickness, size.y)
let top = Finish.snap(o.y + (size.y - th) * 0.5, px) - o.y
let bot = Finish.snap(o.y + (size.y + th) * 0.5, px) - o.y
let x0 = Finish.snap(o.x + size.x * self.start, px) - o.x
let x1 = Finish.snap(o.x + size.x * self.end, px) - o.x
let h = vec2((x1 - x0) * 0.5, (bot - top) * 0.5)
let c = vec2(x0, top) + h
let r = h.y
let d = md_rbox(p, c, h, r)
let t = clamp((p.y - top) / max(bot - top, 1.0), 0.0, 1.0)
let dis = step(4.5, self.intent)
// the channel
var col = mix(vec3(0.910, 0.910, 0.914), vec3(0.933, 0.937, 0.941), dis)
col = mix(col, vec3(0.110, 0.118, 0.133), clamp(Finish.inset(p, c, h, r, vec2(0.0, 0.6), 0.6), 0.0, 1.0) * 0.15)
let rim = mix(vec3(0.557, 0.565, 0.576), vec3(0.690, 0.698, 0.710), smoothstep(0.2, 0.8, t))
col = mix(col, rim, Finish.band(d, 0.0, px, px))
// the value
var v0 = x0
var v1 = x0 + (x1 - x0) * clamp(self.value, 0.0, 1.0)
var hatch = 0.0
var pale = 0.0
if self.value < 0.0 {
v1 = x1
pale = 1.0
hatch = Finish.stripes(p + o, 0.785398, 7.0, 0.5, px)
}
let fh = vec2(max((v1 - v0) * 0.5 - px, 0.0), h.y - px)
let fc = vec2((v0 + v1) * 0.5, c.y)
let fd = md_rbox(p, fc, fh, min(fh.y, fh.x + fh.y))
var ink = mix(vec3(0.655, 0.659, 0.667), vec3(0.267, 0.271, 0.278), smoothstep(0.0, 1.0, t))
if self.intent > 0.5 {
let ic = self.fill_color().rgb
ink = mix(Finish.lift(ic, 0.25), ic * 0.8, t)
}
ink = mix(ink, vec3(0.780, 0.784, 0.792), dis)
ink = mix(ink, mix(vec3(0.886, 0.890, 0.898), vec3(0.753, 0.757, 0.765), hatch), pale)
ink = mix(ink, vec3(0.200, 0.204, 0.212), Finish.band(fd, 0.0, px, px) * smoothstep(0.55, 0.9, t))
ink = mix(ink, vec3(1.0, 1.0, 1.0), Finish.band(fd, 0.0, px, px) * (1.0 - smoothstep(0.1, 0.45, t)) * 0.25)
col = mix(col, ink, Finish.cover(fd, px) * step(0.01, v1 - v0))
let a = Finish.cover(d, px) * self.track
var out = md_seat(d, p.y - c.y, h.y, px, 0.14 * self.track, self.track)
out = Finish.over(out, vec4(col * a, a))
return out * self.opacity
}
// ---------------------------------------------------------------------------
// LEVEL METERS. A 6 point well of cells 4 points long with a point between:
// unlit cells the dark track grey, lit ones flat green, then orange from 70 %
// and red from 90 %, a faint light line along their top; no halo. The hold
// is a dark hairline pair; the over lamp a red lamp, a grey dimple until it
// latches.
mod.widgets.LevelMeter.height = 8.0
mod.widgets.LevelMeterColumn.width = 8.0
mod.widgets.LevelMeter.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * self.rect_size
let vert = self.vertical
let along = mix(self.rect_size.x, self.rect_size.y, vert)
let across = mix(self.rect_size.y, self.rect_size.x, vert)
let a = mix(p.x, self.rect_size.y - p.y, vert)
let b = mix(p.y, p.x, vert)
let q = vec2(a, b)
let th = min(6.0, across - 2.0)
let lamp_room = self.lamp * (th + 4.0)
let bar = max(4.0, along - lamp_room)
let h = vec2(bar * 0.5, th * 0.5)
let c = vec2(h.x, across * 0.5)
let r = 1.5
let d = md_rbox(q, c, h, r)
let pad = 1.0
let span = max(1.0, bar - pad * 2.0)
let share = clamp((a - pad) / span, 0.0, 1.0)
let pitch = 5.0
let cell = fract((a - pad) / pitch)
let in_cell = step(cell, 4.0 / pitch)
let lit = step(a, pad + span * self.level) * step(pad, a) * in_cell
let ramp = mix(mix(vec3(0.247, 0.749, 0.353), vec3(0.918, 0.353, 0.067), step(0.7, share)), vec3(0.949, 0.361, 0.345), step(0.9, share))
let well = vec3(0.659, 0.663, 0.678)
var fill = mix(well, ramp, lit)
let cell_row = in_cell * step(pad, a) * step(a, pad + span)
fill = mix(fill, well * 0.93, (1.0 - cell_row) * (1.0 - lit))
let bt = b - (c.y - h.y)
fill = mix(fill, vec3(1.0, 1.0, 1.0), lit * step(px, bt) * step(bt, 2.0 * px) * 0.25)
var col = md_well(q, c, h, r, px, fill, 0.25, vec3(0.416, 0.424, 0.439))
if self.hold > 0.002 {
let at = pad + span * self.hold
let hm = step(abs(a - at), px) * step(abs(b - c.y), h.y)
col = mix(col, vec3(0.200, 0.204, 0.212), hm)
}
let ca = Finish.cover(d, px)
var out = md_seat(d, b - c.y, h.y, px, 0.14, 1.0)
out = Finish.over(out, vec4(col * ca, ca))
if self.lamp > 0.5 {
let la = bar + 2.0 + th * 0.5 + 1.0
let lc = vec2(mix(la, across * 0.5, vert), mix(across * 0.5, self.rect_size.y - la, vert))
out = Finish.over(out, md_led(p, lc, 2.5, px, vec3(0.910, 0.098, 0.059), step(0.5, self.over), 0.28))
}
return out * self.opacity
}
// ---------------------------------------------------------------------------
// SCROLL BARS. A 10 point well the length of the view, and an 8 point
// riding pill thumb in it with three engraved grip ridges across its middle.
mod.widgets.ScrollBar.bar_size = 10.0
mod.widgets.ScrollBar.bar_side_margin = 0.0
mod.widgets.ScrollBar.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * self.rect_size
let vert = self.is_vertical
let a = mix(p.x, p.y, vert)
let b = mix(p.y, p.x, vert)
let along = mix(self.rect_size.x, self.rect_size.y, vert)
let across = mix(self.rect_size.y, self.rect_size.x, vert)
let q = vec2(a, b)
let sh = vec2(along * 0.5, across * 0.5 - px * 0.5)
let sc = sh
let sd = md_rbox(q, sc, sh, sh.y)
var col = vec3(0.812, 0.816, 0.827)
col = mix(col, vec3(0.110, 0.118, 0.133), clamp(Finish.inset(q, sc, sh, sh.y, vec2(0.0, 0.5), 0.6), 0.0, 1.0) * 0.18)
col = mix(col, vec3(0.557, 0.565, 0.576), Finish.band(sd, 0.0, px, px) * 0.6)
let sa = Finish.cover(sd, px)
var out = vec4(col * sa, sa)
// the thumb
let t0 = along * self.norm_scroll
let tl = along * self.norm_handle
let th = vec2(max(tl * 0.5 - 1.0, 4.0), 4.0)
let tc = vec2(t0 + tl * 0.5, sc.y)
let td = md_rbox(q, tc, th, 4.0)
let con = Finish.drop(q, tc, th, 4.0, vec2(0.0, 0.5), 0.35) * 0.30
out = Finish.over(out, vec4(0.110 * con, 0.118 * con, 0.133 * con, con))
let tt = clamp((b - (tc.y - th.y)) / 8.0, 0.0, 1.0)
var face = mix(vec3(0.957, 0.961, 0.965), vec3(0.839, 0.843, 0.855), tt)
face = Finish.lift(face, 0.3 * self.hover - 0.06 * self.drag)
// grip: three ridges across the middle, on a two point pitch
let fi = clamp(floor((a - tc.x) / 2.0 + 0.5), -1.0, 1.0)
let rx = floor((tc.x + fi * 2.0) / px) * px
let inb = step(abs(b - tc.y), 2.5)
face = mix(face, vec3(0.776, 0.780, 0.792), step(rx - px, a) * step(a, rx) * inb)
face = mix(face, vec3(1.0, 1.0, 1.0), step(rx, a) * step(a, rx + px) * inb * 0.6)
face = mix(face, vec3(1.0, 1.0, 1.0), Finish.band(td, px, 2.0 * px, px) * (1.0 - tt) * 0.8)
let olc = mix(vec3(0.557, 0.565, 0.576), vec3(0.212, 0.220, 0.231), smoothstep(0.15, 0.85, tt))
face = mix(face, olc, Finish.band(td, 0.0, px, px))
let ta = Finish.cover(td, px)
out = Finish.over(out, vec4(face * ta, ta))
return out * self.opacity
}
// ---------------------------------------------------------------------------
// VIEWS AND PANELS. A raised plate: a two-tone hairline, the white line
// inside its top, a face a shade above the housing with the housing's own
// texture, and a tight contact shadow under it. The sunken panel is a flush
// well: its seat and lip, a shade under its top edge, a quiet rim.
let md_plate = fn(p: vec2, size: vec2, o: vec2, px: float, r: float, sunken: float) -> vec4 {
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o
let hi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y, px)) - o
let h = (hi - lo) * 0.5
let c = lo + h
let d = md_rbox(p, c, h, r)
let t = clamp((p.y - lo.y) / max(2.0 * h.y, 1.0), 0.0, 1.0)
var out = vec4(0.0, 0.0, 0.0, 0.0)
if sunken > 0.5 {
out = md_seat(d, p.y - c.y, h.y, px, 0.16, 1.0)
var face = vec3(0.827, 0.831, 0.839) + g3(md_tex(p + o, px))
face = mix(face, vec3(0.110, 0.118, 0.133), clamp(Finish.inset(p, c, h, r, vec2(0.0, 1.2), 1.6), 0.0, 1.0) * 0.20)
face = mix(face, vec3(0.110, 0.118, 0.133), Finish.band(d, px, 2.0 * px, px) * (1.0 - smoothstep(0.0, 0.3, t)) * 0.25)
face = mix(face, mix(vec3(0.494, 0.502, 0.518), vec3(0.604, 0.612, 0.627), t), Finish.band(d, 0.0, px, px))
let a = Finish.cover(d, px)
return Finish.over(out, vec4(face * a, a))
}
let tight = Finish.drop(p, c, h, r, vec2(0.0, 1.0), 0.5) * 0.22
let soft = Finish.drop(p, c, h, r, vec2(0.0, 1.6), 1.2) * 0.09
let sa = tight + soft * (1.0 - tight)
out = vec4(0.110 * sa, 0.118 * sa, 0.133 * sa, sa)
var face = mix(vec3(0.894, 0.898, 0.906), vec3(0.859, 0.863, 0.871), t) + g3(md_tex(p + o, px))
let up = 1.0 - smoothstep(-0.5, 0.5, p.y - (lo.y + r))
face = mix(face, vec3(1.0, 1.0, 1.0), Finish.band(d, px, 2.0 * px, px) * up * 0.7)
face = mix(face, mix(vec3(0.639, 0.647, 0.659), vec3(0.490, 0.502, 0.518), smoothstep(0.1, 0.9, t)), Finish.band(d, 0.0, px, px))
let a = Finish.cover(d, px)
return Finish.over(out, vec4(face * a, a))
}
mod.widgets.PanelView.clip_x = false
mod.widgets.PanelView.clip_y = false
mod.widgets.PanelView.draw_bg.vertex = fn() {
let g = md_grow(self.rect_pos, self.rect_size, 4.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.PanelView.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = 4.0
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
return md_plate(p, self.rect_size, self.rect_pos, px, 6.0, self.sunken)
}
// The card is the raised plate too; its palette and shadow numbers from
// Rust are not read. It lifts a little under the pointer.
mod.widgets.Card.radius = 6.0
mod.widgets.Card.draw_bg.vertex = fn() {
let g = md_grow(self.rect_pos, self.rect_size, 4.0)
return self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.Card.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = 4.0
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
var out = md_plate(p, self.rect_size, self.rect_pos, px, 6.0, 0.0)
let d = md_rbox(p, self.rect_size * 0.5, self.rect_size * 0.5 - vec2(px, px), 6.0)
let l = Finish.cover(d, px) * (0.12 * self.hover - 0.04 * self.down)
return Finish.over(out, vec4(max(l, 0.0), max(l, 0.0), max(l, 0.0), abs(l)))
}
// The plain rounded view keeps the colours its caller gives it; the edge is
// a snapped hairline at the true radius.
mod.widgets.RoundedView.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * self.rect_size
var fill = self.color
if self.color_2.x > -0.5 {
fill = mix(self.color, self.color_2, mix(self.pos.y, self.pos.x, self.gradient_fill_horizontal))
}
let lo = vec2(self.border_inset.x, self.border_inset.y)
let hi = self.rect_size - vec2(self.border_inset.z, self.border_inset.w)
let h = (hi - lo) * 0.5
let c = lo + h
let d = md_rbox(p, c, h, max(self.border_radius, 1.0))
var col = fill
if self.border_size > 0.0 {
col = mix(col, self.border_color, Finish.band(d, 0.0, max(self.border_size, px), px) * self.border_color.a)
}
let a = Finish.cover(d, px) * col.a
return vec4(col.rgb * a, a)
}
// ---------------------------------------------------------------------------
// LISTS. Rows of 22 points on the housing, split by a faint hairline; a
// hovered row darkens a shade; the chosen row is pressed into the housing,
// a grey strip with a shade under its top edge, ending short of the scroll
// bar.
mod.widgets.ListItemOne.height = 22.0
mod.widgets.ListItem.padding = mod.turtle.Inset{left: 10.0 right: 8.0 top: 0.0 bottom: 0.0}
mod.widgets.ListItem.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * self.rect_size
let o = self.rect_pos
let live = 1.0 - self.disabled
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o
let hi = vec2(Finish.snap(o.x + self.rect_size.x, px), Finish.snap(o.y + self.rect_size.y, px)) - o
let lay = (self.hover * 0.035 + self.down * 0.05) * live
var out = vec4(0.110 * lay, 0.118 * lay, 0.133 * lay, lay)
if self.selected > 0.5 {
let h = vec2((hi.x - lo.x) * 0.5 - 6.0, (hi.y - lo.y) * 0.5 - px)
let c = vec2(lo.x + h.x + 1.0, (lo.y + hi.y) * 0.5)
let d = md_rbox(p, c, h, 3.0)
let t = clamp((p.y - (c.y - h.y)) / (2.0 * h.y), 0.0, 1.0)
var col = mix(vec3(0.800, 0.804, 0.812), vec3(0.753, 0.757, 0.765), t)
col = mix(col, vec3(0.110, 0.118, 0.133), clamp(Finish.inset(p, c, h, 3.0, vec2(0.0, 1.0), 1.0), 0.0, 1.0) * 0.18)
col = mix(col, mix(vec3(0.439, 0.447, 0.463), vec3(0.557, 0.565, 0.576), t), Finish.band(d, 0.0, px, px) * 0.8)
let a = Finish.cover(d, px)
out = Finish.over(out, md_seat(d, p.y - c.y, h.y, px, 0.0, 0.9))
out = Finish.over(out, vec4(col * a, a))
}
let yb = hi.y
let dk = step(yb - px, p.y) * step(p.y, yb) * 0.07 * (1.0 - self.selected)
out = Finish.over(out, vec4(0.110 * dk, 0.118 * dk, 0.133 * dk, dk))
return out
}
mod.widgets.ListItem.draw_text.color = #34373b
// The open list of a drop-down: a raised plate with a soft drop shadow; the
// row under the pointer takes the grey pair, the chosen row an engraved
// check.
mod.widgets.PopupMenuFlat.clip_x = false
mod.widgets.PopupMenuFlat.clip_y = false
mod.widgets.PopupMenuFlat.draw_bg.vertex = fn() {
let g = md_grow(self.rect_pos, self.rect_size, 6.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.PopupMenuFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = 6.0
let size = self.rect_size
let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let o = self.rect_pos
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o
let hi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y, px)) - o
let h = (hi - lo) * 0.5
let c = lo + h
let d = md_rbox(p, c, h, 5.0)
let sh = Finish.drop(p, c, h, 5.0, vec2(0.0, 2.0), 2.0) * 0.16
var out = vec4(0.110 * sh, 0.118 * sh, 0.133 * sh, sh)
var face = vec3(0.890, 0.894, 0.902) + g3(md_tex(p + o, px))
face = mix(face, vec3(1.0, 1.0, 1.0), Finish.band(d, px, 2.0 * px, px) * (1.0 - smoothstep(-0.5, 0.5, p.y - (lo.y + 5.0))) * 0.7)
face = mix(face, vec3(0.416, 0.424, 0.439), Finish.band(d, 0.0, px, px))
let a = Finish.cover(d, px)
return Finish.over(out, vec4(face * a, a))
}
mod.widgets.PopupMenuItem.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * self.rect_size
let c = self.rect_size * 0.5
let h = c - vec2(2.0, 0.5)
let d = md_rbox(p, c, h, 3.0)
let live = 1.0 - self.disabled
let t = clamp(self.pos.y, 0.0, 1.0)
let row = mix(vec3(0.800, 0.804, 0.812), vec3(0.753, 0.757, 0.765), t)
let wa = Finish.cover(d, px) * self.hover * live
var out = vec4(row * wa, wa)
let m0 = vec2(6.0, c.y)
let m1 = vec2(8.5, c.y + 2.5)
let m2 = vec2(12.5, c.y - 3.0)
let lit = Finish.cover(min(md_seg(p - vec2(0.0, px), m0, m1), md_seg(p - vec2(0.0, px), m1, m2)) - 0.75, px) * self.active * 0.5
out = Finish.over(out, vec4(lit, lit, lit, lit))
let mk = Finish.cover(min(md_seg(p, m0, m1), md_seg(p, m1, m2)) - 0.75, px) * self.active
return Finish.over(out, vec4(0.204 * mk, 0.216 * mk, 0.231 * mk, mk))
}
// The dock's tabs: the bar is the housing with an engraved line at its foot;
// the tab in use is a cap rounded at the top and open at the foot, where it
// joins the panel below; the others lift a little under the pointer.
mod.widgets.Tab.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 4.0 bottom: 4.0}
mod.widgets.Tab.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * self.rect_size
let o = self.rect_pos
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y + 2.0, px)) - o
let hi = vec2(Finish.snap(o.x + self.rect_size.x, px), Finish.snap(o.y + self.rect_size.y, px)) - o + vec2(0.0, 6.0)
let h = (hi - lo) * 0.5
let c = lo + h
let d = md_rbox(p, c, h, 5.0)
let t = clamp((p.y - lo.y) / max(self.rect_size.y - 2.0, 1.0), 0.0, 1.0)
var face = mix(vec3(0.961, 0.965, 0.969), vec3(0.886, 0.890, 0.898), t)
face = mix(face, vec3(1.0, 1.0, 1.0), Finish.band(d, px, 2.0 * px, px) * (1.0 - smoothstep(-0.5, 0.5, p.y - (lo.y + 5.0))) * 0.85)
face = mix(face, mix(vec3(0.557, 0.565, 0.576), vec3(0.416, 0.424, 0.439), t), Finish.band(d, 0.0, px, px))
let a = Finish.cover(d, px) * self.active
let lift = Finish.cover(d, px) * 0.18 * self.hover * (1.0 - self.active)
return Finish.over(vec4(lift, lift, lift, lift), vec4(face * a, a))
}
mod.widgets.TabBar.draw_fill.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * self.rect_size
let yb = Finish.snap(self.rect_pos.y + self.rect_size.y, px) - self.rect_pos.y
let dk = step(yb - 2.0 * px, p.y) * step(p.y, yb - px) * 0.30
let lt = step(yb - px, p.y) * step(p.y, yb) * 0.6
var out = vec4(0.110 * dk, 0.118 * dk, 0.133 * dk, dk)
return Finish.over(out, vec4(lt, lt, lt, lt))
}
// ---------------------------------------------------------------------------
// CHIPS AND BADGES. A chip is a small seated cap (pressed in while chosen);
// a badge is a small raised cap with semibold digits.
mod.widgets.ChipFlat.size = mod.widgets.ChipSize.Small
mod.widgets.Chip.size = mod.widgets.ChipSize.Small
mod.widgets.ChipFlat.draw_bg.vertex = fn() {
let g = md_grow(self.rect_pos, self.rect_size, 4.0)
self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw)
}
mod.widgets.ChipFlat.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let m = 4.0
let ch = min(22.0, self.rect_size.y)
let dy = (self.rect_size.y - ch) * 0.5
let sz = vec2(self.rect_size.x, ch)
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let pp = (p - vec2(0.0, dy) + vec2(m, m)) / (sz + vec2(2.0 * m, 2.0 * m))
// A filled chip (light ink) is a cap dyed its role's colour; a tonal one
// the grey cap faintly dyed; an outlined one the engraved frame; a ghost
// bare until touched.
let light_ink = step(0.6, Finish.lum(self.ink.rgb))
let has_fill = step(0.05, self.color.a)
let kind = mix(mix(6.0, 5.0, step(0.01, self.border_size)), 0.0, has_fill)
let tint = vec4(self.color.rgb, has_fill * mix(0.35, 1.0, light_ink))
return md_button(pp, sz, self.rect_pos + vec2(0.0, dy), px, self.hover, self.down, self.keyed, 0.0, 0.0, 0.0, vec4(5.0, 5.0, 5.0, 5.0), kind, m, tint, 0.0)
}
mod.widgets.BadgePalette.neutral = #55585c
mod.widgets.BadgePalette.on_neutral = #f4f5f6
mod.widgets.BadgeFlat.draw_text.text_style = mod.theme.font_bold{font_size: 6.5}
mod.widgets.Badge.draw_text.text_style = mod.theme.font_bold{font_size: 6.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(16.0, self.rect_size.y - 2.0)
let y0 = Finish.snap(o.y + (self.rect_size.y - bh) * 0.5, px) - o.y
let lo = vec2(Finish.snap(o.x + 1.0, px) - o.x, y0)
let hi = vec2(Finish.snap(o.x + self.rect_size.x - 1.0, px) - o.x, Finish.snap(o.y + y0 + bh, px) - o.y)
let h = (hi - lo) * 0.5
let c = lo + h
let r = mix(4.0, h.y, step(self.rect_size.x, self.rect_size.y + 0.5) * step(self.rect_size.y, 12.5))
let d = md_rbox(p, c, h, r)
var out = md_seat(d, p.y - c.y, h.y, px, 0.16, 1.0)
let dyed = step(0.06, length(self.color.rgb - vec3(0.886, 0.890, 0.898)))
out = Finish.over(out, md_cap(p, c, h, vec4(r, r, r, r), px, 0.0, 0.0, 0.0, vec4(self.color.rgb, dyed)) * self.color.a)
return out
}
// The spinner keeps its stock turning face (it runs on the pass clock, which
// a face may not read), in graphite with a two point stroke on a visible
// track ring, so a still frame reads as a working indicator.
mod.widgets.SpinnerFlat.draw_bg.color = #55575b
mod.widgets.SpinnerFlat.draw_bg.stroke_width = 3.0
mod.widgets.SpinnerFlat.draw_bg.track_alpha = 0.22
// Rules between groups: engraved, one dark device row over one light one.
mod.widgets.Hr.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * self.rect_size
let y = Finish.snap(self.rect_pos.y + self.rect_size.y * 0.5, px) - self.rect_pos.y
let dk = step(y - px, p.y) * (1.0 - step(y, p.y)) * 0.14
let lt = step(y, p.y) * (1.0 - step(y + px, p.y)) * 0.6
return Finish.over(vec4(0.110 * dk, 0.118 * dk, 0.133 * dk, dk), vec4(lt, lt, lt, lt))
}
mod.widgets.Vr.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * self.rect_size
let x = Finish.snap(self.rect_pos.x + self.rect_size.x * 0.5, px) - self.rect_pos.x
let dk = step(x - px, p.x) * (1.0 - step(x, p.x)) * 0.14
let lt = step(x, p.x) * (1.0 - step(x + px, p.x)) * 0.6
return Finish.over(vec4(0.110 * dk, 0.118 * dk, 0.133 * dk, dk), vec4(lt, lt, lt, lt))
}
// ---------------------------------------------------------------------------
// ENGRAVED WORDS. A word on a cap is cut into it: the ink with a white line
// one device pixel under every glyph, read from the glyph one device pixel
// above, so it costs one more sample and nothing else.
mod.widgets.ButtonFlat.draw_text.pixel = fn() {
let main = self.sample_text_pixel()
if self.texture_index > 1.5 && self.texture_index < 2.5 {
let dxt = length(dFdx(self.t))
let dyt = length(dFdy(self.t))
let tex_size = self.msdf_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let ps = clamp(self.t.xy - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let c = self.get_color()
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) * 0.55 * c.a
return main + vec4(s, s, s, s) * (1.0 - main.a)
}
return main
}
mod.widgets.DropDownFlat.draw_text.pixel = fn() {
let main = self.sample_text_pixel()
if self.texture_index > 1.5 && self.texture_index < 2.5 {
let dxt = length(dFdx(self.t))
let dyt = length(dFdy(self.t))
let tex_size = self.msdf_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let ps = clamp(self.t.xy - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let c = self.get_color()
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) * 0.55 * c.a
return main + vec4(s, s, s, s) * (1.0 - main.a)
}
return main
}
mod.widgets.RadioButtonTabFlat.draw_text.pixel = fn() {
let main = self.sample_text_pixel()
if self.texture_index > 1.5 && self.texture_index < 2.5 {
let dxt = length(dFdx(self.t))
let dyt = length(dFdy(self.t))
let tex_size = self.msdf_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let ps = clamp(self.t.xy - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let c = self.get_color()
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) * 0.55 * c.a
return main + vec4(s, s, s, s) * (1.0 - main.a)
}
return main
}
mod.widgets.SegmentedControlFlat.draw_text.pixel = fn() {
let main = self.sample_text_pixel()
if self.texture_index > 1.5 && self.texture_index < 2.5 {
let dxt = length(dFdx(self.t))
let dyt = length(dFdy(self.t))
let tex_size = self.msdf_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let ps = clamp(self.t.xy - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let c = self.get_color()
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) * 0.55 * c.a
return main + vec4(s, s, s, s) * (1.0 - main.a)
}
return main
}
mod.widgets.Tabs.draw_text.pixel = fn() {
let main = self.sample_text_pixel()
if self.texture_index > 1.5 && self.texture_index < 2.5 {
let dxt = length(dFdx(self.t))
let dyt = length(dFdy(self.t))
let tex_size = self.msdf_texture.size()
let scale = (dxt + dyt) * tex_size.x * 0.5
let half_texel = vec2(0.5 / tex_size.x, 0.5 / tex_size.y)
let ps = clamp(self.t.xy - dFdy(self.t.xy), self.t_min + half_texel, self.t_max - half_texel)
let c = self.get_color()
let s = self.msdf(scale, ps, vec4(0.0, 0.0, 0.0, 1.0)) * 0.55 * c.a
return main + vec4(s, s, s, s) * (1.0 - main.a)
}
return main
}
// ---------------------------------------------------------------------------
// STATES. Every held state is written as a plain value that cuts in at once:
// the library writes some as snap() values, which a state held as a default
// (the states page) never applies. Live, the cut is the same as the snap.
mod.widgets.ButtonFlat.animator.hover.on.from = {all: Snap}
mod.widgets.ButtonFlat.animator.hover.on.apply = {draw_bg: {down: 0.0 hover: 1.0} draw_text: {down: 0.0 hover: 1.0}}
mod.widgets.ButtonFlat.animator.hover.down.from = {all: Snap}
mod.widgets.ButtonFlat.animator.hover.down.apply = {draw_bg: {down: 1.0 hover: 1.0} draw_text: {down: 1.0 hover: 1.0}}
mod.widgets.CheckBoxFlat.animator.hover.on.from = {all: Snap}
mod.widgets.CheckBoxFlat.animator.hover.on.apply = {draw_bg: {down: 0.0 hover: 1.0} draw_text: {down: 0.0 hover: 1.0}}
mod.widgets.CheckBoxFlat.animator.hover.down.from = {all: Snap}
mod.widgets.CheckBoxFlat.animator.hover.down.apply = {draw_bg: {down: 1.0 hover: 1.0} draw_text: {down: 1.0 hover: 1.0}}
mod.widgets.ToggleFlat.animator.hover.on.from = {all: Snap}
mod.widgets.ToggleFlat.animator.hover.on.apply = {draw_bg: {down: 0.0 hover: 1.0} draw_text: {down: 0.0 hover: 1.0}}
mod.widgets.ToggleFlat.animator.hover.down.from = {all: Snap}
mod.widgets.ToggleFlat.animator.hover.down.apply = {draw_bg: {down: 1.0 hover: 1.0} draw_text: {down: 1.0 hover: 1.0}}
mod.widgets.RadioButtonFlat.animator.hover.on.from = {all: Snap}
mod.widgets.RadioButtonFlat.animator.hover.on.apply = {draw_bg: {down: 0.0 hover: 1.0} draw_text: {down: 0.0 hover: 1.0}}
mod.widgets.RadioButtonFlat.animator.hover.down.from = {all: Snap}
mod.widgets.RadioButtonFlat.animator.hover.down.apply = {draw_bg: {down: 1.0 hover: 1.0} draw_text: {down: 1.0 hover: 1.0}}
mod.widgets.TextInputFlat.animator.hover.on.from = {all: Snap}
mod.widgets.TextInputFlat.animator.hover.on.apply = {draw_bg: {down: 0.0 hover: 1.0} draw_text: {down: 0.0 hover: 1.0}}
mod.widgets.TextInputFlat.animator.hover.down.from = {all: Snap}
mod.widgets.TextInputFlat.animator.hover.down.apply = {draw_bg: {down: 1.0 hover: 1.0} draw_text: {down: 1.0 hover: 1.0}}
mod.widgets.DropDownFlat.animator.hover.on.from = {all: Snap}
mod.widgets.DropDownFlat.animator.hover.on.apply = {draw_bg: {down: 0.0 hover: 1.0} draw_text: {down: 0.0 hover: 1.0}}
mod.widgets.DropDownFlat.animator.hover.down.from = {all: Snap}
mod.widgets.DropDownFlat.animator.hover.down.apply = {draw_bg: {down: 1.0 hover: 1.0} draw_text: {down: 1.0 hover: 1.0}}
mod.widgets.SliderFlat.animator.hover.on.from = {all: Snap}
mod.widgets.SliderFlat.animator.hover.on.apply = {draw_bg: {hover: 1.0} draw_text: {hover: 1.0}}
mod.widgets.SliderFlat.animator.drag.on.from = {all: Snap}
mod.widgets.SliderFlat.animator.drag.on.apply = {draw_bg: {drag: 1.0} draw_text: {drag: 1.0}}
// ---------------------------------------------------------------------------
// THE HOUSING under everything: the window's own ground. The base grey, a
// long fall of 0.02 from top to bottom, and the pin-stripe under the grain.
mod.widgets.Window.show_bg = true
mod.widgets.Window.draw_bg.pixel = fn() {
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let p = self.pos * self.rect_size
let t = clamp(self.pos.y, 0.0, 1.0)
let col = vec3(0.855, 0.859, 0.867) + g3(0.010 - 0.020 * t + md_tex(p + self.rect_pos, px))
return vec4(col, 1.0)
}
// ---------------------------------------------------------------- switches and meter
// THE SWITCHES are the sheet's own toggle, in proportion with the
// labels and the controls beside them: the rocker and the slide switch
// take the toggle's face, its size and shape, its quad and its label
// gap (the library's own faces for them are flat boxes sized for
// another face). The rocker turns on a click; the slide switch follows a
// drag, as the toggle does.
mod.widgets.ToggleRocker.label_walk.margin = mod.turtle.Inset{left: 46.0 right: 3.0 top: 0.0 bottom: 0.0}
mod.widgets.ToggleSlide.label_walk.margin = mod.turtle.Inset{left: 46.0 right: 3.0 top: 0.0 bottom: 0.0}
mod.widgets.ToggleRocker.draw_bg.size = 14.0
mod.widgets.ToggleSlide.draw_bg.size = 14.0
mod.widgets.ToggleRocker.min_height = 24.0
mod.widgets.ToggleSlide.min_height = 24.0
mod.widgets.ToggleRocker.draw_bg.pill_aspect = 2.7143
mod.widgets.ToggleSlide.draw_bg.pill_aspect = 2.7143
mod.widgets.ToggleRocker.draw_bg.knob_inset = 7.0
mod.widgets.ToggleSlide.draw_bg.knob_inset = 7.0
mod.widgets.ToggleRocker.draw_bg.pixel = mod.widgets.ToggleFlat.draw_bg.pixel
mod.widgets.ToggleSlide.draw_bg.pixel = mod.widgets.ToggleFlat.draw_bg.pixel
// THE METER sits in the same window as the readouts and the screen: the
// library's display housing drawn round its face -- the bezel ring, a shade
// of whatever ground it is set in with a device pixel of dark at its edge;
// the face with the surround's shadow falling across it from the top and a
// device pixel of black where the glass meets the bezel; the glass's quiet
// diagonal band and the light line inside its top edge; the lip of light on
// the ground under it -- with the same corner, so the meter and the
// readouts read as one family. The scale, the red zone, the needle and its
// pivot are the library's own.
mod.widgets.NeedleMeter.draw_bg.border_radius = uniform(theme.corner_radius * 0.5)
mod.widgets.NeedleMeter.draw_bg.color_bezel = uniform(theme.color_d_05)
mod.widgets.NeedleMeter.draw_bg.color_lip = uniform(theme.color_u_15)
mod.widgets.NeedleMeter.draw_bg.pixel = fn() {
let p = self.pos * self.rect_size
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let lo = vec2(Finish.snap(self.rect_pos.x, px), Finish.snap(self.rect_pos.y, px)) - self.rect_pos
let hi = vec2(Finish.snap(self.rect_pos.x + self.rect_size.x, px), Finish.snap(self.rect_pos.y + self.rect_size.y, px) - px) - self.rect_pos
let c = (lo + hi) * 0.5
let ho = (hi - lo) * 0.5
let r_out = min(self.border_radius, min(ho.x, ho.y))
let outer = Material.sd_box(p, c, ho, r_out)
let b = Finish.snap(2.0, px)
let hf = max(ho - vec2(b, b), vec2(0.5, 0.5))
let rf = max(r_out - b, 0.0)
let face_d = Material.sd_box(p, c, hf, rf)
let pv = self.pivot()
let rr = self.scale_radius()
let q = p - pv
let r = length(q)
let ang = atan2(q.x, 0.0 - q.y)
let sweep = self.sweep * 0.017453293
let t = ang / sweep + 0.5
let on_scale = step(-0.0001, t) * step(t, 1.0001)
var face = self.color.rgb
// The scale: a hairline arc and a tick at every division.
let arc = Finish.cover(abs(r - rr) - px * 0.5, px) * on_scale
let n = max(floor(self.majors + 0.5), 1.0) * max(floor(self.minors + 0.5), 1.0)
let k = floor(clamp(t, 0.0, 1.0) * n + 0.5)
let ak = (k / n - 0.5) * sweep
let perp = abs(q.x * cos(ak) + q.y * sin(ak))
let mn = max(floor(self.minors + 0.5), 1.0)
let major = 1.0 - step(0.5, k - mn * floor(k / mn + 0.0001))
let len = mix(self.tick_minor, self.tick_major, major)
let radial = max(rr - len - r, r - rr)
let tick = Finish.cover(perp - px * mix(0.5, 1.0, major), px) * Finish.cover(radial, px) * step(0.0, 0.0 - q.y * cos(ak) + q.x * sin(ak))
face = mix(face, self.color_scale.rgb, max(arc, tick))
if self.zone < 0.999 {
let zone_t = Finish.cover((self.zone - t) * sweep * r, px) * Finish.cover((t - 1.0) * sweep * r, px)
let zone_r = Finish.cover(max(rr + px - r, r - rr - 2.5), px)
face = mix(face, self.color_zone.rgb, zone_t * zone_r)
}
// The needle, from under the pivot cover to just past the scale.
let na = self.needle_angle(self.value)
let nd = vec2(sin(na), 0.0 - cos(na))
let along = dot(q, nd)
let across = abs(q.x * nd.y - q.y * nd.x)
let needle = Finish.cover(across - px * 0.5, px) * Finish.cover(along - (rr + 3.0), px) * Finish.cover(0.0 - along - max(self.pivot_size - px, 0.0), px)
face = mix(face, self.color_needle.rgb, needle)
let pd = r - self.pivot_size
face = mix(face, mix(self.color_needle.rgb, self.color.rgb, 0.35), Finish.cover(pd, px))
face = mix(face, face * 0.55, Finish.band(pd, 0.0, px, px))
// The housing: the surround's shadow across the face from the top, the
// black device pixel at the glass's edge, and the glass over it all.
let insh = Finish.inset(p, c, hf, rf, vec2(0.0, 2.0), 1.8)
face = face * (1.0 - 0.55 * insh)
face = mix(face, face * 0.4, Finish.band(face_d, 0.0, px, px))
let u = (p - (c - hf)) / max(hf * 2.0, vec2(1.0, 1.0))
let diag = u.x + u.y * 0.6
let band = smoothstep(0.0, 0.12, diag) * (1.0 - smoothstep(0.28, 0.5, diag))
let top = Finish.cover(abs(p.y - (c.y - hf.y) - px * 1.5) - px * 0.5, px) * 0.6
let ga = clamp(0.03 * (band + top), 0.0, 1.0) * Finish.cover(face_d + px, px)
face = face * (1.0 - ga) + vec3(ga, ga, ga)
let bz = self.color_bezel
var col = vec4(bz.rgb * bz.a, bz.a)
col = Finish.over(col, vec4(0.0, 0.0, 0.0, 0.45 * Finish.band(outer, 0.0, px, px)))
col = mix(col, vec4(face, 1.0), Finish.cover(face_d, px))
col = col * Finish.cover(outer, px)
let lip = Finish.ring_out(outer, 0.0, px, px) * step(hi.y, p.y)
let la = self.color_lip.a * lip
return Finish.over(vec4(self.color_lip.rgb * la, la), col) * self.color.a * self.opacity
}
// ---------------------------------------------------------------- lamps
// LAMPS: an LED, as the hardware in his pictures has them. The emitter is
// the light: at full current its middle burns toward white and its rim is
// its most saturated colour; at lower current the middle is no longer
// white, the colour deeper, the glow smaller; out, it is dark tinted glass,
// its colour barely there. Round it a bezel: a raised ring lit from the
// upper left, or a socket cut into the surface, its upper wall in shadow
// and a catch light on its lower rim. Over the lens one crisp highlight of
// a dome. Its light is added, never painted: a tight hot halo and a short
// bloom in its own hue, screened over whatever is there and gone in about
// two radii; on a pale ground, where light can barely brighten, its hue is
// also laid round it as a tint that keeps the ground's luminance (the
// library's halo rule: at most 0.45 one device pixel out, halving every
// 0.4 of its reach, gone at the reach), so it never darkens or greys its
// ground. A bar is the same light drawn out into a light pipe.
let md_led_lamp_add = fn(res: vec4, light: vec3, ground: vec3) -> vec4 {
let est = res.rgb + ground * (1.0 - res.a)
let add = (vec3(1.0, 1.0, 1.0) - exp(-light)) * max(vec3(1.0, 1.0, 1.0) - est, vec3(0.0, 0.0, 0.0))
return vec4(res.rgb + add, res.a)
}
// An LED's colour at full strength: its brightest channel at one.
let md_led_lamp_full = fn(ink: vec3) -> vec3 {
return ink / max(max(ink.x, max(ink.y, ink.z)), 0.01)
}
// The same colour at the emitter's own chroma: the hue kept and its
// saturation raised by `vivid` (0 leaves a pale colour pale; a colour with
// next to no hue, a white or a plain lamp, stays neutral either way).
let md_led_lamp_hue = fn(ink: vec3, vivid: float) -> vec3 {
let f = md_led_lamp_full(ink)
let mn = min(f.x, min(f.y, f.z))
let ch = 1.0 - mn
let pure = (f - vec3(mn, mn, mn)) / max(ch, 0.001)
return mix(f, pure, clamp(vivid, 0.0, 1.0) * 0.72 * smoothstep(0.03, 0.12, ch))
}
// The lamp: its lens a rounded box `hb` either side of `c` (round when the
// two are equal), `lit` its current 0..1, `ink` its colour and `vivid` how
// far its chroma is raised, `ground` what shows through where nothing is
// drawn, `room` how far its light may reach, `bez` and `raised` its bezel
// (raised 0: a recess cut in what is under it; 1: a raised ring of `bez`;
// 2: the same ring polished metal). Premultiplied over `dst`.
let md_led_lamp = fn(dst: vec4, p: vec2, c: vec2, hb: vec2, lit: float, ink: vec3, vivid: float, px: float, ground: vec3, room: float, bez: vec3, raised: float) -> vec4 {
let rt = max(min(hb.x, hb.y), 0.4)
let d = Material.sd_box(p, c, hb, rt)
let bw = clamp(0.3 * rt, 0.7, 2.4)
let ds = d - bw
let L = clamp(lit, 0.0, 1.0)
let b = md_led_lamp_full(ink)
let hue = md_led_lamp_hue(ink, vivid)
let pw = L * sqrt(L)
let white = vec3(1.0, 1.0, 1.0)
let lum = vec3(0.2126, 0.7152, 0.0722)
let pale = smoothstep(0.35, 0.75, dot(ground, lum))
var res = dst
let x = max(ds, 0.0)
let reach = max(room, 1.0)
let win = 1.0 - smoothstep(0.55 * reach, reach, x)
// The lens's own frame: `q` in lens radii from its centre line, `t` its
// distance out, `nrm` the way its outline faces.
let ex = max(hb.x - hb.y, 0.0)
let ey = max(hb.y - hb.x, 0.0)
let qx = p.x - c.x
let qy = p.y - c.y
let q = vec2(qx - clamp(qx, 0.0 - ex, ex), qy - clamp(qy, 0.0 - ey, ey)) / rt
let t = clamp(length(q), 0.0, 1.0)
let nrm = q / max(length(q), 0.001)
// What the bead and its bezel throw on the ground: a soft shadow, down.
let shd = Finish.drop(p, c, hb + vec2(bw, bw), rt + bw, vec2(0.0, 0.22 * rt + 0.3), 0.32 * rt + px) * mix(0.18, 0.34, min(raised, 1.0)) * (1.0 - 0.6 * pw)
res = Finish.over(res, vec4(ground * 0.4 * shd, shd))
if L > 0.001 && pale > 0.001 {
// The tint, on a pale ground only: its hue laid over, never darker.
let h = clamp(0.42 * pw * pow(2.0, -x / max(0.4 * reach, 0.5)) * win, 0.0, 0.45) * pale
let est = res.rgb + ground * (1.0 - res.a)
let w = clamp(dot(hue, lum) / max(dot(est, lum), 0.001), 0.0, 1.0)
res = Finish.over(res, vec4(hue * h * w, h * w))
}
// The bezel: raised, a ring of `bez` rounded over, lit from the upper
// left; or a socket cut into what is under it, its upper wall in shadow
// and a catch light on its lower rim.
let a_s = Finish.cover(ds, px)
let u = clamp(d / max(bw, 0.01), 0.0, 1.0)
let facing = dot(nrm * (u - 0.5) * 2.0, vec2(-0.45, -0.89))
let metal = clamp(raised - 1.0, 0.0, 1.0)
var rc = bez * (0.94 + mix(0.12, 0.55, metal) * facing)
rc = mix(rc, min(bez + vec3(0.30, 0.30, 0.30), white), smoothstep(mix(0.35, 0.55, metal), 0.9, facing) * mix(0.6, 0.85, metal))
rc = mix(rc, rc * 0.8, Finish.band(ds, 0.0, max(px, 0.45), px) * smoothstep(0.0, 0.8, nrm.y) * 0.8)
let under = res.rgb / max(res.a, 0.001) * step(0.001, res.a) + ground * (1.0 - step(0.001, res.a))
let gy = clamp(nrm.y * t, -1.0, 1.0)
var sc = under * mix(0.55, 0.92, smoothstep(-0.9, 0.9, gy))
sc = mix(sc, min(under * 1.06 + vec3(0.28, 0.28, 0.28), white), Finish.band(ds, 0.0, max(px, 0.45), px) * smoothstep(0.2, 0.95, nrm.y) * 0.85)
sc = min(mix(sc, rc, min(raised, 1.0)), white)
res = Finish.over(res, vec4(sc * a_s, a_s))
// The light: a tight hot halo and a short bloom in its hue, gone in
// about two radii, falling on the bezel too.
if L > 0.001 {
let xl = max(d, 0.0)
let halo = exp(-xl / max(0.30 * rt, 0.4)) * 1.5 + exp(-xl / max(0.65 * rt, 0.7)) * 0.32
let hwin = 1.0 - smoothstep(1.1 * rt, 2.0 * rt + 1.0, xl)
res = md_led_lamp_add(res, mix(hue, white, 0.15 * pw) * halo * pw * hwin * win, ground)
}
// THE BEAD: a glossy dome of coloured glass. Its colour deepens toward
// the top; light pools low inside, where it comes through the dome; lit,
// its middle burns toward white at full current and its rim is its most
// saturated colour.
let top = smoothstep(-1.0, 0.6, q.y)
let pool = exp(-(q.y - 0.55) * (q.y - 0.55) / 0.09 - q.x * q.x / 0.30)
var on = mix(hue * mix(0.55, 0.8, L), mix(b, hue, 0.5), top)
on = mix(on, min(mix(b, hue, 0.4) + vec3(0.35, 0.35, 0.35), white), pool * 0.5)
on = mix(on, vec3(1.0, 0.99, 0.975), exp(-t * t / 0.20) * smoothstep(0.5, 1.0, L) * 0.85)
on = mix(on, hue * (0.82 + 0.18 * L), smoothstep(0.62, 1.0, t))
on = on * (0.42 + 0.58 * L)
// Out: dark tinted glass on a dark ground, a grey bead on a pale one,
// its colour barely there, the same dome, a catch light along its lower
// rim.
let offc = mix(mix(vec3(0.085, 0.09, 0.115), ground * 0.62, pale), b * 0.4, 0.12)
var off = mix(offc * 0.62, offc * 1.08, top)
off = mix(off, min(offc + vec3(0.18, 0.18, 0.18), white), pool * 0.35)
off = off * mix(1.0, 0.75, smoothstep(0.75, 1.0, t))
let catchl = smoothstep(0.62, 0.9, t) * (1.0 - smoothstep(0.9, 1.0, t)) * smoothstep(0.2, 0.9, nrm.y)
off = off + vec3(0.30, 0.30, 0.32) * catchl * (1.0 - 0.5 * pale)
var col = mix(off, min(on, white), smoothstep(0.0, 0.35, L))
// One crisp highlight: a white oval across its upper third.
let e = length(vec2(q.x / 0.62, (q.y + 0.46) / 0.30))
let spec = (1.0 - smoothstep(0.55, 1.0, e)) * mix(1.0, 0.3, clamp((q.y + 0.76) / 0.6, 0.0, 1.0))
col = mix(col, white, spec * mix(0.8, 0.55, L) * smoothstep(0.9, 2.2, rt))
// Out, a faint seam where the dome meets its bezel; lit, none: the rim
// is the colour.
col = mix(col, col * 0.7, Finish.band(d, 0.0, px * 0.6, px) * 0.6 * (1.0 - smoothstep(0.0, 0.25, L)))
col = min(col, white)
let a_l = Finish.cover(d, px)
return Finish.over(res, vec4(col * a_l, a_l))
}
// The Lamp widget and the bar lamp: the lamp above, here in a ring of the same moulded plastic, rounded over and throwing its own soft shadow.
// The halo's room is held in the lamp's own box (the library reserves a
// lens thickness either side once a halo is asked for), so the light is
// never cut at its edge.
mod.widgets.Lamp.halo = 0.3
mod.widgets.Lamp.halo_reach = 1.0
mod.widgets.Lamp.draw_bg.vertex = fn() {
let m = 2.0
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
}
mod.widgets.Lamp.draw_bg.pixel = fn() {
let m = 2.0
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
let reach = clamp(self.reach, 0.0, 1.0)
let t = self.rect_size.y / (1.0 + 2.0 * reach)
let room = t * reach
let c = self.rect_size * 0.5
let half_len = max((self.rect_size.x - 2.0 * room) * 0.5, t * 0.5)
// The lens is the lamp's size; its socket stands round it in the room.
let hb = vec2(mix(t * 0.5, half_len, self.bar), t * 0.5)
let res = md_led_lamp(vec4(0.0, 0.0, 0.0, 0.0), p, c, hb, self.lit, self.intent_color().rgb, 1.0 - step(3.5, self.intent), px, vec3(0.855, 0.859, 0.867), max(room - 0.5, 2.0), vec3(0.88, 0.885, 0.895), 1.0)
return res * self.opacity
}
true