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