use mod.prelude.widgets_internal.* // A slate blackboard, and everything on it drawn in chalk. A control is a // LINE OF CHALK drawn on the board by hand (box, ring, track, tick, check, // pointer): its width wandering a little, its path a little off true, and // its body dry and broken where the board's grain did not take the chalk, // so specks of slate show through it. What is chosen is FILLED IN: rubbed // full of chalk laid on its side, hatched over, the words on it written in // the board's own dark (the primary, the chosen segment and tab), or a dot // of solid chalk (a toggle that is on, a chosen radio). A soft coloured // chalk (pale yellow, pink, light blue, mint) marks a state or a value // where one is needed. Pressed, the hand goes round the box a second time // just inside the first and rubs a little dust into it. Focus is a light // blue chalk line drawn round the control. Nothing casts a shadow: there is // no depth on a blackboard but the frame's. // A ROW OF FIELDS IS ONE HEIGHT. Left to themselves the library's fields each // come out at a size of their own, so the box metrics are stated once here // and handed to each field below. A field is a chalk box as a button is, so // the box is 32, the height of a button. let field_height = 32.0 let field_pad_v = mod.theme.mspace_1.top let field_radius = 6.0 // A hairline, for the few stock fields the faces below do not redraw. let field_border = 0.8 // 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: the // height is all these take; a padding would displace its line. let field_frame = fn(w) { w.min_height = field_height } field_room(mod.widgets.TextInput) field_face(mod.widgets.TextInput.draw_bg) field_room(mod.widgets.TextInputFlat) field_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 = #252a27 // Plain views stop clipping, so a chalk line or its dust may fall a point // into the gap beside its control. Scroll views keep their own clip. mod.widgets.View.clip_x = false mod.widgets.View.clip_y = false // --------------------------------------------------------------------------- // SHARED PARTS. Distances in points, negative inside, y down; `px` is one // device pixel in points. Every texture is laid in the piece's own space (a // point from its corner, seeded by its size), so it rides with the piece and // nothing changes when a page scrolls. let g3 = fn(v: float) -> vec3 { return vec3(v, v, v) } // A rounded box, true radius. let cb_rbox = fn(p: vec2, c: vec2, h: vec2, r: float) -> float { let rr = clamp(r, 0.0, min(h.x, h.y)) let q = abs(p - c) - h + vec2(rr, rr) return min(max(q.x, q.y), 0.0) + length(max(q, vec2(0.0, 0.0))) - rr } // A rounded box with a radius per corner (tl, tr, br, bl). let cb_rbox4 = fn(p: vec2, c: vec2, h: vec2, r4: vec4) -> float { let q0 = p - c let rt = mix(r4.y, r4.x, step(q0.x, 0.0)) let rb = mix(r4.z, r4.w, step(q0.x, 0.0)) let r = clamp(mix(rb, rt, step(q0.y, 0.0)), 0.0, min(h.x, h.y)) let q = abs(q0) - h + vec2(r, r) return min(max(q.x, q.y), 0.0) + length(max(q, vec2(0.0, 0.0))) - r } // The corners a button group asks for: a corner written as 0 is a square // join, any other the family's radius. let cb_corners = fn(tl: float, tr: float, br: float, bl: float, r: float) -> vec4 { return vec4(mix(r, step(0.5, tl) * r, step(0.0, tl)), mix(r, step(0.5, tr) * r, step(0.0, tr)), mix(r, step(0.5, br) * r, step(0.0, br)), mix(r, step(0.5, bl) * r, step(0.0, bl))) } let cb_seg = fn(p: vec2, a: vec2, b: vec2) -> float { let pa = p - a let ba = b - a let k = clamp(dot(pa, ba) / max(dot(ba, ba), 0.0001), 0.0, 1.0) return length(pa - ba * k) } let cb_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 PALETTE. The slate #252a27, a charcoal with a little green in its // grey; white chalk #e8eae6; the board's own dark for words written on a // chalk fill; and the soft coloured chalks. let cb_slate_c = fn() -> vec3 { return vec3(0.145, 0.165, 0.153) } let cb_chalk_c = fn() -> vec3 { return vec3(0.910, 0.918, 0.902) } let cb_dim_c = fn() -> vec3 { return vec3(0.490, 0.510, 0.498) } let cb_dark_c = fn() -> vec3 { return vec3(0.114, 0.129, 0.125) } let cb_yellow_c = fn() -> vec3 { return vec3(0.953, 0.890, 0.627) } let cb_pink_c = fn() -> vec3 { return vec3(0.949, 0.702, 0.741) } let cb_blue_c = fn() -> vec3 { return vec3(0.659, 0.804, 0.933) } let cb_mint_c = fn() -> vec3 { return vec3(0.651, 0.890, 0.769) } // THE SLATE at point `w`: a long drift of tone, a fine mottle, a free grain // per device pixel, and the chalk dust caught in its pores, as sparse pale // specks a device pixel or two across, with the odd darker pit beside them. // All of it low, so the board reads as stone under the chalk and never as // a pattern. `amt` scales the whole texture. let cb_slate = fn(w: vec2, px: float, amt: float) -> vec3 { let broad = Finish.noise(w * 0.005 + vec2(3.1, 1.7)) - 0.5 let mid = Finish.noise(w * 0.06 + vec2(13.1, 7.7)) - 0.5 let fine = Finish.noise(w * 0.7 + vec2(5.3, 2.9)) - 0.5 var col = cb_slate_c() * (1.0 + (broad * 0.10 + mid * 0.035 + fine * 0.05) * amt) + g3(Finish.grain(w / px, 0.008 * amt)) let dp = w / px let sp = Finish.noise(dp * 0.6 + vec2(41.0, 17.0)) let h = Finish.hash(floor(dp) + vec2(3.0, 11.0)) let dust = smoothstep(0.74, 0.82, sp) * step(0.5, h) * (0.4 + 0.6 * Finish.noise(w * 0.03 + vec2(2.0, 5.0))) col = col + g3(0.040 * dust * amt) let pit = smoothstep(0.80, 0.86, 1.0 - sp) * step(0.65, h) col = col * (1.0 - 0.18 * pit * amt) return col } // A hand's wobble on a line, at most about `k` points either way: a slow // drift along it and a little tremor. let cb_wob = fn(p: vec2, k: float, seed: vec2) -> float { return (Finish.noise(p * 0.05 + seed) - 0.5) * 2.0 * k + (Finish.noise(p * 0.21 + vec2(seed.y, seed.x) + vec2(3.0, 1.0)) - 0.5) * 0.5 * k } // THE CHALK'S TOOTH at a point: how much chalk the board's grain took there, // 0..1, crisp at the device pixel. Small blobs of a device pixel or two, // a free value per pixel, and streaks that run along a line (`v` is the // distance across it, so the streaks follow its course). let cb_tooth = fn(p: vec2, px: float, v: float) -> float { let dp = p / px let g1 = Finish.noise(dp * 0.55 + vec2(3.7, 1.3)) let g2 = Finish.hash(floor(dp) + vec2(1.0, 7.0)) let st = Finish.noise(vec2(v * 2.2, (p.x + p.y) * 0.05) + vec2(5.0, 2.0)) return g1 * 0.42 + g2 * 0.33 + st * 0.25 } // A LINE OF CHALK `hw` points either side of its middle; `dl` is the // unsigned distance from the middle. Its width wanders as a hand's does; // its body is dry and broken: where the board's tooth did not take the // chalk the slate shows through in specks, more at the line's edges and // along drier stretches, though a trace of dust stays in every gap; a few // crisp grains are shed just beside it. Never a glow: the line's edge is // the chalk's edge. Premultiplied. let cb_stroke = fn(dl: float, hw: float, p: vec2, px: float, ink: vec3, amt: float) -> vec4 { let w = hw * (1.0 + (Finish.noise(p * 0.15 + vec2(7.0, 3.0)) - 0.5) * 0.35) let body = Finish.cover(dl - w, px) let tooth = cb_tooth(p, px, dl / max(w, 0.3)) let dry = Finish.noise(p * 0.11 + vec2(9.0, 4.0)) let edge = smoothstep(0.30, 1.0, dl / max(w, 0.3)) let cut = 0.20 + 0.30 * edge + 0.24 * dry let keep = smoothstep(cut - 0.07, cut + 0.07, tooth) let cov = body * mix(0.24, 1.0, keep) let x = max(dl - w, 0.0) let grains = step(0.94, Finish.hash(floor(p / px) + vec2(13.0, 29.0))) * (1.0 - smoothstep(0.0, 1.1, x)) * (1.0 - body) * 0.45 let bright = 0.86 + 0.14 * Finish.noise(p / px * 0.4 + vec2(1.0, 8.0)) let a = clamp(cov + grains, 0.0, 1.0) * amt return vec4(ink * bright * a, a) } // CHALK RUBBED ON ITS SIDE over the inside of `d`: `dens` 0..1 is how much // of the board it covers. The tooth decides where it took, in streaks that // run on a slant the way the stick was dragged; where it did not, a thinner // dust still lies, so a full field reads even and the words on it stay // clear. Premultiplied. let cb_rub = fn(d: float, p: vec2, px: float, ink: vec3, dens: float, amt: float) -> vec4 { let inside = Finish.cover(d, px) let along = p.x * 0.574 - p.y * 0.819 let across = p.x * 0.819 + p.y * 0.574 let dp = (p + vec2(17.0, 5.0)) / px let g1 = Finish.noise(dp * 0.8 + vec2(2.0, 9.0)) let g2 = Finish.hash(floor(dp) + vec2(23.0, 3.0)) let st = Finish.noise(vec2(along * 0.06, across * 0.9) + vec2(5.0, 1.0)) let tooth = g1 * 0.30 + g2 * 0.35 + st * 0.35 let keep = smoothstep(1.0 - dens - 0.12, 1.0 - dens + 0.12, tooth) let bright = 0.86 + 0.14 * st let a = inside * mix(dens * 0.6, 1.0, keep) * amt return vec4(ink * bright * a, a) } // HATCHING in chalk over the inside of `d`: strokes on a slant `pitch` // points apart, each of its own weight, set a little off its neighbours, // and lifted off the board in places, the board between them. Each stroke // keeps to its own lane, so none is ever cut straight. Premultiplied. let cb_hatch = fn(d: float, p: vec2, px: float, ink: vec3, pitch: float, amt: float, seed: float) -> vec4 { let along = p.x * 0.574 - p.y * 0.819 let across = p.x * 0.819 + p.y * 0.574 let k = floor(across / pitch) let hk = Finish.hash(vec2(k, seed)) let hk2 = Finish.hash(vec2(k + 17.0, seed + 3.0)) let lc = (k + 0.5 + (hk - 0.5) * 0.3) * pitch + (Finish.noise(vec2(along * 0.12, k * 1.7 + seed)) - 0.5) * pitch * 0.15 let dl = abs(across - lc) let hw = pitch * (0.17 + 0.13 * hk2) let run = smoothstep(0.26, 0.40, Finish.noise(vec2(along * 0.07 + hk * 13.0, k * 3.3 + seed))) return cb_stroke(dl, hw, p, px, ink, amt * run * Finish.cover(d + 0.2, px)) } // A FIELD FILLED IN WITH CHALK, inside `d`: rubbed in on its side and hatched // over. `level` 0..1 how full: 1 the primary's light field, 0.3 a soft band. let cb_filled = fn(d: float, p: vec2, px: float, ink: vec3, level: float, seed: float) -> vec4 { var out = cb_rub(d, p, px, ink, 0.66 * level, 0.82) return Finish.over(out, cb_hatch(d, p, px, ink, 2.6, 0.80 * min(level * 1.4, 1.0), seed)) } // A BOX DRAWN IN CHALK round the shape whose exact distance is `d0`: its // line `hw` points either side of a path just inside the edge, wandering a // little as a hand does, and in places a second, lighter pass of the hand a // little off the first. Premultiplied. let cb_outline = fn(d0: float, p: vec2, px: float, hw: float, ink: vec3, amt: float, seed: vec2) -> vec4 { let d = d0 + cb_wob(p, 0.35, seed) let out = cb_stroke(abs(d + hw + 0.15), hw, p + seed, px, ink, amt) let d2 = d0 + cb_wob(p, 0.55, seed + vec2(5.3, 2.1)) + 0.5 let show = smoothstep(0.50, 0.68, Finish.noise(p * 0.045 + seed * 0.37 + vec2(2.0, 9.0))) let s2 = cb_stroke(abs(d2 + hw + 0.15), hw * 0.55, p + seed + vec2(19.0, 7.0), px, ink, amt * 0.5 * show) return Finish.over(s2, out) } // FOCUS: a light blue chalk line drawn round the control, two and a half // points out. let cb_focus = fn(d: float, p: vec2, px: float, focus: float) -> vec4 { let dd = d - 2.6 + cb_wob(p, 0.35, vec2(11.0, 3.0)) return cb_stroke(abs(dd), 0.55, p, px, cb_blue_c(), 0.9 * focus) } // A DISC DRAWN IN CHALK: its ring, and inside it chalk rubbed in at `fill` // 0..1 (0 the bare board, 1 a dot of solid chalk) in the colour `ink`. // Premultiplied. let cb_disc = fn(p: vec2, c: vec2, r: float, px: float, ink: vec3, fill: float, hover: float, press: float, dis: float, seed: vec2) -> vec4 { let live = 1.0 - dis let d = length(p - c) - r let fade = mix(1.0, 0.42, dis) var out = cb_rub(d + 0.6, p + seed, px, ink, clamp(fill + 0.08 * hover * live + 0.12 * press * live, 0.0, 1.0), fade) if fill > 0.3 { out = Finish.over(out, cb_hatch(d + 0.8, p + seed, px, ink, 2.2, 0.75 * fade, seed.x)) } let hw = max(min(r * 0.12, 1.05), 0.6) * (1.0 + 0.12 * hover * live) return Finish.over(out, cb_outline(d, p, px, hw, ink, fade * mix(0.92, 1.0, hover * live), seed)) } // --------------------------------------------------------------------------- // BUTTONS. A box drawn in chalk round the button; `kind` picks what is in // it: 0 nothing but the board (default, flat, tertiary, outline), 1 rubbed // full of chalk with the words in the board's dark (primary), 2 a thin // rub of dust (secondary), 4 the box in pink chalk (danger), 6 bare until // touched (flatter), 7 a soft rub of the chalk `tint`, 9 a field of pale // yellow chalk. Under the pointer the line is drawn a little heavier and a // breath of dust is rubbed in; pressed, the hand goes round a second time // just inside the first. Disabled, the box is half rubbed away and faintly // hatched. The quad grows by `m` for the dust and the focus line. let cb_button = fn(pos: vec2, size: vec2, o: vec2, px: float, hover: float, down: float, focus: float, dis: float, loading: float, anim: float, r4: vec4, kind: float, m: float, tint: vec4, row: float) -> vec4 { let p = pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let live = 1.0 - dis let press = down * live let hov = hover * live let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o let hi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y, px)) - o let c = (lo + hi) * 0.5 let h = (hi - lo) * 0.5 let seed = size * vec2(0.137, 0.271) let d0 = cb_rbox4(p, c, h, r4) let bare = step(5.5, kind) * step(kind, 6.5) var ink = cb_chalk_c() if kind > 3.5 && kind < 4.5 { ink = cb_pink_c() } var out = vec4(0.0, 0.0, 0.0, 0.0) // what is in the box let inner = d0 + 1.6 if kind > 0.5 && kind < 1.5 { out = cb_filled(inner, p + seed, px, cb_chalk_c(), mix(1.0, 0.45, dis) * (1.0 - 0.12 * press), seed.x) } if kind > 1.5 && kind < 2.5 { out = cb_rub(inner, p + seed, px, cb_chalk_c(), 0.16, mix(1.0, 0.5, dis)) } if kind > 6.5 && kind < 7.5 { out = cb_rub(inner, p + seed, px, mix(cb_chalk_c(), tint.rgb, 0.8), 0.32, tint.a) } if kind > 8.5 { out = cb_filled(inner, p + seed, px, cb_yellow_c(), 0.9, seed.x) } if dis > 0.5 && kind < 0.5 { out = cb_hatch(inner, p + seed, px, cb_chalk_c(), 3.0, 0.22, seed.x) } out = Finish.over(out, cb_rub(inner, p + seed * 1.7, px, cb_chalk_c(), 0.17 * hov + 0.07 * press, 1.0 - step(0.5, kind) * step(kind, 1.5))) // the box let hw = 0.95 + 0.18 * hov + 0.08 * press let shown = mix(mix(0.88 + 0.12 * hov, 0.42, dis), clamp(0.55 * hov + press, 0.0, 1.0), bare) out = Finish.over(out, cb_outline(d0, p, px, hw, ink, shown, seed)) // pressed: the second pass just inside the first out = Finish.over(out, cb_stroke(abs(d0 + 3.6 + cb_wob(p, 0.3, seed + vec2(7.0, 1.0))), 0.6, p + vec2(3.0, 5.0), px, ink, 0.75 * press)) out = Finish.over(out, cb_focus(d0, p, px, focus * live)) // The wait spinner, which the stock face drew and this one must too. if loading > 0.0 { let rr = max(min(h.x, h.y) - 5.0, 2.0) let q = p - c var ang = atan2(q.y, q.x) - anim * 2.0 * PI ang = ang - 2.0 * PI * floor(ang / (2.0 * PI)) let on_arc = step(ang, 1.5 * PI) let sink = mix(cb_chalk_c(), cb_dark_c(), step(0.5, kind) * step(kind, 1.5)) out = Finish.over(out, cb_stroke(abs(length(q) - rr), 0.9, p, px, sink, loading * on_arc)) } return out } // Every button template: the root and the sizes share its draw object, so // they wear the plain box; the roles are specialised below. mod.widgets.ButtonFlat.clip_x = false mod.widgets.ButtonFlat.clip_y = false mod.widgets.ButtonFlat.draw_bg.vertex = fn() { let g = cb_grow(self.rect_pos, self.rect_size, 6.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(cb_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, cb_corners(self.border_radius_tl, self.border_radius_tr, self.border_radius_br, self.border_radius_bl, 9.0), 0.0, 6.0, vec4(0.0, 0.0, 0.0, 0.0), 1.0), 6.0) } mod.widgets.Button.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return self.host_over(cb_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, cb_corners(self.border_radius_tl, self.border_radius_tr, self.border_radius_br, self.border_radius_bl, 9.0), 0.0, 6.0, vec4(0.0, 0.0, 0.0, 0.0), 1.0), 6.0) } mod.widgets.ButtonPrimary.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return self.host_over(cb_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, cb_corners(self.border_radius_tl, self.border_radius_tr, self.border_radius_br, self.border_radius_bl, 9.0), 1.0, 6.0, vec4(0.0, 0.0, 0.0, 0.0), 1.0), 6.0) } mod.widgets.ButtonSecondary.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return self.host_over(cb_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, cb_corners(self.border_radius_tl, self.border_radius_tr, self.border_radius_br, self.border_radius_bl, 9.0), 2.0, 6.0, vec4(0.0, 0.0, 0.0, 0.0), 1.0), 6.0) } mod.widgets.ButtonTertiary.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return self.host_over(cb_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, cb_corners(self.border_radius_tl, self.border_radius_tr, self.border_radius_br, self.border_radius_bl, 9.0), 0.0, 6.0, vec4(0.0, 0.0, 0.0, 0.0), 1.0), 6.0) } mod.widgets.ButtonDanger.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return self.host_over(cb_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, cb_corners(self.border_radius_tl, self.border_radius_tr, self.border_radius_br, self.border_radius_bl, 9.0), 4.0, 6.0, vec4(0.0, 0.0, 0.0, 0.0), 1.0), 6.0) } mod.widgets.ButtonOutline.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return self.host_over(cb_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, cb_corners(self.border_radius_tl, self.border_radius_tr, self.border_radius_br, self.border_radius_bl, 9.0), 5.0, 6.0, vec4(0.0, 0.0, 0.0, 0.0), 1.0), 6.0) } mod.widgets.ButtonFlatter.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return self.host_over(cb_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, cb_corners(self.border_radius_tl, self.border_radius_tr, self.border_radius_br, self.border_radius_bl, 9.0), 6.0, 6.0, vec4(0.0, 0.0, 0.0, 0.0), 1.0), 6.0) } // Templates that draw a face of their own on top of these roots keep the // plain quad: the grown one is only for the faces written here. mod.widgets.CloseButton.draw_bg.vertex = fn() { self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size) } mod.widgets.CopyButton.draw_bg.vertex = fn() { self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size) } mod.widgets.BurgerButton.draw_bg.vertex = fn() { self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size) } mod.widgets.ButtonFloating.draw_bg.vertex = fn() { self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size) } mod.widgets.ButtonFloatingSm.draw_bg.vertex = fn() { self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size) } mod.widgets.glass.CutButton.draw_bg.vertex = fn() { self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size) } // Geometry: one 32 point row, 13 points either side of the word; the small // one 26, the large one 40. mod.widgets.ButtonFlat.padding = mod.turtle.Inset{left: 13.0 right: 13.0 top: 4.0 bottom: 4.0} mod.widgets.Button.padding = mod.turtle.Inset{left: 13.0 right: 13.0 top: 4.0 bottom: 4.0} mod.widgets.ButtonPrimary.padding = mod.turtle.Inset{left: 13.0 right: 13.0 top: 4.0 bottom: 4.0} mod.widgets.ButtonSecondary.padding = mod.turtle.Inset{left: 13.0 right: 13.0 top: 4.0 bottom: 4.0} mod.widgets.ButtonDanger.padding = mod.turtle.Inset{left: 13.0 right: 13.0 top: 4.0 bottom: 4.0} mod.widgets.ButtonTertiary.padding = mod.turtle.Inset{left: 13.0 right: 13.0 top: 4.0 bottom: 4.0} mod.widgets.ButtonOutline.padding = mod.turtle.Inset{left: 13.0 right: 13.0 top: 4.0 bottom: 4.0} mod.widgets.ButtonIcon.padding = mod.turtle.Inset{left: 8.0 right: 8.0 top: 4.0 bottom: 4.0} mod.widgets.Button.icon_walk = mod.turtle.Walk{width: 16.0 height: Fit} mod.widgets.ButtonFlat.min_height = 32.0 mod.widgets.Button.min_height = 32.0 mod.widgets.ButtonFlat.draw_text.text_style.font_size = 14.0 mod.widgets.ButtonXs.min_height = 0.0 mod.widgets.ButtonXs.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 0.0 bottom: 0.0} mod.widgets.ButtonXs.draw_text.text_style.font_size = 11.5 mod.widgets.ButtonSm.min_height = 0.0 mod.widgets.ButtonSm.height = 26.0 mod.widgets.ButtonSm.padding = mod.turtle.Inset{left: 10.0 right: 10.0 top: 0.0 bottom: 0.0} mod.widgets.ButtonSm.draw_text.text_style.font_size = 13.0 mod.widgets.ButtonLg.height = 40.0 mod.widgets.ButtonLg.padding = mod.turtle.Inset{left: 18.0 right: 18.0 top: 0.0 bottom: 0.0} mod.widgets.ButtonLg.draw_text.text_style.font_size = 16.0 // The words on a button are chalk; on the primary's chalk field they are // written in the board's dark, and on the danger button in pink chalk. mod.widgets.ButtonFlat.draw_text.text_style.font_family = mod.theme.font_label.font_family mod.widgets.ButtonPrimary.draw_text.color = #1d2120 mod.widgets.ButtonPrimary.draw_text.color_hover = #141816 mod.widgets.ButtonPrimary.draw_text.color_down = #141816 mod.widgets.ButtonPrimary.draw_text.color_focus = #1d2120 mod.widgets.ButtonPrimary.draw_text.color_disabled = #3a3f3c mod.widgets.ButtonPrimary.draw_icon.color = #1d2120 mod.widgets.ButtonPrimary.draw_text.text_style = mod.theme.font_label{font_size: 14.0} mod.widgets.ButtonSecondary.draw_text.color = #e8eae6 mod.widgets.ButtonSecondary.draw_text.color_hover = #f8f9f6 mod.widgets.ButtonSecondary.draw_text.color_down = #f8f9f6 mod.widgets.ButtonSecondary.draw_text.color_focus = #e8eae6 mod.widgets.ButtonSecondary.draw_icon.color = #e8eae6 mod.widgets.ButtonSecondary.draw_text.text_style = mod.theme.font_label{font_size: 14.0} mod.widgets.ButtonTertiary.draw_text.color = #e8eae6 mod.widgets.ButtonTertiary.draw_text.color_hover = #f8f9f6 mod.widgets.ButtonTertiary.draw_text.color_down = #f8f9f6 mod.widgets.ButtonTertiary.draw_text.color_focus = #e8eae6 mod.widgets.ButtonTertiary.draw_icon.color = #e8eae6 mod.widgets.ButtonTertiary.draw_text.text_style = mod.theme.font_label{font_size: 14.0} mod.widgets.ButtonDanger.draw_text.color = #f2b3bd mod.widgets.ButtonDanger.draw_text.color_hover = #f8c6ce mod.widgets.ButtonDanger.draw_text.color_down = #f8c6ce mod.widgets.ButtonDanger.draw_text.color_focus = #f2b3bd mod.widgets.ButtonDanger.draw_icon.color = #f2b3bd mod.widgets.ButtonDanger.draw_text.text_style = mod.theme.font_label{font_size: 14.0} mod.widgets.ButtonOutline.draw_text.color = #e8eae6 mod.widgets.ButtonOutline.draw_text.color_hover = #f8f9f6 mod.widgets.ButtonOutline.draw_text.color_down = #f8f9f6 mod.widgets.ButtonOutline.draw_text.color_focus = #e8eae6 mod.widgets.ButtonOutline.draw_text.color_disabled = #7d827f mod.widgets.ButtonOutline.draw_icon.color = #e8eae6 mod.widgets.ButtonFlat.draw_bg.loading_color = #e8eae6 // --------------------------------------------------------------------------- // CHECK BOXES. A square drawn in chalk; set, a tick drawn in one stroke that // runs out past the box's top right corner, as a hand draws it; mixed, a // dash. Under the pointer the line is a little heavier and a breath of dust // is rubbed into the box; pressed, more. mod.widgets.CheckBoxFlat.clip_x = false mod.widgets.CheckBoxFlat.clip_y = false mod.widgets.CheckBoxFlat.draw_bg.vertex = fn() { let g = cb_grow(self.rect_pos, self.rect_size, 5.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.CheckBoxCustom.draw_bg.vertex = fn() { self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size) } mod.widgets.CheckBoxFlat.padding = mod.turtle.Inset{left: 0.0 right: 3.0 top: 3.0 bottom: 3.0} mod.widgets.CheckBoxFlat.label_walk.margin = mod.turtle.Inset{left: 24.0 right: 3.0 top: 0.0 bottom: 0.0} mod.widgets.CheckBoxFlat.draw_bg.size = uniform(17.0) mod.widgets.CheckBoxFlat.min_height = 26.0 mod.widgets.CheckBoxFlat.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 5.0 let size = self.rect_size let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let o = self.rect_pos let sz = self.size let live = 1.0 - self.disabled let x0 = mix(1.0, size.x - sz - 1.0, self.mark_at_end) let lo = vec2(Finish.snap(o.x + x0, px), Finish.snap(o.y + (size.y - sz) * 0.5, px)) - o let h = vec2(sz * 0.5, sz * 0.5) let c = lo + h let rnd = step(20.0, self.border_radius) let r = mix(2.5, h.y, rnd) let d0 = cb_rbox(p, c, h, r) let ink = mix(mix(cb_chalk_c(), cb_pink_c(), self.error), cb_dim_c(), self.disabled) let fade = mix(1.0, 0.6, self.disabled) var out = cb_rub(d0 + 1.5, p, px, cb_chalk_c(), (0.10 * self.hover + 0.22 * self.down) * live, 1.0) out = Finish.over(out, cb_outline(d0, p, px, 0.95 + 0.12 * self.hover * live, ink, fade, vec2(4.0, 1.0))) let k = sz / 14.0 let wb = cb_wob(p, 0.25, vec2(9.0, 4.0)) let m0 = lo + vec2(2.8, 7.0) * k let m1 = lo + vec2(6.2, 11.4) * k let m2 = lo + vec2(15.6, -2.6) * k let ck = min(cb_seg(p, m0, m1), cb_seg(p, m1, m2)) + wb let dash = cb_seg(p, lo + vec2(3.6, 7.3) * k, lo + vec2(10.6, 6.8) * k) + wb let show_ck = self.active * (1.0 - self.mixed) out = Finish.over(out, cb_stroke(max(ck, 0.0), 1.1 * k, p, px, ink, show_ck * fade)) out = Finish.over(out, cb_stroke(max(dash, 0.0), 1.1 * k, p, px, ink, self.mixed * fade)) return Finish.over(out, cb_focus(d0, p, px, self.focus * live)) } // --------------------------------------------------------------------------- // TOGGLES. A pill drawn in chalk, 38 by 18 points (or the size the toggle // asks for), with a dot in it. Off, the // dot is a chalk ring at the left and the free end of the pill is hatched // over; on, the dot is solid chalk at the right and the hatching has moved // to the end it left. The switches' toggles fill their dot with a coloured // chalk (`kind` 9 pale yellow, 2 light blue). mod.widgets.ToggleFlat.label_walk.margin = mod.turtle.Inset{left: 47.0 right: 3.0 top: 0.0 bottom: 0.0} mod.widgets.ToggleFlat.draw_bg.size = uniform(18.0) mod.widgets.ToggleFlat.min_height = 26.0 mod.widgets.ToggleFlat.draw_bg.pill_aspect = uniform(2.111) mod.widgets.ToggleFlat.draw_bg.knob_inset = uniform(9.0) mod.widgets.ToggleFlat.clip_x = false mod.widgets.ToggleFlat.clip_y = false mod.widgets.ToggleFlat.draw_bg.vertex = fn() { let g = cb_grow(self.rect_pos, self.rect_size, 5.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } let cb_toggle = fn(pos: vec2, size: vec2, o: vec2, px: float, active: float, drag_pos: float, drag: float, hover: float, down: float, focus: float, dis: float, error: float, at_end: float, kind: float, tsz: float, aspect: float) -> vec4 { let m = 5.0 let p = pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let live = 1.0 - dis let th = max(tsz, 8.0) let tw = th * max(aspect, 1.2) let x0 = mix(1.0, size.x - tw - 1.0, at_end) let lo = vec2(Finish.snap(o.x + x0, px), Finish.snap(o.y + (size.y - th) * 0.5, px)) - o let h = vec2(tw * 0.5, th * 0.5) let c = lo + h let d0 = cb_rbox(p, c, h, h.y) let fade = mix(1.0, 0.42, dis) let ink = mix(cb_chalk_c(), cb_pink_c(), error) var dot = cb_chalk_c() if kind > 8.5 { dot = cb_yellow_c() } if kind > 1.5 && kind < 2.5 { dot = cb_blue_c() } let kt = clamp(mix(active, drag_pos, drag), 0.0, 1.0) let kr = th * 0.39 let kx = lo.x + h.y + kt * (tw - th) let kc = vec2(Finish.snap(o.x + kx, px) - o.x, c.y) let kd = length(p - kc) - kr // the hatching over the free end of the pill, clear of the dot let hd = max(d0 + 2.0, 0.0 - kd + 1.4) var out = cb_hatch(hd, p + vec2(31.0, 7.0), px, ink, 3.0, (0.55 + 0.1 * hover * live) * fade, 4.0) out = Finish.over(out, cb_outline(d0, p, px, clamp(th * 0.053, 0.7, 1.1) + 0.1 * hover * live, ink, fade, vec2(2.0, 9.0))) let on = smoothstep(0.3, 0.7, kt) out = Finish.over(out, cb_disc(p, kc, kr, px, mix(ink, dot, on), on * 0.95, hover, down, dis, vec2(13.0, 5.0))) return Finish.over(out, cb_focus(d0, p, px, focus * live)) } mod.widgets.ToggleFlat.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return cb_toggle(self.pos, self.rect_size, self.rect_pos, px, self.active, self.drag_pos, self.drag, self.hover, self.down, self.focus, self.disabled, self.error, self.mark_at_end, 0.0, self.size, self.pill_aspect) } // --------------------------------------------------------------------------- // RADIOS. A ring drawn in chalk; chosen, a dot of chalk rubbed and hatched // in at its middle. let cb_radio = fn(p: vec2, c: vec2, rad: float, px: float, hover: float, down: float, focus: float, active: float, dis: float) -> vec4 { let live = 1.0 - dis let d0 = length(p - c) - rad let ink = mix(cb_chalk_c(), cb_dim_c(), dis) let fade = mix(1.0, 0.6, dis) var out = cb_rub(d0 + 1.5, p, px, cb_chalk_c(), (0.10 * hover + 0.22 * down) * live, 1.0) out = Finish.over(out, cb_outline(d0, p, px, 0.95 + 0.12 * hover * live, ink, fade, vec2(2.0, 5.0))) let dd = length(p - c) - rad * 0.54 out = Finish.over(out, cb_rub(dd, p + vec2(7.0, 3.0), px, ink, 0.85, active * fade)) out = Finish.over(out, cb_stroke(abs(dd + 0.5 + cb_wob(p, 0.15, vec2(9.0, 1.0))), 0.55, p, px, ink, active * fade)) return Finish.over(out, cb_focus(d0, p, px, focus * live)) } mod.widgets.RadioButtonFlat.clip_x = false mod.widgets.RadioButtonFlat.clip_y = false mod.widgets.RadioButtonFlat.draw_bg.vertex = fn() { let g = cb_grow(self.rect_pos, self.rect_size, 5.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.glass.RadioButton.draw_bg.vertex = fn() { self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size) } mod.widgets.RadioButtonFlat.padding = mod.turtle.Inset{left: 0.0 right: 3.0 top: 3.0 bottom: 3.0} mod.widgets.RadioButtonFlat.label_walk.margin = mod.turtle.Inset{left: 23.0 right: 3.0 top: 0.0 bottom: 0.0} mod.widgets.RadioButtonFlat.draw_bg.size = uniform(16.0) mod.widgets.RadioButtonFlat.min_height = 26.0 mod.widgets.RadioButtonFlat.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 5.0 let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let o = self.rect_pos let sz = self.size let lo = vec2(Finish.snap(o.x + 1.0, px), Finish.snap(o.y + (self.rect_size.y - sz) * 0.5, px)) - o let c = lo + vec2(sz * 0.5, sz * 0.5) return cb_radio(p, c, sz * 0.5, px, self.hover, self.down, self.focus, self.active, self.disabled) } // The flatter radio is its word alone: chosen, the word is ringed round by // hand in chalk; under the pointer a faint ring of dust. mod.widgets.RadioButtonFlatter.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 5.0 let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let live = 1.0 - self.disabled let c = self.rect_size * 0.5 let ab = self.rect_size * 0.5 + vec2(3.0, 1.0) let q = (p - c) / ab let d = (length(q) - 1.0) * min(ab.x, ab.y) + cb_wob(p, 0.3, vec2(6.0, 2.0)) var out = cb_stroke(abs(d), 0.5, p, px, cb_chalk_c(), 0.35 * self.hover * live * (1.0 - self.active)) out = Finish.over(out, cb_stroke(abs(d), 0.85, p, px, mix(cb_chalk_c(), cb_dim_c(), self.disabled), self.active)) return Finish.over(out, cb_focus(d, p, px, self.focus * live)) } // Radio tabs: each a chalk box; the chosen one is rubbed full of chalk, its // word in the board's dark. mod.widgets.RadioButtonTabFlat.padding = mod.turtle.Inset{left: 14.0 right: 14.0 top: 6.0 bottom: 4.0} mod.widgets.RadioButtonTabFlat.label_walk.margin = mod.turtle.Inset{left: 0.0 right: 0.0 top: 0.0 bottom: 0.0} mod.widgets.RadioButtonTabFlat.min_height = 30.0 mod.widgets.RadioButtonTabFlat.clip_x = false mod.widgets.RadioButtonTabFlat.clip_y = false mod.widgets.RadioButtonTabFlat.draw_bg.vertex = fn() { let g = cb_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 px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return cb_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down * (1.0 - self.active), self.focus, self.disabled, 0.0, 0.0, vec4(9.0, 9.0, 9.0, 9.0), step(0.5, self.active), 5.0, vec4(0.0, 0.0, 0.0, 0.0), 0.0) } mod.widgets.RadioButtonTabFlat.draw_text.color = #e8eae6 mod.widgets.RadioButtonTabFlat.draw_text.color_active = #1d2120 mod.widgets.RadioButtonTabFlat.draw_text.color_disabled = #7d827f // --------------------------------------------------------------------------- // SLIDERS. The track is a line of chalk the length of the travel, drawn // heavier and brighter over the part the value covers. The thumb is a chalk // ring 15 points across, hatched in, riding on the line: the line stops at // its rim. Every slider's quad grows for the dust and the focus line. mod.widgets.SliderMinimal.height = 32.0 mod.widgets.SliderMinimal.clip_x = false mod.widgets.SliderMinimal.clip_y = false mod.widgets.SliderMinimal.draw_bg.vertex = fn() { let g = cb_grow(self.rect_pos, self.rect_size, 5.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } let cb_track = fn(p: vec2, x0: float, x1: float, cy: float, f0: float, f1: float, px: float, hw: float, thin: float, dis: float) -> vec4 { let ink = mix(cb_chalk_c(), cb_dim_c(), dis) let wy = cy + (Finish.noise(vec2(p.x * 0.03, 3.0)) - 0.5) * 0.7 let dl = cb_seg(p, vec2(x0 + thin, wy), vec2(x1 - thin, wy)) var out = cb_stroke(dl, thin, p, px, ink, mix(0.80, 0.4, dis)) if f1 - f0 > 0.01 { let a = min(f0 + hw, f1) let b = max(f1 - hw, a) let dt = cb_seg(p, vec2(a, wy), vec2(b, wy)) out = Finish.over(out, cb_stroke(dt, hw, p + vec2(11.0, 3.0), px, ink, mix(1.0, 0.5, dis))) } return out } // The thumb: the chalk disc, the track cleared under it. let cb_thumb = fn(under: vec4, p: vec2, kc: vec2, kr: float, px: float, hover: float, press: float, dis: float) -> vec4 { let clear = 1.0 - Finish.cover(length(p - kc) - kr + 0.6, px) return Finish.over(under * clear, cb_disc(p, kc, kr, px, cb_chalk_c(), 0.5, hover, press, dis, vec2(3.0, 17.0))) } mod.widgets.SliderFlat.height = 40.0 mod.widgets.SliderFlat.clip_x = false mod.widgets.SliderFlat.clip_y = false mod.widgets.SliderFlat.draw_bg.vertex = fn() { let g = cb_grow(self.rect_pos, self.rect_size, 5.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.SliderFlat.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 5.0 let size = self.rect_size let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let o = self.rect_pos let live = 1.0 - self.disabled let cy = Finish.snap(o.y + size.y - 10.0, px) - o.y let x0 = Finish.snap(o.x + 1.0, px) - o.x let x1 = Finish.snap(o.x + size.x - 1.0, px) - o.x let w = x1 - x0 let kr = 7.5 let span = w - 2.0 * kr let kx = x0 + kr + self.slide_pos * span let ox = x0 + kr + self.origin_pos * span let f0 = mix(x0, min(ox, kx), self.arc_origin) let f1 = mix(kx, max(ox, kx), self.arc_origin) var out = cb_track(p, x0, x1, cy, f0, f1, px, 1.7, 1.2, self.disabled) let kc = vec2(Finish.snap(o.x + kx, px) - o.x, cy) out = cb_thumb(out, p, kc, kr, px, max(self.hover, self.focus * 0.5), self.drag * live, self.disabled) return Finish.over(out, cb_focus(length(p - kc) - kr, p, px, self.focus * live)) } mod.widgets.SliderFlat.text_input.draw_text.text_style = mod.theme.font_label{font_size: 13.0} // The hand leans its last figure past its advance, and a field clips its // words to the width they were laid out in: a slider's value stands in a // box of its own 36 points wide, the figure set most of the way to its // right end, so its last figure has room and is never cut. mod.widgets.SliderFlat.text_input.width = 36.0 mod.widgets.SliderFlat.text_input.label_align = mod.turtle.Align{x: 0.8 y: 0.0} // The range slider: the heavy line between the two ends, and a thumb on // each end, rubbed fuller while it is the one held. mod.widgets.RangeSliderFlat.handle_size = 15.0 mod.widgets.RangeSliderFlat.track_inset = 1.0 mod.widgets.RangeSliderFlat.clip_x = false mod.widgets.RangeSliderFlat.clip_y = false mod.widgets.RangeSliderFlat.draw_bg.vertex = fn() { let g = cb_grow(self.rect_pos, self.rect_size, 5.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.RangeSliderFlat.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 5.0 let size = self.rect_size let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let o = self.rect_pos let live = 1.0 - self.disabled let top = self.label_px let cy = Finish.snap(o.y + top + (size.y - top) * 0.5, px) - o.y let x0 = Finish.snap(o.x + 1.0, px) - o.x let x1 = Finish.snap(o.x + size.x - 1.0, px) - o.x let span = max(size.x - self.inset_px * 2.0 - self.handle_px, 1.0) let sx = self.inset_px + self.handle_px * 0.5 + self.start_pos * span let ex = self.inset_px + self.handle_px * 0.5 + self.end_pos * span var out = cb_track(p, x0, x1, cy, sx, ex, px, 1.7, 1.2, self.disabled) let kr = self.handle_px * 0.5 let k0 = vec2(Finish.snap(o.x + sx, px) - o.x, cy) let k1 = vec2(Finish.snap(o.x + ex, px) - o.x, cy) out = cb_thumb(out, p, k0, kr, px, max(self.hot_start, self.focus * 0.5) * live, self.drag * self.hot_start * live, self.disabled) out = cb_thumb(out, p, k1, kr, px, max(self.hot_end, self.focus * 0.5) * live, self.drag * self.hot_end * live, self.disabled) return out } // The minimal slider: one thin chalk line, a heavier one over the value, // and a small thumb. mod.widgets.SliderMinimal.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 5.0 let size = self.rect_size let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let o = self.rect_pos let live = 1.0 - self.disabled let cy = Finish.snap(o.y + size.y - 8.0, px) - o.y let kr = 6.0 let kx = kr + self.slide_pos * (size.x - 2.0 * kr) var out = cb_track(p, 0.0, size.x, cy, 0.0, kx, px, 1.2, 0.8, self.disabled) let kc = vec2(Finish.snap(o.x + kx, px) - o.x, cy) out = cb_thumb(out, p, kc, kr, px, self.hover, self.drag * live, self.disabled) return Finish.over(out, cb_focus(length(p - kc) - kr, p, px, self.focus * live)) } mod.widgets.SliderMinimal.text_input.draw_text.text_style = mod.theme.font_label{font_size: 13.0} mod.widgets.SliderMinimal.text_input.width = 36.0 mod.widgets.SliderMinimal.text_input.label_align = mod.turtle.Align{x: 0.8 y: 0.0} // The round slider: a chalk pill beside its label, hatched in up to a small // solid chalk dot riding in it; the value reads beside the pill. mod.widgets.SliderRoundFlat.height = 22.0 mod.widgets.SliderRoundFlat.clip_x = false mod.widgets.SliderRoundFlat.clip_y = false mod.widgets.SliderRoundFlat.draw_bg.vertex = fn() { let g = cb_grow(self.rect_pos, self.rect_size, 5.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.SliderRoundFlat.text_input.margin = mod.turtle.Inset{left: 0.0 right: 8.0 top: 0.0 bottom: 0.0} mod.widgets.SliderRoundFlat.text_input.width = 34.0 mod.widgets.SliderRoundFlat.text_input.min_height = 0.0 mod.widgets.SliderRoundFlat.text_input.padding = mod.turtle.Inset{left: 0.0 right: 0.0 top: 0.0 bottom: 0.0} mod.widgets.SliderRoundFlat.text_input.draw_text.text_style = mod.theme.font_label{font_size: 13.0} mod.widgets.SliderRoundFlat.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 5.0 let size = self.rect_size let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let o = self.rect_pos let live = 1.0 - self.disabled let dis = self.disabled let x0 = Finish.snap(o.x + self.label_size, px) - o.x let y0 = Finish.snap(o.y + 1.0, px) - o.y let x1 = Finish.snap(o.x + size.x - 48.0, px) - o.x let y1 = Finish.snap(o.y + size.y - 1.0, px) - o.y let h = vec2((x1 - x0) * 0.5, (y1 - y0) * 0.5) let c = vec2(x0, y0) + h let d0 = cb_rbox(p, c, h, h.y) let fade = mix(1.0, 0.42, dis) let g = 2.6 let kr = h.y - g let kx = x0 + g + kr + self.slide_pos * (2.0 * h.x - 2.0 * g - 2.0 * kr) let fill = cb_rbox(p, vec2((x0 + g + kx) * 0.5, c.y), vec2((kx - x0 - g) * 0.5, h.y - g), h.y - g) var out = cb_hatch(max(fill, d0 + g), p + vec2(5.0, 9.0), px, cb_chalk_c(), 2.6, 0.8 * fade, 7.0) out = Finish.over(out, cb_outline(d0, p, px, 0.9 + 0.1 * self.hover * live, cb_chalk_c(), fade, vec2(5.0, 5.0))) let kc = vec2(Finish.snap(o.x + kx, px) - o.x, c.y) out = Finish.over(out, cb_disc(p, kc, kr, px, cb_chalk_c(), 0.95, self.hover, self.drag * live, dis, vec2(1.0, 4.0))) return Finish.over(out, cb_focus(d0, p, px, self.focus * live)) } // --------------------------------------------------------------------------- // FADERS. A narrow slot drawn as two chalk lines four points apart, a row // of chalk dots beside it at every step of the travel (a dash at each // quarter), and a cap drawn as a small chalk box hatched in, 22 points // across and 12 along the travel, a line drawn across its middle. The slot // stops at the cap. mod.widgets.SliderFader.cap_size = 12.0 mod.widgets.SliderFader.clip_x = false mod.widgets.SliderFader.clip_y = false mod.widgets.SliderFader.draw_bg.vertex = fn() { let g = cb_grow(self.rect_pos, self.rect_size, 5.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.SliderFader.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 5.0 let size = self.rect_size let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let o = self.rect_pos let live = 1.0 - self.disabled let dis = self.disabled let vert = self.is_vertical let a = mix(p.x, size.y - p.y, vert) let b = mix(p.y, p.x, vert) let along = mix(size.x, size.y, vert) let across = mix(size.y, size.x, vert) let travel = max(along - self.inset_px * 2.0 - self.cap_px, 1.0) let foot = self.inset_px + self.cap_px * 0.5 let mid = floor(across * 0.5 / px) * px let ink = mix(cb_chalk_c(), cb_dim_c(), dis) let fade = mix(1.0, 0.5, dis) // the slot: a narrow pill drawn in chalk let sw = 4.4 let sc = mix(vec2(along * 0.5, mid), vec2(mid, size.y - along * 0.5), vert) let sh = mix(vec2(along * 0.5 - self.inset_px, sw * 0.5), vec2(sw * 0.5, along * 0.5 - self.inset_px), vert) let sd = cb_rbox(p, sc, sh, sw * 0.5) var out = cb_stroke(abs(sd + 0.5 + cb_wob(p, 0.2, vec2(3.0, 3.0))), 0.6, p, px, ink, 0.8 * fade) // the dots beside it: one at every sixteenth of the travel, a short dash // at each quarter let nt = 16.0 let f = (a - foot) / travel * nt let fi = floor(f + 0.5) let ta = foot + fi / nt * travel let major = 1.0 - step(0.1, abs(fi * 0.25 - floor(fi * 0.25 + 0.5))) let in_rng = step(-0.5, fi) * step(fi, nt + 0.5) let off = abs(b - mid) - sw * 0.5 - 4.5 let tl = mix(0.0, 2.5, major) let dt = cb_seg(vec2(a, off), vec2(ta, 0.0), vec2(ta, tl)) out = Finish.over(out, cb_stroke(dt, 0.75, p, px, ink, in_rng * 0.85 * fade)) // the cap let at = foot + self.slide_pos * travel let cw = min(22.0, across) let cs = mix(vec2(at, mid), vec2(mid, size.y - at), vert) let ch = mix(vec2(self.cap_px * 0.5, cw * 0.5), vec2(cw * 0.5, self.cap_px * 0.5), vert) let press = self.drag * live let cc = vec2(Finish.snap(o.x + cs.x, px) - o.x, Finish.snap(o.y + cs.y, px) - o.y) let cd = cb_rbox(p, cc, ch, 2.5) out = out * (1.0 - Finish.cover(cd - 0.8, px)) out = Finish.over(out, cb_filled(cd + 1.6, p + vec2(5.0, 61.0), px, ink, (0.55 + 0.15 * max(self.hover, self.focus * 0.5) * live + 0.15 * press) * fade, 3.0)) out = Finish.over(out, cb_outline(cd, p, px, 0.9, ink, fade, vec2(7.0, 2.0))) let ld = mix(abs(p.y - cc.y), abs(p.x - cc.x), 1.0 - vert) let lspan = mix(abs(p.x - cc.x) - (ch.x - 2.5), abs(p.y - cc.y) - (ch.y - 2.5), 1.0 - vert) out = Finish.over(out, cb_stroke(max(ld, max(lspan, 0.0)), 0.75, p, px, cb_dark_c(), 0.9 * fade)) return Finish.over(out, cb_focus(cd, p, px, self.focus * live)) } // --------------------------------------------------------------------------- // KNOBS. A circle drawn in chalk with a heavy hand, gone round twice; its // face the board with a little dust rubbed and scribbled into it; a chalk // pointer from near its middle toward its rim, turned with the value. Round // it, ticks drawn on the board: the ones the value has passed in full // chalk, the rest half rubbed away. let cb_knob = fn(p: vec2, c: vec2, rad: float, px: float, ang: float, hover: float, press: float, dis: float) -> vec4 { let live = 1.0 - dis let q = p - c let d0 = length(q) - rad let ink = mix(cb_chalk_c(), cb_dim_c(), dis) let fade = mix(1.0, 0.55, dis) var out = cb_rub(d0 + 2.0, p + vec2(rad * 3.1, 17.0), px, ink, 0.16 + 0.08 * hover * live + 0.10 * press * live, fade * 0.8) out = Finish.over(out, cb_hatch(d0 + 2.5, p + vec2(rad * 1.7, 5.0), px, ink, 2.6, 0.30 * fade, rad)) let hw = max(rad * 0.10, 1.15) * (1.0 + 0.1 * hover * live) out = Finish.over(out, cb_outline(d0, p, px, hw, ink, fade, vec2(rad, 3.0))) out = Finish.over(out, cb_stroke(abs(d0 + hw * 3.0 + cb_wob(p, 0.3, vec2(1.0, rad))), hw * 0.55, p + vec2(9.0, 9.0), px, ink, 0.7 * fade)) let dir = vec2(0.0 - sin(ang), cos(ang)) let dp = cb_seg(p, c + dir * rad * 0.08, c + dir * rad * 0.78) out = Finish.over(out, cb_stroke(dp, max(rad * 0.085, 1.05), p + vec2(1.0, 5.0), px, ink, fade)) return out } // Ticks round a knob: `n` of them over the sweep from `r0` to `r1`, the // ends and the middle longer; those between `v0` and `v1` in full chalk. let cb_ticks = fn(p: vec2, c: vec2, r0: float, r1: float, gap: float, v0: float, v1: float, n: float, px: float, dis: float) -> vec4 { let start = gap * 0.5 let sweep = 2.0 * PI - gap let q = p - c var ang = atan2(0.0 - q.x, q.y) if ang < 0.0 { ang = ang + 2.0 * PI } let k = clamp(floor((ang - start) / sweep * (n - 1.0) + 0.5), 0.0, n - 1.0) let f = k / (n - 1.0) let ak = start + sweep * f let dir = vec2(0.0 - sin(ak), cos(ak)) let major = max(1.0 - step(0.5, k), max(step(n - 1.5, k), 1.0 - step(0.5, abs(k - (n - 1.0) * 0.5)))) let ri = mix(r0 + (r1 - r0) * 0.35, r0, major) let dl = cb_seg(p, c + dir * ri, c + dir * r1) let on = step(v0 - 0.001, f) * step(f, v1 + 0.001) let ink = mix(cb_chalk_c(), cb_dim_c(), dis) let amt = mix(0.45, 1.0, on) * mix(1.0, 0.55, dis) return cb_stroke(dl, 0.7, p, px, ink, amt) } // An arc drawn round a knob in chalk over the value, half rubbed away over // the rest; `ar` its middle radius. The gap is centred at the bottom and the // value runs clockwise from its left end, or from the origin when bipolar. let cb_arc = fn(p: vec2, c: vec2, ar: float, aw: float, px: float, v0: float, v1: float, gap: float, dis: float) -> vec4 { let start = gap * 0.5 let sweep = 2.0 * PI - gap let q = p - c var ang = atan2(0.0 - q.x, q.y) if ang < 0.0 { ang = ang + 2.0 * PI } let rd = abs(length(q) + cb_wob(p, 0.3, vec2(8.0, 8.0)) - ar) let in_arc = step(start, ang) * step(ang, start + sweep) let e0 = c + vec2(0.0 - sin(start), cos(start)) * ar let e1 = c + vec2(0.0 - sin(start + sweep), cos(start + sweep)) * ar let track_d = min(mix(1000.0, rd, in_arc), min(length(p - e0), length(p - e1))) let a0 = start + sweep * v0 let a1 = start + sweep * v1 let l0 = c + vec2(0.0 - sin(a0), cos(a0)) * ar let l1 = c + vec2(0.0 - sin(a1), cos(a1)) * ar let lit_in = step(a0, ang) * step(ang, a1) let lit_d = min(mix(1000.0, rd, lit_in), min(length(p - l0), length(p - l1))) let ink = mix(cb_chalk_c(), cb_dim_c(), dis) var out = cb_stroke(track_d, 0.6, p, px, ink, mix(0.45, 0.3, dis)) return Finish.over(out, cb_stroke(lit_d, aw * 0.5, p + vec2(3.0, 7.0), px, ink, mix(1.0, 0.55, dis))) } mod.widgets.RotaryKnob.clip_x = false mod.widgets.RotaryKnob.clip_y = false mod.widgets.RotaryKnob.draw_bg.vertex = fn() { let g = cb_grow(self.rect_pos, self.rect_size, 5.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.RotaryKnob.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 5.0 let size = self.rect_size let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let o = self.rect_pos let live = 1.0 - self.disabled let dia = min(size.x, size.y) let rr = dia * 0.5 - 0.5 let c = vec2(Finish.snap(o.x + size.x * 0.5, px) - o.x, Finish.snap(o.y + size.y - dia * 0.5, px) - o.y) let gap = self.gap * PI / 180.0 let rad = floor(rr * 0.74 / px) * px let lo = min(self.origin_pos, self.slide_pos) let hi = max(self.origin_pos, self.slide_pos) let v0 = mix(0.0, lo, self.arc_origin) let v1 = mix(self.slide_pos, hi, self.arc_origin) var out = cb_ticks(p, c, rad + 2.4, rr + 0.5, gap, v0, v1, 11.0, px, self.disabled) let ang = gap * 0.5 + (2.0 * PI - gap) * self.slide_pos out = Finish.over(out, cb_knob(p, c, rad, px, ang, max(self.hover, self.focus * 0.5), self.drag * live, self.disabled)) return Finish.over(out, cb_focus(length(p - c) - rr, p, px, self.focus * live)) } // Knob captions are chalk capitals. mod.widgets.RotaryKnob.draw_text.color = #e8eae6 mod.widgets.RotaryKnob.draw_text.text_style = mod.theme.font_label{font_size: 12.5} mod.widgets.RotaryFlat.clip_x = false mod.widgets.RotaryFlat.clip_y = false mod.widgets.RotaryFlat.draw_bg.vertex = fn() { let g = cb_grow(self.rect_pos, self.rect_size, 5.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.RotaryFlat.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 5.0 let size = self.rect_size let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let o = self.rect_pos let live = 1.0 - self.disabled let room = vec2(size.x, size.y - 24.0) let dia = min(room.x, room.y) let rr = dia * 0.5 - 3.5 let c = vec2(Finish.snap(o.x + size.x * 0.5, px) - o.x, Finish.snap(o.y + 24.0 + room.y * 0.5, px) - o.y) let gap = self.gap * PI / 180.0 let aw = 2.6 let ar = floor((rr - aw * 0.5) / px) * px let rad = floor(ar / 1.24 / px) * px let lo = min(self.origin_pos, self.slide_pos) let hi = max(self.origin_pos, self.slide_pos) let v0 = mix(0.0, lo, self.arc_origin) let v1 = mix(self.slide_pos, hi, self.arc_origin) var out = cb_arc(p, c, ar, aw, px, v0, v1, gap, self.disabled) let ang = gap * 0.5 + (2.0 * PI - gap) * self.slide_pos out = Finish.over(out, cb_knob(p, c, rad, px, ang, max(self.hover, self.focus * 0.5), self.drag * live, self.disabled)) return Finish.over(out, cb_focus(length(p - c) - ar - aw * 0.5, p, px, self.focus * live)) } // THE INSTRUMENT FRAME, one for the readouts, the screen, the meter and the // dial's value: a patch of the board kept clean (a shade darker than the // dusty board round it), framed by a box drawn in chalk, and a second, // fainter line drawn just inside the first. `face` is what the patch shows. // Premultiplied. let cb_window = fn(p: vec2, lo: vec2, hi: vec2, r: float, b: float, px: float, face: vec3, recess: float) -> vec4 { let c = (lo + hi) * 0.5 let ho = (hi - lo) * 0.5 let r_out = min(r, min(ho.x, ho.y)) let d0 = cb_rbox(p, c, ho, r_out) let a = Finish.cover(d0 + 0.5, px) var out = vec4(face * a, a) out = Finish.over(out, cb_outline(d0, p, px, 0.95, cb_chalk_c(), 0.95, ho * 0.37)) if recess > 0.0 { let di = d0 + b + 0.8 + cb_wob(p, 0.25, vec2(23.0, 1.0)) out = Finish.over(out, cb_stroke(abs(di), 0.45, p + vec2(5.0, 1.0), px, cb_chalk_c(), 0.38)) } return out } // The clean patch the instruments show on: the slate, a shade darker and // calmer than the board. let cb_screen_face = fn(p: vec2, px: float, base: vec3) -> vec3 { return cb_slate(p + vec2(31.0, 7.0), px, 0.7) * (base / cb_slate_c()) } // The dial's value: the instrument frame in small, the figure in chalk. mod.widgets.RotaryFlat.text_input.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 2.0 bottom: 2.0} mod.widgets.RotaryFlat.text_input.draw_bg.vertex = fn() { self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size) } mod.widgets.RotaryFlat.text_input.min_height = 22.0 mod.widgets.RotaryFlat.text_input.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * self.rect_size let o = self.rect_pos let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o + vec2(0.5, 0.5) let hi = vec2(Finish.snap(o.x + self.rect_size.x, px), Finish.snap(o.y + self.rect_size.y, px)) - o - vec2(0.5, 1.0) return cb_window(p, lo, hi, 5.0, 2.2, px, cb_screen_face(p, px, vec3(0.110, 0.125, 0.118)), 0.0) } mod.widgets.RotaryFlat.text_input.draw_text.color = #e8eae6 mod.widgets.RotaryFlat.text_input.draw_text.color_hover = #f8f9f6 mod.widgets.RotaryFlat.text_input.draw_text.color_focus = #f3e3a0 mod.widgets.RotaryFlat.text_input.draw_text.color_empty = #7d827f mod.widgets.RotaryFlat.text_input.draw_text.text_style = mod.theme.font_label{font_size: 13.0} // --------------------------------------------------------------------------- // TEXT FIELDS. A box drawn in chalk round the board where one writes. Under // the pointer the line is a shade brighter; focused, a light blue chalk // line runs just inside it; disabled, the box is half rubbed away and // faintly hatched over. `p` is the point in the grown quad. let cb_field = fn(p: vec2, size: vec2, o: vec2, px: float, hover: float, focus: float, dis: float, r: float) -> vec4 { let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o let hi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y, px)) - o let h = (hi - lo) * 0.5 let c = lo + h let seed = size * vec2(0.061, 0.17) let d0 = cb_rbox(p, c, h, r) var out = cb_rub(d0 + 1.5, p, px, cb_chalk_c(), 0.05 * hover * (1.0 - dis), 1.0) out = Finish.over(out, cb_outline(d0, p, px, 0.9 + 0.1 * hover, mix(cb_chalk_c(), cb_dim_c(), dis), mix(0.85 + 0.15 * hover, 0.55, dis), seed)) out = Finish.over(out, cb_hatch(d0 + 2.2, p + seed, px, cb_chalk_c(), 3.0, 0.16 * dis, seed.x)) let fd = d0 + 3.6 + cb_wob(p, 0.25, vec2(1.0, 21.0)) return Finish.over(out, cb_stroke(abs(fd), 0.5, p, px, cb_blue_c(), 0.85 * focus)) } mod.widgets.TextInputFlat.padding = mod.turtle.Inset{left: 10.0 right: 10.0 top: 4.0 bottom: 4.0} mod.widgets.TextInputFlat.clip_x = false mod.widgets.TextInputFlat.clip_y = false mod.widgets.TextInputFlat.draw_bg.vertex = fn() { let g = cb_grow(self.rect_pos, self.rect_size, 5.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.TextInputFlat.draw_bg.pixel = fn() { // The value field inside a slider or a well draws no box of its own. if self.color.a < 0.01 { return vec4(0.0, 0.0, 0.0, 0.0) } let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 5.0 let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) return cb_field(p, self.rect_size, self.rect_pos, px, self.hover, self.focus, self.disabled, 6.0) } mod.widgets.FieldWell.draw_bg.vertex = fn() { let g = cb_grow(self.rect_pos, self.rect_size, 5.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.FieldWell.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 5.0 let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) return cb_field(p, self.rect_size, self.rect_pos, px, self.hover, self.focus, self.disabled, 6.0) } mod.widgets.TextInputFlat.draw_text.color_empty = #a2a7a4 mod.widgets.TextInputFlat.draw_text.color_disabled = #7d827f mod.widgets.TextInputFlat.draw_cursor.color = #f3e3a0 // NUMBER FIELDS. The field, and on its right a stepper column set off by a // chalk line, its two halves each with a chevron drawn in chalk; the half // under the pointer takes a breath of dust, the held half more. mod.widgets.NumberField.clip_x = false mod.widgets.NumberField.clip_y = false mod.widgets.NumberField.draw_bg.vertex = fn() { let g = cb_grow(self.rect_pos, self.rect_size, 5.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.NumberField.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 5.0 let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) return cb_field(p, self.rect_size, self.rect_pos, px, self.hover, self.focus, self.disabled, 6.0) } mod.widgets.NumberField.pad_left = 10.0 mod.widgets.NumberSpin.width = 20.0 mod.widgets.NumberSpin.draw_text.color = #00000000 mod.widgets.NumberSpin.draw_text.color_disabled = #00000000 mod.widgets.NumberSpin.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * self.rect_size let o = self.rect_pos let live = 1.0 - self.disabled let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y + 3.5, px)) - o let hi = vec2(Finish.snap(o.x + self.rect_size.x - 3.5, px), Finish.snap(o.y + self.rect_size.y - 3.5, px)) - o let mid = (lo.y + hi.y) * 0.5 let upper = step(p.y, mid) let hot = mix(max(0.0, 0.0 - self.hot), max(0.0, self.hot), upper) * live let dn = mix(max(0.0, 0.0 - self.down), max(0.0, self.down), upper) * live let box = max(max(lo.x - p.x, p.x - hi.x), max(lo.y - p.y, p.y - hi.y)) var out = cb_rub(box, p, px, cb_chalk_c(), 0.12 * hot + 0.25 * dn, 1.0) let ink = mix(cb_chalk_c(), cb_dim_c(), self.disabled) let fade = mix(1.0, 0.6, self.disabled) let dv = abs(p.x - lo.x - 0.6 + cb_wob(p, 0.2, vec2(3.0, 3.0))) let lv = max(dv, max(lo.y + 0.5 - p.y, p.y - hi.y + 0.5)) out = Finish.over(out, cb_stroke(max(lv, 0.0), 0.75, p, px, ink, fade)) let cx = (lo.x + hi.x) * 0.5 + 0.8 let yu = (lo.y + mid) * 0.5 + 0.5 let yd = (mid + hi.y) * 0.5 - 0.5 let cu = min(cb_seg(p, vec2(cx - 3.6, yu + 1.9), vec2(cx, yu - 1.7)), cb_seg(p, vec2(cx, yu - 1.7), vec2(cx + 3.6, yu + 1.9))) let cdn = min(cb_seg(p, vec2(cx - 3.6, yd - 1.9), vec2(cx, yd + 1.7)), cb_seg(p, vec2(cx, yd + 1.7), vec2(cx + 3.6, yd - 1.9))) return Finish.over(out, cb_stroke(min(cu, cdn), 0.75, p, px, ink, fade)) } // The drag-number: the field, its track a breath of dust behind the digits. mod.widgets.ValueInput.draw_bg.vertex = fn() { let g = cb_grow(self.rect_pos, self.rect_size, 5.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.ValueInput.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 5.0 let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) var out = cb_field(p, self.rect_size, self.rect_pos, px, max(self.hover, self.drag), self.focus, 0.0, 6.0) let d = cb_rbox(p, self.rect_size * 0.5, self.rect_size * 0.5 - vec2(3.5, 3.5), 3.0) let wx = 3.5 + (self.rect_size.x - 7.0) * max(self.fill, 0.0) let inside = max(d, p.x - wx) + (1.0 - step(0.0, self.fill)) * 100.0 return Finish.over(out, cb_rub(inside, p, px, cb_chalk_c(), 0.16, 1.0)) } mod.widgets.ValueInput.draw_text.color = #e8eae6 mod.widgets.ValueInput.draw_text.text_style = mod.theme.font_label{font_size: 13.5 line_spacing: 1.0} mod.widgets.ValueInput.arrow_color = #e8eae6 mod.widgets.ValueInput.text_input.draw_bg.color = #00000000 // --------------------------------------------------------------------------- // DROP-DOWNS. The button's chalk box, with a chevron drawn in chalk on its // right. Open, the box is held pressed, the hand gone round it twice. let cb_dropdown = fn(pos: vec2, size: vec2, o: vec2, px: float, hover: float, held: float, focus: float, dis: float, ins: float) -> vec4 { let m = 5.0 let p = pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) var out = cb_button(pos, size, o, px, hover, held, focus, dis, 0.0, 0.0, vec4(8.0, 8.0, 8.0, 8.0), 0.0, m, vec4(0.0, 0.0, 0.0, 0.0), 0.0) let cc = vec2(size.x - 15.0, size.y * 0.5 + 0.3) let wb = cb_wob(p, 0.2, vec2(2.0, 2.0)) let chev = min(cb_seg(p, cc + vec2(-4.4, -2.2), cc + vec2(0.0, 2.2)), cb_seg(p, cc + vec2(0.0, 2.2), cc + vec2(4.4, -2.2))) + wb return Finish.over(out, cb_stroke(max(chev, 0.0), 0.85, p, px, mix(cb_chalk_c(), cb_dim_c(), dis), mix(1.0, 0.6, dis))) } mod.widgets.DropDownFlat.padding = mod.turtle.Inset{left: 12.0 right: 30.0 top: 4.0 bottom: 4.0} mod.widgets.DropDown.padding = mod.turtle.Inset{left: 12.0 right: 30.0 top: 4.0 bottom: 4.0} mod.widgets.DropDownFlat.clip_x = false mod.widgets.DropDownFlat.clip_y = false mod.widgets.DropDownFlat.draw_bg.vertex = fn() { let g = cb_grow(self.rect_pos, self.rect_size, 5.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.DropDownFlat.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return cb_dropdown(self.pos, self.rect_size, self.rect_pos, px, self.hover, max(self.down, self.active), self.focus, self.disabled, 0.0) } // The select wears a button of its own, built before this sheet: it is given // the drop-down's face, its grown quad, and no clip, here by name. mod.widgets.Select.clip_x = false mod.widgets.Select.clip_y = false mod.widgets.MultiSelect.clip_x = false mod.widgets.MultiSelect.clip_y = false mod.widgets.Select.face.clip_x = false mod.widgets.Select.face.clip_y = false mod.widgets.MultiSelect.face.clip_x = false mod.widgets.MultiSelect.face.clip_y = false mod.widgets.Select.face.draw_bg.vertex = fn() { let g = cb_grow(self.rect_pos, self.rect_size, 5.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.MultiSelect.face.draw_bg.vertex = fn() { let g = cb_grow(self.rect_pos, self.rect_size, 5.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.Select.face.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return cb_dropdown(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, 0.0) } mod.widgets.MultiSelect.face.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return cb_dropdown(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, 0.0) } mod.widgets.Select.face.padding.right = 30.0 mod.widgets.MultiSelect.face.padding.right = 30.0 // The combo box: the field one types into, its chevron drawn in chalk at // the right end; the chevron sits down a device pixel while held. mod.widgets.ComboBox.padding = mod.turtle.Inset{left: 10.0 right: 30.0 top: 4.0 bottom: 4.0} mod.widgets.ComboBox.input.min_height = 0.0 mod.widgets.ComboBox.clip_x = false mod.widgets.ComboBox.clip_y = false mod.widgets.ComboBox.draw_bg.vertex = fn() { let g = cb_grow(self.rect_pos, self.rect_size, 5.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.ComboBox.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 5.0 let size = self.rect_size let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let live = 1.0 - self.disabled var out = cb_field(p, size, self.rect_pos, px, self.hover, self.focus, self.disabled, 6.0) let held = self.down * self.hover * live let cc = vec2(size.x - 15.0, size.y * 0.5 + held * px + 0.3) let chev = min(cb_seg(p, cc + vec2(-4.4, -2.2), cc + vec2(0.0, 2.2)), cb_seg(p, cc + vec2(0.0, 2.2), cc + vec2(4.4, -2.2))) return Finish.over(out, cb_stroke(chev, 0.85, p, px, mix(cb_chalk_c(), cb_dim_c(), self.disabled), mix(1.0, 0.6, self.disabled))) } // --------------------------------------------------------------------------- // SEGMENTS AND TABS. The strip is one box drawn in chalk; the chosen segment // is a smaller box inside it rubbed full of chalk, its word in the board's // dark, so it reads as the one pressed in. let cb_strip = fn(p: vec2, size: vec2, o: vec2, px: float) -> vec4 { let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o let hi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y, px)) - o let h = (hi - lo) * 0.5 let c = lo + h return cb_outline(cb_rbox(p, c, h, 9.0), p, px, 0.95, cb_chalk_c(), 0.9, size * vec2(0.11, 0.07)) } // The chosen cell: the chalk field and its own line. `lo` and `hi` are the // cell's corners in the quad's own points. let cb_cell = fn(p: vec2, lo: vec2, hi: vec2, px: float) -> vec4 { let c = (lo + hi) * 0.5 let h = (hi - lo) * 0.5 let d0 = cb_rbox(p, c, h, 7.0) var out = cb_filled(d0 + 1.0, p + hi * 1.7, px, cb_chalk_c(), 1.0, hi.x) return Finish.over(out, cb_outline(d0, p, px, 0.8, cb_chalk_c(), 0.95, hi * 0.21)) } mod.widgets.SegmentedControlFlat.segment_height = 28.0 mod.widgets.SegmentedControlFlat.segment_padding = 12.0 mod.widgets.SegmentedControlFlat.draw_bg.vertex = fn() { let g = cb_grow(self.rect_pos, self.rect_size, 5.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.SegmentedControlFlat.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 5.0 let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) return cb_strip(p, self.rect_size, self.rect_pos, px) } // The pill is the segment's own box, inside the strip's line; the chalk field // stands two points further in. Its quad grows two points round it for the // chalk's dust. mod.widgets.SegmentedControlFlat.draw_pill.vertex = fn() { self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(2.0, 2.0), self.rect_size + vec2(4.0, 4.0)) } mod.widgets.SegmentedControlFlat.draw_pill.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let o = self.rect_pos let size = self.rect_size let p = self.pos * (size + vec2(4.0, 4.0)) - vec2(2.0, 2.0) let lo = vec2(Finish.snap(o.x + 2.0, px), Finish.snap(o.y + 2.0, px)) - o let hi = vec2(Finish.snap(o.x + size.x - 2.0, px), Finish.snap(o.y + size.y - 2.0, px)) - o return cb_cell(p, lo, hi, px) } mod.widgets.SegmentedControlFlat.draw_text.color = #e8eae6 mod.widgets.SegmentedControlFlat.draw_text_selected.color = #1d2120 mod.widgets.SegmentedControlFlat.palette.on_neutral_container = #1d2120 mod.widgets.SegmentedControlFlat.draw_text.text_style = mod.theme.font_label{} mod.widgets.SegmentedControlFlat.draw_text_selected.text_style = mod.theme.font_label{} // The tab strip: the tabs are their words written on the board; the tab in // use is underlined in chalk, a hovered one faintly. The close mark is a // chalk cross; the add mark a plus in a chalk ring. let cb_tab = fn(pos: vec2, size: vec2, o: vec2, px: float, kind: float, hover: float) -> vec4 { let m = 5.0 let p = pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let lo = vec2(Finish.snap(o.x + 6.0, px), Finish.snap(o.y, px)) - o let hi = vec2(Finish.snap(o.x + size.x - 6.0, px), Finish.snap(o.y + size.y, px)) - o let uy = hi.y - 4.5 + cb_wob(p, 0.4, vec2(size.x * 0.1, 3.0)) let du = cb_seg(p, vec2(lo.x, uy), vec2(hi.x, uy - 0.6)) var out = cb_stroke(du, 0.6, p, px, cb_chalk_c(), 0.4 * hover) return Finish.over(out, cb_stroke(du, 1.05, p + vec2(7.0, 3.0), px, cb_chalk_c(), step(0.5, kind))) } mod.widgets.Tabs.height = 30.0 mod.widgets.Tabs.clip_x = false mod.widgets.Tabs.clip_y = false mod.widgets.Tabs.draw_bg.pixel = fn() { return vec4(0.0, 0.0, 0.0, 0.0) } mod.widgets.Tabs.draw_tab.vertex = fn() { let g = cb_grow(self.rect_pos, self.rect_size, 5.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.Tabs.draw_tab.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return cb_tab(self.pos, self.rect_size, self.rect_pos, px, 0.0, 0.0) } mod.widgets.Tabs.draw_tab_hover.vertex = fn() { let g = cb_grow(self.rect_pos, self.rect_size, 5.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.Tabs.draw_tab_hover.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return cb_tab(self.pos, self.rect_size, self.rect_pos, px, 0.0, 1.0) } mod.widgets.Tabs.draw_tab_active.vertex = fn() { let g = cb_grow(self.rect_pos, self.rect_size, 5.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.Tabs.draw_tab_active.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return cb_tab(self.pos, self.rect_size, self.rect_pos, px, 1.0, 0.0) } mod.widgets.Tabs.draw_sep.pixel = fn() { return vec4(0.0, 0.0, 0.0, 0.0) } mod.widgets.Tabs.draw_mark.color = #e8eae6 mod.widgets.Tabs.draw_mark.vertex = fn() { let g = cb_grow(self.rect_pos, self.rect_size, 6.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.Tabs.draw_mark.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 6.0 let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let c = self.rect_size * 0.5 var out = vec4(0.0, 0.0, 0.0, 0.0) if self.plus > 0.5 { let rr = min(self.rect_size.x, self.rect_size.y) * 0.5 + 1.0 out = cb_outline(length(p - c) - rr, p, px, 0.85, self.color.rgb, self.color.a, vec2(23.0, 7.0)) } let a = mix(0.7853981634, 0.0, self.plus) let r = mix(3.4, 4.6, self.plus) let u = vec2(cos(a), sin(a)) * r let v = vec2(0.0 - sin(a), cos(a)) * r let dd = min(cb_seg(p, c - u, c + u), cb_seg(p, c - v, c + v)) return Finish.over(out, cb_stroke(dd, mix(0.7, 0.85, self.plus), p, px, self.color.rgb, self.color.a)) } mod.widgets.Tabs.draw_text.color = #c4c8c5 mod.widgets.Tabs.draw_text_active.color = #f8f9f6 mod.widgets.Tabs.draw_text.text_style = mod.theme.font_label{font_size: 13.5} mod.widgets.Tabs.draw_text_active.text_style = mod.theme.font_label{font_size: 13.5} // --------------------------------------------------------------------------- // PROGRESS. A thin chalk line the length of the bar, and over the value a // broad band drawn round in chalk and rubbed and hatched full (a coloured // chalk for an intent). The unknown value is a row of short upright // capsules drawn in chalk along the whole bar: a face may not run a clock. mod.widgets.ProgressBarFlat.height = 9.0 mod.widgets.ProgressBarFlat.draw_bg.thickness = uniform(8.0) mod.widgets.ProgressBarFlat.clip_x = false mod.widgets.ProgressBarFlat.clip_y = false mod.widgets.ProgressBarFlat.draw_bg.vertex = fn() { let g = cb_grow(self.rect_pos, self.rect_size, 3.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.ProgressBarFlat.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 3.0 let size = self.rect_size let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let th = min(self.thickness, size.y) let cy = size.y * 0.5 let x0 = size.x * self.start let x1 = size.x * self.end let dis = step(4.5, self.intent) var ink = cb_chalk_c() if self.intent > 0.5 { ink = mix(self.fill_color().rgb, cb_chalk_c(), 0.35) } ink = mix(ink, cb_dim_c(), dis) let wy = cy + (Finish.noise(vec2(p.x * 0.04, 7.0)) - 0.5) * 0.6 let hw = th * 0.5 - 0.3 var out = cb_stroke(cb_seg(p, vec2(x0 + 0.8, wy), vec2(x1 - 0.8, wy)), 0.75, p, px, cb_chalk_c(), 0.6 * self.track * step(0.0, self.value)) if self.value < 0.0 { let u = (p.x - x0) / 7.0 let k = floor(u) let sx = x0 + (k + 0.5) * 7.0 + (Finish.hash(vec2(k, 3.0)) - 0.5) * 0.8 let ds = cb_rbox(p, vec2(sx, wy), vec2(1.3, hw), 1.3) let inr = step(x0 + 1.0, sx) * step(sx, x1 - 1.0) out = Finish.over(out, cb_rub(ds + 0.6, p, px, ink, 0.45, inr)) out = Finish.over(out, cb_outline(ds, p, px, 0.55, ink, inr, vec2(k, 1.0))) } else { let v1 = x0 + (x1 - x0) * clamp(self.value, 0.0, 1.0) if v1 - x0 > 0.5 { let bc = vec2((x0 + v1) * 0.5, wy) let bh = vec2((v1 - x0) * 0.5, hw) let bd = cb_rbox(p, bc, bh, hw) out = out * (1.0 - Finish.cover(bd, px)) out = Finish.over(out, cb_filled(bd + 1.0, p + vec2(3.0, 11.0), px, ink, 1.0, 2.0)) out = Finish.over(out, cb_outline(bd, p, px, 0.65, ink, 1.0, vec2(5.0, 3.0))) } } return out * self.opacity } // --------------------------------------------------------------------------- // LAMPS: an LED set into the board, ringed round in chalk. The emitter is the // light: at full current its middle burns toward white and its rim is its // most saturated colour; at lower current the middle is no longer white, // the colour deeper, the glow smaller; out, it is dark tinted glass, its // colour barely there. Round it a dark socket, its upper wall in shade and // a catch light on its lower rim, and round that a ring drawn in chalk. // Over the lens one crisp highlight of a dome. Its light is added, never // painted: a tight hot halo and a short bloom in its own hue, screened over // the board and gone in about two radii. let cb_led_add = fn(res: vec4, light: vec3, ground: vec3) -> vec4 { let est = res.rgb + ground * (1.0 - res.a) let add = (vec3(1.0, 1.0, 1.0) - exp(0.0 - light)) * max(vec3(1.0, 1.0, 1.0) - est, vec3(0.0, 0.0, 0.0)) return vec4(res.rgb + add, res.a) } let cb_led = fn(p: vec2, c: vec2, hb: vec2, lit: float, ink: vec3, vivid: float, px: float, room: float) -> vec4 { let rt = max(min(hb.x, hb.y), 0.4) let d = Material.sd_box(p, c, hb, rt) let bw = clamp(0.42 * rt, 1.0, 2.4) let ds = d - bw let L = clamp(lit, 0.0, 1.0) let b = ink / max(max(ink.x, max(ink.y, ink.z)), 0.01) let mn = min(b.x, min(b.y, b.z)) let pure = (b - vec3(mn, mn, mn)) / max(1.0 - mn, 0.001) let hue = mix(b, pure, clamp(vivid, 0.0, 1.0) * 0.72 * smoothstep(0.03, 0.12, 1.0 - mn)) let pw = L * sqrt(L) let white = vec3(1.0, 1.0, 1.0) let ground = cb_slate_c() var res = vec4(0.0, 0.0, 0.0, 0.0) let ex = max(hb.x - hb.y, 0.0) let ey = max(hb.y - hb.x, 0.0) let qx = p.x - c.x let qy = p.y - c.y let q = vec2(qx - clamp(qx, 0.0 - ex, ex), qy - clamp(qy, 0.0 - ey, ey)) / rt let t = clamp(length(q), 0.0, 1.0) let nrm = q / max(length(q), 0.001) // the ring drawn round it in chalk res = Finish.over(res, cb_stroke(abs(ds - 1.8 + cb_wob(p, 0.25, c * 0.1)), 0.68, p + c, px, cb_chalk_c(), 0.92)) // the socket: the hole's wall, shade above and a catch light below let a_s = Finish.cover(ds, px) let gy = clamp(nrm.y * t, -1.0, 1.0) var sc = vec3(0.060, 0.068, 0.064) * mix(0.55, 1.3, smoothstep(-0.9, 0.9, gy)) sc = mix(sc, vec3(0.42, 0.44, 0.43), Finish.band(ds, 0.0, max(px, 0.45), px) * smoothstep(0.2, 0.95, nrm.y) * 0.7) res = Finish.over(res, vec4(sc * a_s, a_s)) // the light: a tight hot halo and a short bloom in its hue if L > 0.001 { let xl = max(d, 0.0) let halo = exp(0.0 - xl / max(0.30 * rt, 0.4)) * 1.5 + exp(0.0 - xl / max(0.65 * rt, 0.7)) * 0.32 let hwin = 1.0 - smoothstep(0.55 * room, room, xl) res = cb_led_add(res, mix(hue, white, 0.15 * pw) * halo * pw * hwin, ground) } // the bead: a glossy dome of coloured glass let top = smoothstep(-1.0, 0.6, q.y) let pool = exp(0.0 - (q.y - 0.55) * (q.y - 0.55) / 0.09 - q.x * q.x / 0.30) var on = mix(hue * mix(0.55, 0.8, L), mix(b, hue, 0.5), top) on = mix(on, min(mix(b, hue, 0.4) + vec3(0.35, 0.35, 0.35), white), pool * 0.5) on = mix(on, vec3(1.0, 0.99, 0.975), exp(0.0 - t * t / 0.20) * smoothstep(0.5, 1.0, L) * 0.85) on = mix(on, hue * (0.82 + 0.18 * L), smoothstep(0.62, 1.0, t)) on = on * (0.42 + 0.58 * L) let offc = mix(vec3(0.075, 0.078, 0.076), b * 0.45, 0.24) var off = mix(offc * 0.62, offc * 1.08, top) off = mix(off, min(offc + vec3(0.18, 0.18, 0.18), white), pool * 0.35) off = off * mix(1.0, 0.75, smoothstep(0.75, 1.0, t)) let catchl = smoothstep(0.62, 0.9, t) * (1.0 - smoothstep(0.9, 1.0, t)) * smoothstep(0.2, 0.9, nrm.y) off = off + vec3(0.30, 0.30, 0.32) * catchl var col = mix(off, min(on, white), smoothstep(0.0, 0.35, L)) let e = length(vec2(q.x / 0.62, (q.y + 0.46) / 0.30)) let spec = (1.0 - smoothstep(0.55, 1.0, e)) * mix(1.0, 0.3, clamp((q.y + 0.76) / 0.6, 0.0, 1.0)) col = mix(col, white, spec * mix(0.8, 0.55, L) * smoothstep(0.9, 2.2, rt)) col = mix(col, col * 0.7, Finish.band(d, 0.0, px * 0.6, px) * 0.6 * (1.0 - smoothstep(0.0, 0.25, L))) col = min(col, white) let a_l = Finish.cover(d, px) return Finish.over(res, vec4(col * a_l, a_l)) } // --------------------------------------------------------------------------- // LEVEL METERS. A row of small boxes drawn in chalk, the lit ones rubbed // full and the last tenth in pink chalk, the unlit ones only drawn round, // half rubbed away; the hold is a stroke drawn across; the over lamp an LED // ringed in chalk. On a display (a meter with no lamp lying along a screen) // the boxes are the display's pale yellow chalk, the unlit ones a smudge. mod.widgets.LevelMeter.height = 9.0 mod.widgets.LevelMeterColumn.width = 9.0 mod.widgets.LevelMeter.draw_bg.vertex = fn() { let g = cb_grow(self.rect_pos, self.rect_size, 3.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.LevelMeter.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * (self.rect_size + vec2(6.0, 6.0)) - vec2(3.0, 3.0) let vert = self.vertical let along = mix(self.rect_size.x, self.rect_size.y, vert) let across = mix(self.rect_size.y, self.rect_size.x, vert) let a = mix(p.x, self.rect_size.y - p.y, vert) let b = mix(p.y, p.x, vert) let screen = (1.0 - step(0.5, self.lamp)) * (1.0 - step(0.5, vert)) let th = min(8.0, across - 1.0) let lamp_room = self.lamp * (th + 6.0) let bar = max(4.0, along - lamp_room) let pitch = mix(5.5, 4.0, screen) let cw = mix(3.4, 2.4, screen) let n = max(floor((bar - 1.0) / pitch), 1.0) let pad = (bar - n * pitch) * 0.5 let k = clamp(floor((a - pad) / pitch), 0.0, n - 1.0) let ca = pad + (k + 0.5) * pitch let share = (k + 0.5) / n let lit = step(share, self.level + 0.5 / n) * step(0.001, self.level) let q = vec2(a, b) let hh = th * 0.5 - mix(0.5, 1.0, screen) let dd = cb_rbox(q, vec2(ca, across * 0.5), vec2(cw * 0.5, hh), 1.0) let hot = step(0.9, share) var ink = mix(cb_chalk_c(), cb_pink_c(), hot) ink = mix(ink, cb_yellow_c(), screen) var out = cb_rub(dd, p, px, ink, 0.85, lit) out = Finish.over(out, cb_stroke(abs(dd + 0.35), 0.4, p + vec2(k * 7.0, 0.0), px, ink, mix(mix(0.45, 0.85, lit), mix(0.12, 0.9, lit), screen))) if self.hold > 0.002 { let at = pad + n * pitch * self.hold let hd = cb_seg(q, vec2(at, across * 0.5 - hh - 1.0), vec2(at, across * 0.5 + hh + 1.0)) out = Finish.over(out, cb_stroke(hd, 0.75, p, px, ink, 1.0)) } if self.lamp > 0.5 { let la = bar + 3.0 + th * 0.5 let lc = vec2(mix(la, across * 0.5, vert), mix(across * 0.5, self.rect_size.y - la, vert)) out = Finish.over(out, cb_led(p, lc, vec2(2.4, 2.4), step(0.5, self.over), vec3(0.95, 0.20, 0.12), 1.0, px, 2.2)) } return out * self.opacity } // --------------------------------------------------------------------------- // SCROLL BARS. A faint chalk line down the bar, and riding on it a narrow // band drawn round in chalk and rubbed in; brighter under the hand. mod.widgets.ScrollBar.bar_size = 10.0 mod.widgets.ScrollBar.bar_side_margin = 0.0 mod.widgets.ScrollBar.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * self.rect_size let vert = self.is_vertical let along = mix(self.rect_size.x, self.rect_size.y, vert) let sc = self.rect_size * 0.5 let sh = mix(vec2(along * 0.5 - 2.0, 0.0), vec2(0.0, along * 0.5 - 2.0), vert) let sd = cb_rbox(p, sc, sh, 0.0) var out = cb_stroke(abs(sd), 0.45, p, px, cb_chalk_c(), 0.30) let t0 = along * self.norm_scroll let tl = along * self.norm_handle let tc1 = t0 + tl * 0.5 let tc = mix(vec2(tc1, sc.y), vec2(sc.x, tc1), vert) let th = mix(vec2(max(tl * 0.5 - 1.5, 4.0), 2.8), vec2(2.8, max(tl * 0.5 - 1.5, 4.0)), vert) let td = cb_rbox(p, tc, th, 2.8) out = out * (1.0 - Finish.cover(td, px)) let lift = 0.10 * self.hover + 0.12 * self.drag out = Finish.over(out, cb_rub(td + 0.8, p, px, cb_chalk_c(), 0.35 + lift, 1.0)) out = Finish.over(out, cb_outline(td, p, px, 0.55, cb_chalk_c(), 0.75 + lift * 2.0, vec2(3.0, 9.0))) return out * self.opacity } // --------------------------------------------------------------------------- // VIEWS AND PANELS. A raised panel is a box drawn round in chalk, a breath // of dust rubbed over its inside. The sunken panel is drawn as a frame in a // frame: its box, and a second box drawn three points inside the first, the // board inside them a shade darker where no dust has settled. let cb_plate = fn(p: vec2, size: vec2, o: vec2, px: float, r: float, sunken: float) -> vec4 { let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o let hi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y, px)) - o let h = (hi - lo) * 0.5 let c = lo + h let seed = size * vec2(0.073, 0.119) let d0 = cb_rbox(p, c, h, r) if sunken > 0.5 { let a = Finish.cover(d0 + 3.0, px) * 0.32 var out = vec4(0.0, 0.0, 0.0, a) out = Finish.over(out, cb_outline(d0, p, px, 0.9, cb_chalk_c(), 0.85, seed)) let di = cb_rbox(p, c, h - vec2(3.2, 3.2), max(r - 2.5, 1.0)) return Finish.over(out, cb_outline(di, p, px, 0.6, cb_chalk_c(), 0.55, seed + vec2(3.0, 7.0))) } var out = cb_rub(d0 + 2.0, p + seed, px, cb_chalk_c(), 0.05, 1.0) return Finish.over(out, cb_outline(d0, p, px, 0.95, cb_chalk_c(), 0.88, seed)) } mod.widgets.PanelView.clip_x = false mod.widgets.PanelView.clip_y = false mod.widgets.PanelView.draw_bg.vertex = fn() { let g = cb_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) return cb_plate(p, self.rect_size, self.rect_pos, px, 8.0, self.sunken) } // The card is a box drawn in chalk with its top right corner folded down, // the fold drawn in and hatched, as a note card is drawn on a board; under // the pointer its line is a shade brighter. mod.widgets.Card.radius = 6.0 mod.widgets.Card.clip_x = false mod.widgets.Card.clip_y = false mod.widgets.Card.draw_bg.vertex = fn() { let g = cb_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 size = self.rect_size let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let o = self.rect_pos let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o let hi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y, px)) - o let h = (hi - lo) * 0.5 let c = lo + h let f = 13.0 // the box with its corner cut, and the folded flap let box = cb_rbox(p, c, h, 6.0) let cut = (p.x - (hi.x - f)) - (p.y - lo.y) let d0 = max(box, cut * 0.7071) let flap = max(max(hi.x - f - p.x, p.y - (lo.y + f)), cut * 0.7071) let lift = 0.88 + 0.12 * self.hover - 0.1 * self.down var out = cb_rub(d0 + 2.0, p + size * 0.37, px, cb_chalk_c(), 0.04 + 0.04 * self.hover, 1.0) out = Finish.over(out, cb_hatch(flap + 1.0, p, px, cb_chalk_c(), 2.4, 0.6, 9.0)) out = Finish.over(out, cb_outline(d0, p, px, 0.95, cb_chalk_c(), lift, size * 0.05)) return Finish.over(out, cb_outline(flap, p, px, 0.75, cb_chalk_c(), lift * 0.9, size * 0.03)) } // The plain rounded view keeps the colour its caller gives it, laid as a // patch of that colour with the slate's grain in it; the edge a chalk line // where the caller asks for one. mod.widgets.RoundedView.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * self.rect_size var fill = self.color if self.color_2.x > -0.5 { fill = mix(self.color, self.color_2, mix(self.pos.y, self.pos.x, self.gradient_fill_horizontal)) } let lo = vec2(self.border_inset.x, self.border_inset.y) let hi = self.rect_size - vec2(self.border_inset.z, self.border_inset.w) let h = (hi - lo) * 0.5 let c = lo + h let d = cb_rbox(p, c, h, max(self.border_radius, 1.0)) let grain = cb_slate(p + self.rect_size * 0.29, px, step(0.9, fill.a)) / cb_slate_c() let col = fill.rgb * grain let a = Finish.cover(d, px) * fill.a var out = vec4(col * a, a) if self.border_size > 0.0 { out = Finish.over(out, cb_stroke(abs(d + max(self.border_size, 0.6)), max(self.border_size * 0.6, 0.5), p, px, self.border_color.rgb, self.border_color.a)) } return out } // --------------------------------------------------------------------------- // LISTS. Rows of 22 points of words on the board; a hovered row is // underlined in faint chalk; the chosen row is a soft band drawn round and // lightly rubbed, so its chalk words still stand out on it. mod.widgets.ListItemOne.height = 22.0 mod.widgets.ListItem.padding = mod.turtle.Inset{left: 10.0 right: 8.0 top: 0.0 bottom: 0.0} let cb_row_band = fn(p: vec2, lo: vec2, hi: vec2, px: float, seed: float) -> vec4 { let h = vec2((hi.x - lo.x) * 0.5 - 4.0, (hi.y - lo.y) * 0.5 - 1.5) let c = vec2((lo.x + hi.x) * 0.5, (lo.y + hi.y) * 0.5) let d0 = cb_rbox(p, c, h, h.y) var out = cb_rub(d0 + 1.2, p + vec2(3.0, 41.0 + seed), px, cb_chalk_c(), 0.22, 1.0) out = Finish.over(out, cb_hatch(d0 + 1.5, p + vec2(seed, 7.0), px, cb_chalk_c(), 3.2, 0.32, seed)) return Finish.over(out, cb_outline(d0, p, px, 0.65, cb_chalk_c(), 0.8, vec2(seed, 3.0))) } mod.widgets.ListItem.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * self.rect_size let o = self.rect_pos let live = 1.0 - self.disabled let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o let hi = vec2(Finish.snap(o.x + self.rect_size.x, px), Finish.snap(o.y + self.rect_size.y, px)) - o var out = vec4(0.0, 0.0, 0.0, 0.0) if self.selected > 0.5 { out = cb_row_band(p, lo, hi - vec2(10.0, 0.0), px, 5.0) } let uy = hi.y - 2.5 + (Finish.noise(vec2(p.x * 0.07, 5.0)) - 0.5) * 0.6 let du = cb_seg(p, vec2(lo.x + 8.0, uy), vec2(hi.x - 14.0, uy)) out = Finish.over(out, cb_stroke(du, 0.45, p, px, cb_chalk_c(), (0.35 * self.hover + 0.25 * self.down) * live * (1.0 - self.selected))) return out } mod.widgets.ListItem.draw_text.color = #e8eae6 // The open list of a drop-down: a small slate held over the board, drawn // round in chalk; the row under the pointer is rubbed with a little chalk, // the chosen row carries a chalk tick. mod.widgets.PopupMenuFlat.clip_x = false mod.widgets.PopupMenuFlat.clip_y = false mod.widgets.PopupMenuFlat.draw_bg.vertex = fn() { let g = cb_grow(self.rect_pos, self.rect_size, 6.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.PopupMenuFlat.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 6.0 let size = self.rect_size let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let o = self.rect_pos let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o let hi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y, px)) - o let h = (hi - lo) * 0.5 let c = lo + h let d0 = cb_rbox(p, c, h, 6.0) let a = Finish.cover(d0 + 0.5, px) let face = cb_slate(p + vec2(17.0, 9.0), px, 1.0) * 0.92 var out = vec4(face * a, a) return Finish.over(out, cb_outline(d0, p, px, 0.9, cb_chalk_c(), 0.92, size * 0.07)) } mod.widgets.PopupMenuItem.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * self.rect_size let c = self.rect_size * 0.5 let live = 1.0 - self.disabled let h = vec2(c.x - 4.0, c.y - 2.0) let d = cb_rbox(p, c, h, h.y) var out = cb_rub(d, p + vec2(5.0, 9.0), px, cb_chalk_c(), 0.30, self.hover * live) let m0 = vec2(6.0, c.y) let m1 = vec2(8.8, c.y + 3.0) let m2 = vec2(13.6, c.y - 3.8) let mk = min(cb_seg(p, m0, m1), cb_seg(p, m1, m2)) return Finish.over(out, cb_stroke(mk, 0.85, p, px, cb_chalk_c(), self.active)) } // The dock's tabs: the tab in use is drawn round in chalk on its top and // sides, open at the foot where it joins the panel below; the others take a // breath of dust under the pointer. mod.widgets.Tab.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 3.0 bottom: 3.0} mod.widgets.Tab.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * self.rect_size let o = self.rect_pos let lo = vec2(Finish.snap(o.x + 1.5, px), Finish.snap(o.y + 3.0, px)) - o let hi = vec2(Finish.snap(o.x + self.rect_size.x - 1.5, px), Finish.snap(o.y + self.rect_size.y, px)) - o + vec2(0.0, 8.0) let h = (hi - lo) * 0.5 let c = lo + h let d = cb_rbox(p, c, h, 6.0) var out = cb_rub(d + 1.5, p, px, cb_chalk_c(), 0.08, self.hover * (1.0 - self.active)) return Finish.over(out, cb_outline(d, p, px, 0.8, cb_chalk_c(), 0.9 * self.active, vec2(7.0, 7.0))) } 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 = self.rect_size.y - 1.0 + (Finish.noise(vec2(p.x * 0.05, 1.0)) - 0.5) * 0.4 return cb_stroke(abs(p.y - yb), 0.55, p, px, cb_chalk_c(), 0.6) } // --------------------------------------------------------------------------- // CHIPS AND BADGES. A chip is a small chalk box (a soft rub of its colour when // it has one, bare until touched when a ghost); a badge is a small box drawn // round with its count, a rub of its colour inside. mod.widgets.ChipFlat.size = mod.widgets.ChipSize.Small mod.widgets.Chip.size = mod.widgets.ChipSize.Small mod.widgets.ChipFlat.draw_bg.vertex = fn() { let g = cb_grow(self.rect_pos, self.rect_size, 5.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.ChipFlat.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 5.0 let ch = min(26.0, self.rect_size.y) let dy = (self.rect_size.y - ch) * 0.5 let sz = vec2(self.rect_size.x, ch) let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let pp = (p - vec2(0.0, dy) + vec2(m, m)) / (sz + vec2(2.0 * m, 2.0 * m)) let has_fill = step(0.05, self.color.a) let mx = max(self.color.r, max(self.color.g, self.color.b)) let mn = min(self.color.r, min(self.color.g, self.color.b)) let neutral = 1.0 - step(0.25, (mx - mn) / max(mx, 0.01)) let kind = mix(mix(6.0, 5.0, step(0.01, self.border_size)), mix(7.0, 0.0, neutral), has_fill) let tint = vec4(self.color.rgb, has_fill) return cb_button(pp, sz, self.rect_pos + vec2(0.0, dy), px, self.hover, self.down, self.keyed, 0.0, 0.0, 0.0, vec4(6.0, 6.0, 6.0, 6.0), kind, m, tint, 0.0) } mod.widgets.BadgePalette.neutral = #353a37 mod.widgets.BadgePalette.on_neutral = #e8eae6 mod.widgets.BadgeFlat.draw_text.text_style = mod.theme.font_label{font_size: 12.0} mod.widgets.Badge.draw_text.text_style = mod.theme.font_label{font_size: 12.0} mod.widgets.BadgeFlat.clip_x = false mod.widgets.BadgeFlat.clip_y = false mod.widgets.BadgeFlat.draw_bg.vertex = fn() { let g = cb_grow(self.rect_pos, self.rect_size, 4.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.BadgeFlat.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 4.0 let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let bh = min(20.0, self.rect_size.y - 1.0) let y0 = (self.rect_size.y - bh) * 0.5 let lo = vec2(1.0, y0) let hi = vec2(self.rect_size.x - 1.0, y0 + bh) let h = (hi - lo) * 0.5 let c = lo + h let r = mix(5.0, h.y, step(self.rect_size.x, self.rect_size.y + 0.5) * step(self.rect_size.y, 12.5)) let d0 = cb_rbox(p, c, h, r) let lum = Finish.lum(self.color.rgb) var out = cb_rub(d0 + 1.2, p + vec2(9.0, 4.0), px, self.color.rgb, 0.4, step(0.3, lum)) out = Finish.over(out, cb_outline(d0, p, px, 0.75, cb_chalk_c(), 0.9, vec2(2.0, 8.0))) return out * self.color.a } // The spinner keeps its stock turning face (it runs on the pass clock, which // a face may not read), in chalk with a faint track ring. mod.widgets.SpinnerFlat.draw_bg.color = #e8eae6 mod.widgets.SpinnerFlat.draw_bg.stroke_width = 3.0 mod.widgets.SpinnerFlat.draw_bg.track_alpha = 0.25 // Rules between groups: a faint line of chalk drawn by hand. 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 = self.rect_size.y * 0.5 + (Finish.noise(vec2(p.x * 0.04, 3.0)) - 0.5) * 0.8 return cb_stroke(abs(p.y - y), 0.5, p, px, cb_chalk_c(), 0.45) } 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 = self.rect_size.x * 0.5 + (Finish.noise(vec2(3.0, p.y * 0.04)) - 0.5) * 0.8 return cb_stroke(abs(p.x - x), 0.5, p, px, cb_chalk_c(), 0.45) } // --------------------------------------------------------------------------- // WORDS IN CHALK. Every pale word is chalk: where the board's grain did not // take it, a device pixel here and there shows a little of the slate through // the letter, so the hand reads as dry chalk while every letter keeps its // whole shape. The grain is laid in the word's own points (its glyph's place // and the point inside it), so it is never the same on two letters and does // not move when a page scrolls. Dark words (on a chalk field) are clean. // Written once, handed to every label. let cb_words = fn() { if self.texture_index > 0.5 && self.texture_index < 1.5 { return self.sample_text_pixel() } if self.texture_index > 2.5 { return self.sample_text_pixel() } let c = self.get_color() let dxt = length(dFdx(self.t)) let dyt = length(dFdy(self.t)) var a0 = 0.0 if self.texture_index < 0.5 { let tex_size = self.grayscale_texture.size() let scale = (dxt + dyt) * tex_size.x * 0.5 let ht = vec2(0.5 / tex_size.x, 0.5 / tex_size.y) a0 = self.sdf(scale, clamp(self.t.xy, self.t_min + ht, self.t_max - ht), c) } else { let tex_size = self.msdf_texture.size() let scale = (dxt + dyt) * tex_size.x * 0.5 let ht = vec2(0.5 / tex_size.x, 0.5 / tex_size.y) a0 = self.msdf(scale, clamp(self.t.xy, self.t_min + ht, self.t_max - ht), c) } let pale = step(0.45, dot(c.rgb, vec3(0.299, 0.587, 0.114))) let dpi = max(self.draw_pass.dpi_factor, 0.5) let wp = (self.rect_pos + self.pos * self.rect_size) * dpi let g1 = Finish.noise(wp * 0.5 + vec2(3.0, 7.0)) let g2 = Finish.hash(floor(wp) + vec2(11.0, 5.0)) let keep = smoothstep(0.28, 0.52, g1 * 0.55 + g2 * 0.45) let a = a0 * mix(1.0, 0.70 + 0.30 * keep, pale) * c.a return vec4(c.rgb * a, a) } mod.widgets.Label.draw_text.pixel = cb_words mod.widgets.H1.draw_text.pixel = cb_words mod.widgets.H2.draw_text.pixel = cb_words mod.widgets.H3.draw_text.pixel = cb_words mod.widgets.H4.draw_text.pixel = cb_words mod.widgets.H5.draw_text.pixel = cb_words mod.widgets.H6.draw_text.pixel = cb_words mod.widgets.ButtonFlat.draw_text.pixel = cb_words mod.widgets.Button.draw_text.pixel = cb_words mod.widgets.ButtonFlatter.draw_text.pixel = cb_words mod.widgets.ButtonPrimary.draw_text.pixel = cb_words mod.widgets.ButtonSecondary.draw_text.pixel = cb_words mod.widgets.ButtonTertiary.draw_text.pixel = cb_words mod.widgets.ButtonDanger.draw_text.pixel = cb_words mod.widgets.ButtonOutline.draw_text.pixel = cb_words mod.widgets.ButtonXs.draw_text.pixel = cb_words mod.widgets.ButtonSm.draw_text.pixel = cb_words mod.widgets.ButtonLg.draw_text.pixel = cb_words mod.widgets.CheckBoxFlat.draw_text.pixel = cb_words mod.widgets.CheckBox.draw_text.pixel = cb_words mod.widgets.ToggleFlat.draw_text.pixel = cb_words mod.widgets.Toggle.draw_text.pixel = cb_words mod.widgets.RadioButtonFlat.draw_text.pixel = cb_words mod.widgets.RadioButton.draw_text.pixel = cb_words mod.widgets.RadioButtonTabFlat.draw_text.pixel = cb_words mod.widgets.RadioButtonTab.draw_text.pixel = cb_words mod.widgets.SliderFlat.draw_text.pixel = cb_words mod.widgets.Slider.draw_text.pixel = cb_words mod.widgets.SliderMinimal.draw_text.pixel = cb_words mod.widgets.SliderRoundFlat.draw_text.pixel = cb_words mod.widgets.SliderRound.draw_text.pixel = cb_words mod.widgets.RotaryFlat.draw_text.pixel = cb_words mod.widgets.Rotary.draw_text.pixel = cb_words mod.widgets.RotaryKnob.draw_text.pixel = cb_words mod.widgets.DropDownFlat.draw_text.pixel = cb_words mod.widgets.DropDown.draw_text.pixel = cb_words mod.widgets.SegmentedControlFlat.draw_text.pixel = cb_words mod.widgets.SegmentedControl.draw_text.pixel = cb_words mod.widgets.Tabs.draw_text.pixel = cb_words mod.widgets.ListItem.draw_text.pixel = cb_words mod.widgets.ListItemOne.draw_text.pixel = cb_words mod.widgets.ChipFlat.draw_text.pixel = cb_words mod.widgets.Chip.draw_text.pixel = cb_words // --------------------------------------------------------------------------- // STATES. Every held state is written as a plain value that cuts in at once: // the library writes some as snap() values, which a state held as a default // (the states page) never applies. Live, the cut is the same as the snap. mod.widgets.ButtonFlat.animator.hover.on.from = {all: Snap} mod.widgets.ButtonFlat.animator.hover.on.apply = {draw_bg: {down: 0.0 hover: 1.0} draw_text: {down: 0.0 hover: 1.0}} mod.widgets.ButtonFlat.animator.hover.down.from = {all: Snap} mod.widgets.ButtonFlat.animator.hover.down.apply = {draw_bg: {down: 1.0 hover: 1.0} draw_text: {down: 1.0 hover: 1.0}} mod.widgets.CheckBoxFlat.animator.hover.on.from = {all: Snap} mod.widgets.CheckBoxFlat.animator.hover.on.apply = {draw_bg: {down: 0.0 hover: 1.0} draw_text: {down: 0.0 hover: 1.0}} mod.widgets.CheckBoxFlat.animator.hover.down.from = {all: Snap} mod.widgets.CheckBoxFlat.animator.hover.down.apply = {draw_bg: {down: 1.0 hover: 1.0} draw_text: {down: 1.0 hover: 1.0}} mod.widgets.ToggleFlat.animator.hover.on.from = {all: Snap} mod.widgets.ToggleFlat.animator.hover.on.apply = {draw_bg: {down: 0.0 hover: 1.0} draw_text: {down: 0.0 hover: 1.0}} mod.widgets.ToggleFlat.animator.hover.down.from = {all: Snap} mod.widgets.ToggleFlat.animator.hover.down.apply = {draw_bg: {down: 1.0 hover: 1.0} draw_text: {down: 1.0 hover: 1.0}} mod.widgets.RadioButtonFlat.animator.hover.on.from = {all: Snap} mod.widgets.RadioButtonFlat.animator.hover.on.apply = {draw_bg: {down: 0.0 hover: 1.0} draw_text: {down: 0.0 hover: 1.0}} mod.widgets.RadioButtonFlat.animator.hover.down.from = {all: Snap} mod.widgets.RadioButtonFlat.animator.hover.down.apply = {draw_bg: {down: 1.0 hover: 1.0} draw_text: {down: 1.0 hover: 1.0}} mod.widgets.TextInputFlat.animator.hover.on.from = {all: Snap} mod.widgets.TextInputFlat.animator.hover.on.apply = {draw_bg: {down: 0.0 hover: 1.0} draw_text: {down: 0.0 hover: 1.0}} mod.widgets.TextInputFlat.animator.hover.down.from = {all: Snap} mod.widgets.TextInputFlat.animator.hover.down.apply = {draw_bg: {down: 1.0 hover: 1.0} draw_text: {down: 1.0 hover: 1.0}} mod.widgets.DropDownFlat.animator.hover.on.from = {all: Snap} mod.widgets.DropDownFlat.animator.hover.on.apply = {draw_bg: {down: 0.0 hover: 1.0} draw_text: {down: 0.0 hover: 1.0}} mod.widgets.DropDownFlat.animator.hover.down.from = {all: Snap} mod.widgets.DropDownFlat.animator.hover.down.apply = {draw_bg: {down: 1.0 hover: 1.0} draw_text: {down: 1.0 hover: 1.0}} mod.widgets.SliderFlat.animator.hover.on.from = {all: Snap} mod.widgets.SliderFlat.animator.hover.on.apply = {draw_bg: {hover: 1.0} draw_text: {hover: 1.0}} mod.widgets.SliderFlat.animator.drag.on.from = {all: Snap} mod.widgets.SliderFlat.animator.drag.on.apply = {draw_bg: {drag: 1.0} draw_text: {drag: 1.0}} // --------------------------------------------------------------------------- // THE BOARD under everything: the window's own ground. The slate over its // whole face; a thin haze of chalk dust left by old lessons and the long // soft wipes of an eraser, both kept faint under the content and heavier // toward the frame; and a broad, soft fall of light toward the corners. Round // it a wooden frame: a rail along the top where the caption is, thin stiles // down the sides, and along the foot the ledge the chalk lies on, a little // dust spilled on it, with a stick of chalk and the eraser at the right. let cb_wipes = fn(p: vec2, ang: float, spacing: float, seed: float) -> float { let cs = cos(ang) let sn = sin(ang) let u = p.x * cs + p.y * sn let v = p.y * cs - p.x * sn // the lanes bend with the arm's swing, differently from place to place, // so no two wipes run parallel for long let vb = v + (Finish.noise(vec2(u * 0.0028 + seed, v * 0.004)) - 0.5) * spacing * 1.4 let k = floor(vb / spacing) let hk = Finish.hash(vec2(k, seed)) let vc = (k + 0.5 + (hk - 0.5) * 0.3) * spacing let hw = spacing * (0.09 + 0.07 * Finish.hash(vec2(k + 5.0, seed))) * (0.7 + 0.6 * Finish.noise(vec2(u * 0.015, k + seed))) let edge = abs(vb - vc) / max(hw, 1.0) + (Finish.noise(vec2(u * 0.08, vb * 0.3 + seed)) - 0.5) * 0.35 let band = 1.0 - smoothstep(0.40, 1.0, edge) let run = smoothstep(0.52, 0.68, Finish.noise(vec2(u * 0.007 + hk * 9.0, k * 2.3 + seed))) let streak = 0.5 + 0.5 * Finish.noise(vec2(u * 0.02, (vb - vc) * 0.9 + seed)) return band * run * streak } let cb_wood = fn(along: float, across: float, px: float, seed: float) -> vec3 { let base = vec3(0.330, 0.205, 0.115) let fig = Finish.noise(vec2(along * 0.012 + seed, across * 0.35)) let lines = Finish.noise(vec2(along * 0.03 + seed * 3.0, across * 1.6 + fig * 3.0)) let pores = Finish.noise(vec2(along * 0.35, across * 3.5 + seed)) var col = base * (0.80 + 0.30 * fig) col = mix(col, vec3(0.180, 0.105, 0.055), smoothstep(0.55, 0.85, lines) * 0.55) col = col * (0.94 + 0.10 * pores) + g3(Finish.grain(vec2(along, across) / px, 0.010)) return col } mod.widgets.Window.show_bg = true mod.widgets.Window.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * self.rect_size let sz = self.rect_size var col = cb_slate(p, px, 1.0) // dust and wipes, faint under the content, heavier at the frame let e = min(min(p.x, sz.x - p.x), min(p.y, sz.y - p.y)) let edge = 1.0 - smoothstep(8.0, 70.0, e) let haze = smoothstep(0.38, 0.85, Finish.fbm(p * 0.004 + vec2(7.0, 2.0))) let w1 = cb_wipes(p, -0.12, 90.0, 1.0) let w2 = cb_wipes(p + vec2(400.0, 37.0), 0.20, 130.0, 7.0) let dust = haze * (0.024 + 0.045 * edge) + max(w1, w2 * 0.8) * (0.026 + 0.05 * edge) col = col + (cb_chalk_c() - col) * dust // the fall of light toward the corners let q = (p - sz * 0.5) / (sz * 0.5) let vig = smoothstep(0.50, 1.50, length(q)) col = col * (1.0 - 0.40 * vig) // THE FRAME. Rails 27 points along the top, 5 down the sides, the ledge // 10 at the foot; mitred at the corners. let tt = 27.0 let ts = 5.0 let tb = 10.0 let dt = p.y - tt let dl = p.x - ts let dr = sz.x - ts - p.x let db = sz.y - tb - p.y let fd = min(min(dt, db), min(dl, dr)) // the slate darkened in the frame's shade along its inner edge col = col * (1.0 - 0.30 * (1.0 - smoothstep(0.0, 4.0, fd))) if fd < 0.0 { var wood = vec3(0.0, 0.0, 0.0) if dt <= min(min(dl, dr), db) && p.y < tt { // top rail: grain along x, lit from above wood = cb_wood(p.x, p.y, px, 1.0) let v = p.y / tt wood = wood * (1.10 - 0.25 * smoothstep(0.55, 1.0, v)) wood = mix(wood, wood * 1.45, Finish.band(0.0 - p.y, 0.0, 1.2, px) * 0.5) } else { if db <= min(dl, dr) { // the ledge: its top face catching the light, chalk dust on // it, and its front edge a darker band let yy = p.y - (sz.y - tb) wood = cb_wood(p.x, yy, px, 5.0) let topf = 1.0 - smoothstep(4.5, 5.5, yy) wood = mix(wood * 0.78, wood * 1.18, topf) let spill = smoothstep(0.55, 0.85, Finish.noise(vec2(p.x * 0.05, yy * 0.4))) * topf let specks = step(0.82, Finish.hash(floor(p / px))) * topf wood = wood + (cb_chalk_c() - wood) * clamp(spill * 0.35 + specks * 0.18 * (0.3 + spill), 0.0, 0.6) } else { // a side stile: grain down it let xx = min(p.x, sz.x - p.x) wood = cb_wood(p.y, xx, px, 3.0) wood = wood * (0.92 + 0.10 * smoothstep(0.0, ts, xx)) } } // a dark seam where the frame meets the slate and at the mitres wood = mix(wood, wood * 0.55, (1.0 - smoothstep(0.0, 1.2, 0.0 - fd)) * 0.8) col = wood } // On the ledge, at the right: a stick of chalk and the eraser. let ly = sz.y - tb + 2.5 let sc = vec2(sz.x - 118.0, ly) let ec = vec2(sz.x - 62.0, sz.y - tb - 1.0) let lie = min(cb_rbox(p, sc + vec2(0.8, 1.6), vec2(16.0, 2.0), 2.0), cb_rbox(p, ec + vec2(1.0, 4.4), vec2(24.0, 2.0), 1.5)) col = col * (1.0 - 0.45 * (1.0 - smoothstep(-0.5, 1.6, lie))) let stick = cb_rbox(p, sc, vec2(16.0, 2.2), 2.2) let sa = Finish.cover(stick, px) let scol = vec3(0.93, 0.93, 0.91) * (0.86 + 0.14 * Finish.noise(p * 0.9)) * (1.05 - 0.25 * smoothstep(-2.0, 2.0, p.y - sc.y)) col = mix(col, scol, sa) let er = cb_rbox(p, ec, vec2(24.0, 5.0), 1.5) let felt = cb_rbox(p, ec + vec2(0.0, 3.2), vec2(23.5, 1.8), 1.0) let ea = Finish.cover(er, px) var ecol = cb_wood(p.x * 1.3, p.y, px, 9.0) * 1.25 ecol = mix(ecol, vec3(0.62, 0.62, 0.60) * (0.8 + 0.2 * Finish.noise(p * 1.4)), Finish.cover(felt, px)) col = mix(col, ecol, ea) return vec4(col, 1.0) } // A splitter is a seam between two panes: down its middle a faint line of // chalk; under the hand, held or focused, the line is gone over along the // grip's length. mod.widgets.Splitter.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * self.rect_size let v = self.is_vertical let across = mix(p.y, p.x, v) let along = mix(p.x, p.y, v) let thick = mix(self.rect_size.y, self.rect_size.x, v) let span = mix(self.rect_size.x, self.rect_size.y, v) let m = thick * 0.5 + (Finish.noise(vec2(along * 0.03, 2.0)) - 0.5) * 0.6 var out = cb_stroke(abs(across - m), 0.4, p, px, cb_chalk_c(), 0.28) let act = max(max(self.hover, self.drag), self.focus) let inbar = 1.0 - smoothstep(self.bar_size * 0.5 - 2.0, self.bar_size * 0.5, abs(along - span * 0.5)) return Finish.over(out, cb_stroke(abs(across - m), 0.8, p, px, cb_chalk_c(), act * inbar)) } // ---------------------------------------------------------------- switches and meter // THE SWITCHES are the sheet's own toggle, in proportion with the labels // and the controls beside them: the rocker and the slide switch take the // toggle's face (the pill, its hatching and its dot), its size and shape, // its quad and its label gap; the rocker's dot is pale yellow chalk and the // slide's light blue, so the two kinds tell apart. mod.widgets.ToggleRocker.label_walk.margin = mod.turtle.Inset{left: 47.0 right: 3.0 top: 0.0 bottom: 0.0} mod.widgets.ToggleSlide.label_walk.margin = mod.turtle.Inset{left: 47.0 right: 3.0 top: 0.0 bottom: 0.0} mod.widgets.ToggleRocker.draw_bg.size = uniform(18.0) mod.widgets.ToggleSlide.draw_bg.size = uniform(18.0) mod.widgets.ToggleRocker.min_height = 26.0 mod.widgets.ToggleSlide.min_height = 26.0 mod.widgets.ToggleRocker.draw_bg.pill_aspect = uniform(2.111) mod.widgets.ToggleSlide.draw_bg.pill_aspect = uniform(2.111) mod.widgets.ToggleRocker.draw_bg.knob_inset = uniform(9.0) mod.widgets.ToggleSlide.draw_bg.knob_inset = uniform(9.0) mod.widgets.ToggleRocker.clip_x = false mod.widgets.ToggleRocker.clip_y = false mod.widgets.ToggleSlide.clip_x = false mod.widgets.ToggleSlide.clip_y = false mod.widgets.ToggleRocker.draw_bg.vertex = mod.widgets.ToggleFlat.draw_bg.vertex mod.widgets.ToggleSlide.draw_bg.vertex = mod.widgets.ToggleFlat.draw_bg.vertex mod.widgets.ToggleRocker.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return cb_toggle(self.pos, self.rect_size, self.rect_pos, px, self.active, self.drag_pos, self.drag, self.hover, self.down, self.focus, self.disabled, self.error, self.mark_at_end, 9.0, self.size, self.pill_aspect) } mod.widgets.ToggleSlide.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return cb_toggle(self.pos, self.rect_size, self.rect_pos, px, self.active, self.drag_pos, self.drag, self.hover, self.down, self.focus, self.disabled, self.error, self.mark_at_end, 2.0, self.size, self.pill_aspect) } // The readouts and the screen: the instrument frame round a clean patch of // slate. mod.widgets.ScreenView.clip_x = false mod.widgets.ScreenView.clip_y = false mod.widgets.ScreenView.draw_bg.vertex = fn() { let g = cb_grow(self.rect_pos, self.rect_size, 4.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.ScreenView.draw_bg.pixel = fn() { let m = 4.0 let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let lo = vec2(Finish.snap(self.rect_pos.x, px), Finish.snap(self.rect_pos.y, px)) - self.rect_pos let hi = vec2(Finish.snap(self.rect_pos.x + self.rect_size.x, px), Finish.snap(self.rect_pos.y + self.rect_size.y, px) - px) - self.rect_pos let b = 3.0 let face = cb_screen_face(p, px, self.color.rgb) return cb_window(p, lo, hi, 7.0, b, px, face, max(self.recess, 2.5)) } mod.widgets.ScreenView.bezel = 3.0 mod.widgets.ScreenView.sheen = 0.0 // THE DIGITS of a readout are written in chalk: each lit segment a chalk // stroke with the slate showing through its grain, the unlit ones a faint // smudge of dust where they were rubbed out. mod.widgets.Readout.draw_bg.pixel = fn() { let h = max(self.rect_size.y, 1.0) let pp = self.pos * self.rect_size let over = (self.rect_size.x - self.span) * 0.5 let p = (pp - vec2(over, 0.0)) / h let q = vec2(p.x + (p.y - 0.5) * self.slant, p.y) let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let s = self.stroke var lit = 10.0 var all = 10.0 if self.colon > 0.5 { let cx = self.span / h * 0.5 let rr = s * 0.62 let d1 = length(q - vec2(cx, 0.31)) - rr let d2 = length(q - vec2(cx, 0.69)) - rr all = min(d1, d2) lit = all } else { let x0 = (self.span / h - self.glyph_width) * 0.3 let dq = q - vec2(x0, 0.0) let dd = self.digit(dq) let room = self.span / h - x0 - self.glyph_width let dp = length(dq - vec2(self.glyph_width + room * 0.45, 1.0 - s * 0.62)) - s * 0.62 all = min(dd.y, dp) lit = min(dd.x, dp + (1.0 - step(0.5, self.dot)) * 10.0) } let wp = pp + self.rect_pos * 0.37 let body = Finish.cover(lit * h, px) let tooth = cb_tooth(wp, px, lit * h) let keep = smoothstep(0.20, 0.34, tooth) let a_lit = body * mix(0.35, 1.0, keep) let ghost = Finish.cover(all * h, px) * clamp(self.ghost, 0.0, 1.0) * smoothstep(0.35, 0.6, tooth) * (1.0 - body) let a = clamp(a_lit + ghost, 0.0, 1.0) * self.opacity * self.color.a let bright = 0.88 + 0.12 * Finish.noise(wp / px * 0.4) return vec4(self.color.rgb * bright * a, a) } // THE METER sits in the same frame as the readouts and the screen, on the // same clean slate: its scale drawn in pale yellow chalk, the red zone // rubbed in pink chalk, the needle a white chalk line and its pivot a dot of // chalk. mod.widgets.NeedleMeter.clip_x = false mod.widgets.NeedleMeter.clip_y = false mod.widgets.NeedleMeter.draw_bg.border_radius = uniform(7.0) mod.widgets.NeedleMeter.draw_bg.color_needle = uniform(#e8eae6) mod.widgets.NeedleMeter.draw_bg.color_zone = uniform(#f2b3bd) mod.widgets.NeedleMeter.draw_bg.color_scale = uniform(#f3e3a0) mod.widgets.NeedleMeter.draw_bg.vertex = fn() { let g = cb_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 p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let lo = vec2(Finish.snap(self.rect_pos.x, px), Finish.snap(self.rect_pos.y, px)) - self.rect_pos let hi = vec2(Finish.snap(self.rect_pos.x + self.rect_size.x, px), Finish.snap(self.rect_pos.y + self.rect_size.y, px) - px) - self.rect_pos let b = 3.0 let pv = self.pivot() let rr = self.scale_radius() let q = p - pv let r = length(q) let ang = atan2(q.x, 0.0 - q.y) let sweep = self.sweep * 0.017453293 let t = ang / sweep + 0.5 let on_scale = step(-0.0001, t) * step(t, 1.0001) let face = cb_screen_face(p, px, vec3(0.110, 0.125, 0.118)) var out = cb_window(p, lo, hi, self.border_radius, b, px, face, 2.5) // the red zone, rubbed in just inside the scale if self.zone < 0.999 { let zone_t = Finish.cover((self.zone - t) * sweep * r, px) * Finish.cover((t - 1.0) * sweep * r, px) let zd = max(rr + 0.5 - r, r - rr - 3.0) out = Finish.over(out, cb_rub(zd, p, px, self.color_zone.rgb, 0.8, zone_t)) } // the scale: an arc and a tick at every division let arcd = abs(r - rr + cb_wob(p, 0.2, vec2(4.0, 4.0))) out = Finish.over(out, cb_stroke(arcd, 0.55, p, px, self.color_scale.rgb, on_scale)) let n = max(floor(self.majors + 0.5), 1.0) * max(floor(self.minors + 0.5), 1.0) let k = floor(clamp(t, 0.0, 1.0) * n + 0.5) let ak = (k / n - 0.5) * sweep let mn = max(floor(self.minors + 0.5), 1.0) let major = 1.0 - step(0.5, k - mn * floor(k / mn + 0.0001)) let len = mix(self.tick_minor, self.tick_major, major) let tdir = vec2(sin(ak), 0.0 - cos(ak)) let td = cb_seg(p, pv + tdir * (rr - len), pv + tdir * rr) out = Finish.over(out, cb_stroke(td, mix(0.45, 0.65, major), p + vec2(3.0, 0.0), px, self.color_scale.rgb, 1.0)) // the needle, from under the pivot to just past the scale let na = self.needle_angle(self.value) let nd = vec2(sin(na), 0.0 - cos(na)) let nl = cb_seg(p, pv - nd * max(self.pivot_size - px, 0.0), pv + nd * (rr + 3.0)) out = Finish.over(out, cb_stroke(nl, 0.7, p + vec2(9.0, 2.0), px, self.color_needle.rgb, 1.0)) out = Finish.over(out, cb_disc(p, pv, max(self.pivot_size, 2.0), px, self.color_scale.rgb, 0.9, 0.0, 0.0, 0.0, vec2(4.0, 4.0))) return out * self.color.a * self.opacity } mod.widgets.NeedleMeter.draw_label.color = #f3e3a0 // ---------------------------------------------------------------- lamps // The Lamp widget and the bar lamp. A round lamp's halo has its room in the // lamp's own box (the library reserves a lens thickness either side once a // halo is asked for), so the light is never cut at its edge and stays in // its cell. A bar is a short light pipe: six points thick and a little over // twice that long, set at the left of its cell three points in, its glow // held to two and a half points round its socket -- so the whole lamp takes // the first twenty points of its cell and leaves the rest free. mod.widgets.Lamp.halo = 0.3 mod.widgets.Lamp.halo_reach = 1.0 mod.widgets.Lamp.draw_bg.color_accent = uniform(#ff9a2a) mod.widgets.Lamp.draw_bg.vertex = fn() { let m = 2.0 self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m)) } mod.widgets.Lamp.draw_bg.pixel = fn() { let m = 2.0 let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let reach = clamp(self.reach, 0.0, 1.0) let t = self.rect_size.y / (1.0 + 2.0 * reach) let room = t * reach let tb = min(t, 6.0) let lb = tb * 2.1 let c = mix(self.rect_size * 0.5, vec2(3.5 + lb * 0.5, self.rect_size.y * 0.5), self.bar) let hb = mix(vec2(t * 0.5, t * 0.5), vec2(lb * 0.5, tb * 0.5), self.bar) let glow = mix(max(room - 0.5, 2.0), 2.5, self.bar) let res = cb_led(p, c, hb, self.lit, self.intent_color().rgb, 1.0 - step(3.5, self.intent), px, glow) return res * self.opacity } // ---------------------------------------------------------------- tree rows // The rows of a tree are words written on the board one under another, the // floor left calm; the chosen row is the list's soft band. mod.widgets.FileTreeNode.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * self.rect_size let lo = vec2(0.0, 0.0) let hi = self.rect_size - vec2(10.0, 0.0) return cb_row_band(p, lo, hi, px, 11.0 + self.is_even * 7.0) * self.active } true